Spaces:
Configuration error
Configuration error
import { | |
walk, | |
generate, | |
property as resolveProperty, | |
keyword as resolveKeyword | |
} from 'css-tree'; | |
let fingerprintId = 1; | |
const dontRestructure = new Set([ | |
'src' // https://github.com/afelix/csso/issues/50 | |
]); | |
const DONT_MIX_VALUE = { | |
// https://developer.mozilla.org/en-US/docs/Web/CSS/display#Browser_compatibility | |
'display': /table|ruby|flex|-(flex)?box$|grid|contents|run-in/i, | |
// https://developer.mozilla.org/en/docs/Web/CSS/text-align | |
'text-align': /^(start|end|match-parent|justify-all)$/i | |
}; | |
const SAFE_VALUES = { | |
cursor: [ | |
'auto', 'crosshair', 'default', 'move', 'text', 'wait', 'help', | |
'n-resize', 'e-resize', 's-resize', 'w-resize', | |
'ne-resize', 'nw-resize', 'se-resize', 'sw-resize', | |
'pointer', 'progress', 'not-allowed', 'no-drop', 'vertical-text', 'all-scroll', | |
'col-resize', 'row-resize' | |
], | |
overflow: [ | |
'hidden', 'visible', 'scroll', 'auto' | |
], | |
position: [ | |
'static', 'relative', 'absolute', 'fixed' | |
] | |
}; | |
const NEEDLESS_TABLE = { | |
'border-width': ['border'], | |
'border-style': ['border'], | |
'border-color': ['border'], | |
'border-top': ['border'], | |
'border-right': ['border'], | |
'border-bottom': ['border'], | |
'border-left': ['border'], | |
'border-top-width': ['border-top', 'border-width', 'border'], | |
'border-right-width': ['border-right', 'border-width', 'border'], | |
'border-bottom-width': ['border-bottom', 'border-width', 'border'], | |
'border-left-width': ['border-left', 'border-width', 'border'], | |
'border-top-style': ['border-top', 'border-style', 'border'], | |
'border-right-style': ['border-right', 'border-style', 'border'], | |
'border-bottom-style': ['border-bottom', 'border-style', 'border'], | |
'border-left-style': ['border-left', 'border-style', 'border'], | |
'border-top-color': ['border-top', 'border-color', 'border'], | |
'border-right-color': ['border-right', 'border-color', 'border'], | |
'border-bottom-color': ['border-bottom', 'border-color', 'border'], | |
'border-left-color': ['border-left', 'border-color', 'border'], | |
'margin-top': ['margin'], | |
'margin-right': ['margin'], | |
'margin-bottom': ['margin'], | |
'margin-left': ['margin'], | |
'padding-top': ['padding'], | |
'padding-right': ['padding'], | |
'padding-bottom': ['padding'], | |
'padding-left': ['padding'], | |
'font-style': ['font'], | |
'font-variant': ['font'], | |
'font-weight': ['font'], | |
'font-size': ['font'], | |
'font-family': ['font'], | |
'list-style-type': ['list-style'], | |
'list-style-position': ['list-style'], | |
'list-style-image': ['list-style'] | |
}; | |
function getPropertyFingerprint(propertyName, declaration, fingerprints) { | |
const realName = resolveProperty(propertyName).basename; | |
if (realName === 'background') { | |
return propertyName + ':' + generate(declaration.value); | |
} | |
const declarationId = declaration.id; | |
let fingerprint = fingerprints[declarationId]; | |
if (!fingerprint) { | |
switch (declaration.value.type) { | |
case 'Value': | |
const special = {}; | |
let vendorId = ''; | |
let iehack = ''; | |
let raw = false; | |
declaration.value.children.forEach(function walk(node) { | |
switch (node.type) { | |
case 'Value': | |
case 'Brackets': | |
case 'Parentheses': | |
node.children.forEach(walk); | |
break; | |
case 'Raw': | |
raw = true; | |
break; | |
case 'Identifier': { | |
const { name } = node; | |
if (!vendorId) { | |
vendorId = resolveKeyword(name).vendor; | |
} | |
if (/\\[09]/.test(name)) { | |
iehack = RegExp.lastMatch; | |
} | |
if (SAFE_VALUES.hasOwnProperty(realName)) { | |
if (SAFE_VALUES[realName].indexOf(name) === -1) { | |
special[name] = true; | |
} | |
} else if (DONT_MIX_VALUE.hasOwnProperty(realName)) { | |
if (DONT_MIX_VALUE[realName].test(name)) { | |
special[name] = true; | |
} | |
} | |
break; | |
} | |
case 'Function': { | |
let { name } = node; | |
if (!vendorId) { | |
vendorId = resolveKeyword(name).vendor; | |
} | |
if (name === 'rect') { | |
// there are 2 forms of rect: | |
// rect(<top>, <right>, <bottom>, <left>) - standart | |
// rect(<top> <right> <bottom> <left>) β backwards compatible syntax | |
// only the same form values can be merged | |
const hasComma = node.children.some((node) => | |
node.type === 'Operator' && node.value === ',' | |
); | |
if (!hasComma) { | |
name = 'rect-backward'; | |
} | |
} | |
special[name + '()'] = true; | |
// check nested tokens too | |
node.children.forEach(walk); | |
break; | |
} | |
case 'Dimension': { | |
const { unit } = node; | |
if (/\\[09]/.test(unit)) { | |
iehack = RegExp.lastMatch; | |
} | |
switch (unit) { | |
// is not supported until IE11 | |
case 'rem': | |
// v* units is too buggy across browsers and better | |
// don't merge values with those units | |
case 'vw': | |
case 'vh': | |
case 'vmin': | |
case 'vmax': | |
case 'vm': // IE9 supporting "vm" instead of "vmin". | |
special[unit] = true; | |
break; | |
} | |
break; | |
} | |
} | |
}); | |
fingerprint = raw | |
? '!' + fingerprintId++ | |
: '!' + Object.keys(special).sort() + '|' + iehack + vendorId; | |
break; | |
case 'Raw': | |
fingerprint = '!' + declaration.value.value; | |
break; | |
default: | |
fingerprint = generate(declaration.value); | |
} | |
fingerprints[declarationId] = fingerprint; | |
} | |
return propertyName + fingerprint; | |
} | |
function needless(props, declaration, fingerprints) { | |
const property = resolveProperty(declaration.property); | |
if (NEEDLESS_TABLE.hasOwnProperty(property.basename)) { | |
const table = NEEDLESS_TABLE[property.basename]; | |
for (const entry of table) { | |
const ppre = getPropertyFingerprint(property.prefix + entry, declaration, fingerprints); | |
const prev = props.hasOwnProperty(ppre) ? props[ppre] : null; | |
if (prev && (!declaration.important || prev.item.data.important)) { | |
return prev; | |
} | |
} | |
} | |
} | |
function processRule(rule, item, list, props, fingerprints) { | |
const declarations = rule.block.children; | |
declarations.forEachRight(function(declaration, declarationItem) { | |
const { property } = declaration; | |
const fingerprint = getPropertyFingerprint(property, declaration, fingerprints); | |
const prev = props[fingerprint]; | |
if (prev && !dontRestructure.has(property)) { | |
if (declaration.important && !prev.item.data.important) { | |
props[fingerprint] = { | |
block: declarations, | |
item: declarationItem | |
}; | |
prev.block.remove(prev.item); | |
// TODO: use it when we can refer to several points in source | |
// declaration.loc = { | |
// primary: declaration.loc, | |
// merged: prev.item.data.loc | |
// }; | |
} else { | |
declarations.remove(declarationItem); | |
// TODO: use it when we can refer to several points in source | |
// prev.item.data.loc = { | |
// primary: prev.item.data.loc, | |
// merged: declaration.loc | |
// }; | |
} | |
} else { | |
const prev = needless(props, declaration, fingerprints); | |
if (prev) { | |
declarations.remove(declarationItem); | |
// TODO: use it when we can refer to several points in source | |
// prev.item.data.loc = { | |
// primary: prev.item.data.loc, | |
// merged: declaration.loc | |
// }; | |
} else { | |
declaration.fingerprint = fingerprint; | |
props[fingerprint] = { | |
block: declarations, | |
item: declarationItem | |
}; | |
} | |
} | |
}); | |
if (declarations.isEmpty) { | |
list.remove(item); | |
} | |
} | |
export default function restructBlock(ast) { | |
const stylesheetMap = {}; | |
const fingerprints = Object.create(null); | |
walk(ast, { | |
visit: 'Rule', | |
reverse: true, | |
enter(node, item, list) { | |
const stylesheet = this.block || this.stylesheet; | |
const ruleId = (node.pseudoSignature || '') + '|' + node.prelude.children.first.id; | |
let ruleMap; | |
let props; | |
if (!stylesheetMap.hasOwnProperty(stylesheet.id)) { | |
ruleMap = {}; | |
stylesheetMap[stylesheet.id] = ruleMap; | |
} else { | |
ruleMap = stylesheetMap[stylesheet.id]; | |
} | |
if (ruleMap.hasOwnProperty(ruleId)) { | |
props = ruleMap[ruleId]; | |
} else { | |
props = {}; | |
ruleMap[ruleId] = props; | |
} | |
processRule.call(this, node, item, list, props, fingerprints); | |
} | |
}); | |
}; | |