262 lines
8.9 KiB
JavaScript
262 lines
8.9 KiB
JavaScript
|
var resolveProperty = require('../../utils/names.js').property;
|
|||
|
var resolveKeyword = require('../../utils/names.js').keyword;
|
|||
|
var walkRulesRight = require('../../utils/walk.js').rulesRight;
|
|||
|
var translate = require('../../utils/translate.js');
|
|||
|
var dontRestructure = {
|
|||
|
'src': 1 // https://github.com/afelix/csso/issues/50
|
|||
|
};
|
|||
|
|
|||
|
var 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
|
|||
|
};
|
|||
|
|
|||
|
var CURSOR_SAFE_VALUE = [
|
|||
|
'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'
|
|||
|
];
|
|||
|
|
|||
|
var 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) {
|
|||
|
var realName = resolveProperty(propertyName).name;
|
|||
|
|
|||
|
if (realName === 'background' ||
|
|||
|
(realName === 'filter' && declaration.value.sequence.first().type === 'Progid')) {
|
|||
|
return propertyName + ':' + translate(declaration.value);
|
|||
|
}
|
|||
|
|
|||
|
var declarationId = declaration.id;
|
|||
|
var fingerprint = fingerprints[declarationId];
|
|||
|
|
|||
|
if (!fingerprint) {
|
|||
|
var vendorId = '';
|
|||
|
var iehack = '';
|
|||
|
var special = {};
|
|||
|
|
|||
|
declaration.value.sequence.each(function walk(node) {
|
|||
|
switch (node.type) {
|
|||
|
case 'Argument':
|
|||
|
case 'Value':
|
|||
|
case 'Braces':
|
|||
|
node.sequence.each(walk);
|
|||
|
break;
|
|||
|
|
|||
|
case 'Identifier':
|
|||
|
var name = node.name;
|
|||
|
|
|||
|
if (!vendorId) {
|
|||
|
vendorId = resolveKeyword(name).vendor;
|
|||
|
}
|
|||
|
|
|||
|
if (/\\[09]/.test(name)) {
|
|||
|
iehack = RegExp.lastMatch;
|
|||
|
}
|
|||
|
|
|||
|
if (realName === 'cursor') {
|
|||
|
if (CURSOR_SAFE_VALUE.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':
|
|||
|
var name = node.name;
|
|||
|
|
|||
|
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
|
|||
|
if (node.arguments.size < 4) {
|
|||
|
name = 'rect-backward';
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
special[name + '()'] = true;
|
|||
|
|
|||
|
// check nested tokens too
|
|||
|
node.arguments.each(walk);
|
|||
|
|
|||
|
break;
|
|||
|
|
|||
|
case 'Dimension':
|
|||
|
var unit = node.unit;
|
|||
|
|
|||
|
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 = '|' + Object.keys(special).sort() + '|' + iehack + vendorId;
|
|||
|
|
|||
|
fingerprints[declarationId] = fingerprint;
|
|||
|
}
|
|||
|
|
|||
|
return propertyName + fingerprint;
|
|||
|
}
|
|||
|
|
|||
|
function needless(props, declaration, fingerprints) {
|
|||
|
var property = resolveProperty(declaration.property.name);
|
|||
|
|
|||
|
if (NEEDLESS_TABLE.hasOwnProperty(property.name)) {
|
|||
|
var table = NEEDLESS_TABLE[property.name];
|
|||
|
|
|||
|
for (var i = 0; i < table.length; i++) {
|
|||
|
var ppre = getPropertyFingerprint(property.prefix + table[i], declaration, fingerprints);
|
|||
|
var prev = props[ppre];
|
|||
|
|
|||
|
if (prev && (!declaration.value.important || prev.item.data.value.important)) {
|
|||
|
return prev;
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
function processRuleset(ruleset, item, list, props, fingerprints) {
|
|||
|
var declarations = ruleset.block.declarations;
|
|||
|
|
|||
|
declarations.eachRight(function(declaration, declarationItem) {
|
|||
|
var property = declaration.property.name;
|
|||
|
var fingerprint = getPropertyFingerprint(property, declaration, fingerprints);
|
|||
|
var prev = props[fingerprint];
|
|||
|
|
|||
|
if (prev && !dontRestructure.hasOwnProperty(property)) {
|
|||
|
if (declaration.value.important && !prev.item.data.value.important) {
|
|||
|
props[fingerprint] = {
|
|||
|
block: declarations,
|
|||
|
item: declarationItem
|
|||
|
};
|
|||
|
|
|||
|
prev.block.remove(prev.item);
|
|||
|
declaration.info = {
|
|||
|
primary: declaration.info,
|
|||
|
merged: prev.item.data.info
|
|||
|
};
|
|||
|
} else {
|
|||
|
declarations.remove(declarationItem);
|
|||
|
prev.item.data.info = {
|
|||
|
primary: prev.item.data.info,
|
|||
|
merged: declaration.info
|
|||
|
};
|
|||
|
}
|
|||
|
} else {
|
|||
|
var prev = needless(props, declaration, fingerprints);
|
|||
|
|
|||
|
if (prev) {
|
|||
|
declarations.remove(declarationItem);
|
|||
|
prev.item.data.info = {
|
|||
|
primary: prev.item.data.info,
|
|||
|
merged: declaration.info
|
|||
|
};
|
|||
|
} else {
|
|||
|
declaration.fingerprint = fingerprint;
|
|||
|
|
|||
|
props[fingerprint] = {
|
|||
|
block: declarations,
|
|||
|
item: declarationItem
|
|||
|
};
|
|||
|
}
|
|||
|
}
|
|||
|
});
|
|||
|
|
|||
|
if (declarations.isEmpty()) {
|
|||
|
list.remove(item);
|
|||
|
}
|
|||
|
};
|
|||
|
|
|||
|
module.exports = function restructBlock(ast) {
|
|||
|
var stylesheetMap = {};
|
|||
|
var fingerprints = Object.create(null);
|
|||
|
|
|||
|
walkRulesRight(ast, function(node, item, list) {
|
|||
|
if (node.type !== 'Ruleset') {
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
var stylesheet = this.stylesheet;
|
|||
|
var rulesetId = (node.pseudoSignature || '') + '|' + node.selector.selectors.first().id;
|
|||
|
var rulesetMap;
|
|||
|
var props;
|
|||
|
|
|||
|
if (!stylesheetMap.hasOwnProperty(stylesheet.id)) {
|
|||
|
rulesetMap = {};
|
|||
|
stylesheetMap[stylesheet.id] = rulesetMap;
|
|||
|
} else {
|
|||
|
rulesetMap = stylesheetMap[stylesheet.id];
|
|||
|
}
|
|||
|
|
|||
|
if (rulesetMap.hasOwnProperty(rulesetId)) {
|
|||
|
props = rulesetMap[rulesetId];
|
|||
|
} else {
|
|||
|
props = {};
|
|||
|
rulesetMap[rulesetId] = props;
|
|||
|
}
|
|||
|
|
|||
|
processRuleset.call(this, node, item, list, props, fingerprints);
|
|||
|
});
|
|||
|
};
|