Fix code quality violations and exclude Manifest from checks
Document application modes (development/debug/production) Add global file drop handler, order column normalization, SPA hash fix Serve CDN assets via /_vendor/ URLs instead of merging into bundles Add production minification with license preservation Improve JSON formatting for debugging and production optimization Add CDN asset caching with CSS URL inlining for production builds Add three-mode system (development, debug, production) Update Manifest CLAUDE.md to reflect helper class architecture Refactor Manifest.php into helper classes for better organization Pre-manifest-refactor checkpoint: Add app_mode documentation 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
109
node_modules/csso/cjs/restructure/1-mergeAtrule.cjs
generated
vendored
Executable file
109
node_modules/csso/cjs/restructure/1-mergeAtrule.cjs
generated
vendored
Executable file
@@ -0,0 +1,109 @@
|
||||
'use strict';
|
||||
|
||||
const cssTree = require('css-tree');
|
||||
|
||||
const { hasOwnProperty } = Object.prototype;
|
||||
|
||||
function addRuleToMap(map, item, list, single) {
|
||||
const node = item.data;
|
||||
const name = cssTree.keyword(node.name).basename;
|
||||
const id = node.name.toLowerCase() + '/' + (node.prelude ? node.prelude.id : null);
|
||||
|
||||
if (!hasOwnProperty.call(map, name)) {
|
||||
map[name] = Object.create(null);
|
||||
}
|
||||
|
||||
if (single) {
|
||||
delete map[name][id];
|
||||
}
|
||||
|
||||
if (!hasOwnProperty.call(map[name], id)) {
|
||||
map[name][id] = new cssTree.List();
|
||||
}
|
||||
|
||||
map[name][id].append(list.remove(item));
|
||||
}
|
||||
|
||||
function relocateAtrules(ast, options) {
|
||||
const collected = Object.create(null);
|
||||
let topInjectPoint = null;
|
||||
|
||||
ast.children.forEach(function(node, item, list) {
|
||||
if (node.type === 'Atrule') {
|
||||
const name = cssTree.keyword(node.name).basename;
|
||||
|
||||
switch (name) {
|
||||
case 'keyframes':
|
||||
addRuleToMap(collected, item, list, true);
|
||||
return;
|
||||
|
||||
case 'media':
|
||||
if (options.forceMediaMerge) {
|
||||
addRuleToMap(collected, item, list, false);
|
||||
return;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (topInjectPoint === null &&
|
||||
name !== 'charset' &&
|
||||
name !== 'import') {
|
||||
topInjectPoint = item;
|
||||
}
|
||||
} else {
|
||||
if (topInjectPoint === null) {
|
||||
topInjectPoint = item;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
for (const atrule in collected) {
|
||||
for (const id in collected[atrule]) {
|
||||
ast.children.insertList(
|
||||
collected[atrule][id],
|
||||
atrule === 'media' ? null : topInjectPoint
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
function isMediaRule(node) {
|
||||
return node.type === 'Atrule' && node.name === 'media';
|
||||
}
|
||||
|
||||
function processAtrule(node, item, list) {
|
||||
if (!isMediaRule(node)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const prev = item.prev && item.prev.data;
|
||||
|
||||
if (!prev || !isMediaRule(prev)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// merge @media with same query
|
||||
if (node.prelude &&
|
||||
prev.prelude &&
|
||||
node.prelude.id === prev.prelude.id) {
|
||||
prev.block.children.appendList(node.block.children);
|
||||
list.remove(item);
|
||||
|
||||
// TODO: use it when we can refer to several points in source
|
||||
// prev.loc = {
|
||||
// primary: prev.loc,
|
||||
// merged: node.loc
|
||||
// };
|
||||
}
|
||||
}
|
||||
|
||||
function rejoinAtrule(ast, options) {
|
||||
relocateAtrules(ast, options);
|
||||
|
||||
cssTree.walk(ast, {
|
||||
visit: 'Atrule',
|
||||
reverse: true,
|
||||
enter: processAtrule
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = rejoinAtrule;
|
||||
51
node_modules/csso/cjs/restructure/2-initialMergeRuleset.cjs
generated
vendored
Executable file
51
node_modules/csso/cjs/restructure/2-initialMergeRuleset.cjs
generated
vendored
Executable file
@@ -0,0 +1,51 @@
|
||||
'use strict';
|
||||
|
||||
const cssTree = require('css-tree');
|
||||
const utils = require('./utils.cjs');
|
||||
|
||||
function processRule(node, item, list) {
|
||||
const selectors = node.prelude.children;
|
||||
const declarations = node.block.children;
|
||||
|
||||
list.prevUntil(item.prev, function(prev) {
|
||||
// skip non-ruleset node if safe
|
||||
if (prev.type !== 'Rule') {
|
||||
return utils.unsafeToSkipNode.call(selectors, prev);
|
||||
}
|
||||
|
||||
const prevSelectors = prev.prelude.children;
|
||||
const prevDeclarations = prev.block.children;
|
||||
|
||||
// try to join rulesets with equal pseudo signature
|
||||
if (node.pseudoSignature === prev.pseudoSignature) {
|
||||
// try to join by selectors
|
||||
if (utils.isEqualSelectors(prevSelectors, selectors)) {
|
||||
prevDeclarations.appendList(declarations);
|
||||
list.remove(item);
|
||||
return true;
|
||||
}
|
||||
|
||||
// try to join by declarations
|
||||
if (utils.isEqualDeclarations(declarations, prevDeclarations)) {
|
||||
utils.addSelectors(prevSelectors, selectors);
|
||||
list.remove(item);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// go to prev ruleset if has no selector similarities
|
||||
return utils.hasSimilarSelectors(selectors, prevSelectors);
|
||||
});
|
||||
}
|
||||
|
||||
// NOTE: direction should be left to right, since rulesets merge to left
|
||||
// ruleset. When direction right to left unmerged rulesets may prevent lookup
|
||||
// TODO: remove initial merge
|
||||
function initialMergeRule(ast) {
|
||||
cssTree.walk(ast, {
|
||||
visit: 'Rule',
|
||||
enter: processRule
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = initialMergeRule;
|
||||
46
node_modules/csso/cjs/restructure/3-disjoinRuleset.cjs
generated
vendored
Executable file
46
node_modules/csso/cjs/restructure/3-disjoinRuleset.cjs
generated
vendored
Executable file
@@ -0,0 +1,46 @@
|
||||
'use strict';
|
||||
|
||||
const cssTree = require('css-tree');
|
||||
|
||||
function processRule(node, item, list) {
|
||||
const selectors = node.prelude.children;
|
||||
|
||||
// generate new rule sets:
|
||||
// .a, .b { color: red; }
|
||||
// ->
|
||||
// .a { color: red; }
|
||||
// .b { color: red; }
|
||||
|
||||
// while there are more than 1 simple selector split for rulesets
|
||||
while (selectors.head !== selectors.tail) {
|
||||
const newSelectors = new cssTree.List();
|
||||
|
||||
newSelectors.insert(selectors.remove(selectors.head));
|
||||
|
||||
list.insert(list.createItem({
|
||||
type: 'Rule',
|
||||
loc: node.loc,
|
||||
prelude: {
|
||||
type: 'SelectorList',
|
||||
loc: node.prelude.loc,
|
||||
children: newSelectors
|
||||
},
|
||||
block: {
|
||||
type: 'Block',
|
||||
loc: node.block.loc,
|
||||
children: node.block.children.copy()
|
||||
},
|
||||
pseudoSignature: node.pseudoSignature
|
||||
}), item);
|
||||
}
|
||||
}
|
||||
|
||||
function disjoinRule(ast) {
|
||||
cssTree.walk(ast, {
|
||||
visit: 'Rule',
|
||||
reverse: true,
|
||||
enter: processRule
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = disjoinRule;
|
||||
429
node_modules/csso/cjs/restructure/4-restructShorthand.cjs
generated
vendored
Executable file
429
node_modules/csso/cjs/restructure/4-restructShorthand.cjs
generated
vendored
Executable file
@@ -0,0 +1,429 @@
|
||||
'use strict';
|
||||
|
||||
const cssTree = require('css-tree');
|
||||
|
||||
const REPLACE = 1;
|
||||
const REMOVE = 2;
|
||||
const TOP = 0;
|
||||
const RIGHT = 1;
|
||||
const BOTTOM = 2;
|
||||
const LEFT = 3;
|
||||
const SIDES = ['top', 'right', 'bottom', 'left'];
|
||||
const SIDE = {
|
||||
'margin-top': 'top',
|
||||
'margin-right': 'right',
|
||||
'margin-bottom': 'bottom',
|
||||
'margin-left': 'left',
|
||||
|
||||
'padding-top': 'top',
|
||||
'padding-right': 'right',
|
||||
'padding-bottom': 'bottom',
|
||||
'padding-left': 'left',
|
||||
|
||||
'border-top-color': 'top',
|
||||
'border-right-color': 'right',
|
||||
'border-bottom-color': 'bottom',
|
||||
'border-left-color': 'left',
|
||||
'border-top-width': 'top',
|
||||
'border-right-width': 'right',
|
||||
'border-bottom-width': 'bottom',
|
||||
'border-left-width': 'left',
|
||||
'border-top-style': 'top',
|
||||
'border-right-style': 'right',
|
||||
'border-bottom-style': 'bottom',
|
||||
'border-left-style': 'left'
|
||||
};
|
||||
const MAIN_PROPERTY = {
|
||||
'margin': 'margin',
|
||||
'margin-top': 'margin',
|
||||
'margin-right': 'margin',
|
||||
'margin-bottom': 'margin',
|
||||
'margin-left': 'margin',
|
||||
|
||||
'padding': 'padding',
|
||||
'padding-top': 'padding',
|
||||
'padding-right': 'padding',
|
||||
'padding-bottom': 'padding',
|
||||
'padding-left': 'padding',
|
||||
|
||||
'border-color': 'border-color',
|
||||
'border-top-color': 'border-color',
|
||||
'border-right-color': 'border-color',
|
||||
'border-bottom-color': 'border-color',
|
||||
'border-left-color': 'border-color',
|
||||
'border-width': 'border-width',
|
||||
'border-top-width': 'border-width',
|
||||
'border-right-width': 'border-width',
|
||||
'border-bottom-width': 'border-width',
|
||||
'border-left-width': 'border-width',
|
||||
'border-style': 'border-style',
|
||||
'border-top-style': 'border-style',
|
||||
'border-right-style': 'border-style',
|
||||
'border-bottom-style': 'border-style',
|
||||
'border-left-style': 'border-style'
|
||||
};
|
||||
|
||||
class TRBL {
|
||||
constructor(name) {
|
||||
this.name = name;
|
||||
this.loc = null;
|
||||
this.iehack = undefined;
|
||||
this.sides = {
|
||||
'top': null,
|
||||
'right': null,
|
||||
'bottom': null,
|
||||
'left': null
|
||||
};
|
||||
}
|
||||
|
||||
getValueSequence(declaration, count) {
|
||||
const values = [];
|
||||
let iehack = '';
|
||||
const hasBadValues = declaration.value.type !== 'Value' || declaration.value.children.some(function(child) {
|
||||
let special = false;
|
||||
|
||||
switch (child.type) {
|
||||
case 'Identifier':
|
||||
switch (child.name) {
|
||||
case '\\0':
|
||||
case '\\9':
|
||||
iehack = child.name;
|
||||
return;
|
||||
|
||||
case 'inherit':
|
||||
case 'initial':
|
||||
case 'unset':
|
||||
case 'revert':
|
||||
special = child.name;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'Dimension':
|
||||
switch (child.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 = child.unit;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'Hash': // color
|
||||
case 'Number':
|
||||
case 'Percentage':
|
||||
break;
|
||||
|
||||
case 'Function':
|
||||
if (child.name === 'var') {
|
||||
return true;
|
||||
}
|
||||
|
||||
special = child.name;
|
||||
break;
|
||||
|
||||
default:
|
||||
return true; // bad value
|
||||
}
|
||||
|
||||
values.push({
|
||||
node: child,
|
||||
special,
|
||||
important: declaration.important
|
||||
});
|
||||
});
|
||||
|
||||
if (hasBadValues || values.length > count) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (typeof this.iehack === 'string' && this.iehack !== iehack) {
|
||||
return false;
|
||||
}
|
||||
|
||||
this.iehack = iehack; // move outside
|
||||
|
||||
return values;
|
||||
}
|
||||
|
||||
canOverride(side, value) {
|
||||
const currentValue = this.sides[side];
|
||||
|
||||
return !currentValue || (value.important && !currentValue.important);
|
||||
}
|
||||
|
||||
add(name, declaration) {
|
||||
function attemptToAdd() {
|
||||
const sides = this.sides;
|
||||
const side = SIDE[name];
|
||||
|
||||
if (side) {
|
||||
if (side in sides === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const values = this.getValueSequence(declaration, 1);
|
||||
|
||||
if (!values || !values.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// can mix only if specials are equal
|
||||
for (const key in sides) {
|
||||
if (sides[key] !== null && sides[key].special !== values[0].special) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!this.canOverride(side, values[0])) {
|
||||
return true;
|
||||
}
|
||||
|
||||
sides[side] = values[0];
|
||||
|
||||
return true;
|
||||
} else if (name === this.name) {
|
||||
const values = this.getValueSequence(declaration, 4);
|
||||
|
||||
if (!values || !values.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
switch (values.length) {
|
||||
case 1:
|
||||
values[RIGHT] = values[TOP];
|
||||
values[BOTTOM] = values[TOP];
|
||||
values[LEFT] = values[TOP];
|
||||
break;
|
||||
|
||||
case 2:
|
||||
values[BOTTOM] = values[TOP];
|
||||
values[LEFT] = values[RIGHT];
|
||||
break;
|
||||
|
||||
case 3:
|
||||
values[LEFT] = values[RIGHT];
|
||||
break;
|
||||
}
|
||||
|
||||
// can mix only if specials are equal
|
||||
for (let i = 0; i < 4; i++) {
|
||||
for (const key in sides) {
|
||||
if (sides[key] !== null && sides[key].special !== values[i].special) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (let i = 0; i < 4; i++) {
|
||||
if (this.canOverride(SIDES[i], values[i])) {
|
||||
sides[SIDES[i]] = values[i];
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!attemptToAdd.call(this)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// TODO: use it when we can refer to several points in source
|
||||
// if (this.loc) {
|
||||
// this.loc = {
|
||||
// primary: this.loc,
|
||||
// merged: declaration.loc
|
||||
// };
|
||||
// } else {
|
||||
// this.loc = declaration.loc;
|
||||
// }
|
||||
if (!this.loc) {
|
||||
this.loc = declaration.loc;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
isOkToMinimize() {
|
||||
const top = this.sides.top;
|
||||
const right = this.sides.right;
|
||||
const bottom = this.sides.bottom;
|
||||
const left = this.sides.left;
|
||||
|
||||
if (top && right && bottom && left) {
|
||||
const important =
|
||||
top.important +
|
||||
right.important +
|
||||
bottom.important +
|
||||
left.important;
|
||||
|
||||
return important === 0 || important === 4;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
getValue() {
|
||||
const result = new cssTree.List();
|
||||
const sides = this.sides;
|
||||
const values = [
|
||||
sides.top,
|
||||
sides.right,
|
||||
sides.bottom,
|
||||
sides.left
|
||||
];
|
||||
const stringValues = [
|
||||
cssTree.generate(sides.top.node),
|
||||
cssTree.generate(sides.right.node),
|
||||
cssTree.generate(sides.bottom.node),
|
||||
cssTree.generate(sides.left.node)
|
||||
];
|
||||
|
||||
if (stringValues[LEFT] === stringValues[RIGHT]) {
|
||||
values.pop();
|
||||
if (stringValues[BOTTOM] === stringValues[TOP]) {
|
||||
values.pop();
|
||||
if (stringValues[RIGHT] === stringValues[TOP]) {
|
||||
values.pop();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (let i = 0; i < values.length; i++) {
|
||||
result.appendData(values[i].node);
|
||||
}
|
||||
|
||||
if (this.iehack) {
|
||||
result.appendData({
|
||||
type: 'Identifier',
|
||||
loc: null,
|
||||
name: this.iehack
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
type: 'Value',
|
||||
loc: null,
|
||||
children: result
|
||||
};
|
||||
}
|
||||
|
||||
getDeclaration() {
|
||||
return {
|
||||
type: 'Declaration',
|
||||
loc: this.loc,
|
||||
important: this.sides.top.important,
|
||||
property: this.name,
|
||||
value: this.getValue()
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
function processRule(rule, shorts, shortDeclarations, lastShortSelector) {
|
||||
const declarations = rule.block.children;
|
||||
const selector = rule.prelude.children.first.id;
|
||||
|
||||
rule.block.children.forEachRight(function(declaration, item) {
|
||||
const property = declaration.property;
|
||||
|
||||
if (!MAIN_PROPERTY.hasOwnProperty(property)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const key = MAIN_PROPERTY[property];
|
||||
let shorthand;
|
||||
let operation;
|
||||
|
||||
if (!lastShortSelector || selector === lastShortSelector) {
|
||||
if (key in shorts) {
|
||||
operation = REMOVE;
|
||||
shorthand = shorts[key];
|
||||
}
|
||||
}
|
||||
|
||||
if (!shorthand || !shorthand.add(property, declaration)) {
|
||||
operation = REPLACE;
|
||||
shorthand = new TRBL(key);
|
||||
|
||||
// if can't parse value ignore it and break shorthand children
|
||||
if (!shorthand.add(property, declaration)) {
|
||||
lastShortSelector = null;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
shorts[key] = shorthand;
|
||||
shortDeclarations.push({
|
||||
operation,
|
||||
block: declarations,
|
||||
item,
|
||||
shorthand
|
||||
});
|
||||
|
||||
lastShortSelector = selector;
|
||||
});
|
||||
|
||||
return lastShortSelector;
|
||||
}
|
||||
|
||||
function processShorthands(shortDeclarations, markDeclaration) {
|
||||
shortDeclarations.forEach(function(item) {
|
||||
const shorthand = item.shorthand;
|
||||
|
||||
if (!shorthand.isOkToMinimize()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (item.operation === REPLACE) {
|
||||
item.item.data = markDeclaration(shorthand.getDeclaration());
|
||||
} else {
|
||||
item.block.remove(item.item);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function restructBlock(ast, indexer) {
|
||||
const stylesheetMap = {};
|
||||
const shortDeclarations = [];
|
||||
|
||||
cssTree.walk(ast, {
|
||||
visit: 'Rule',
|
||||
reverse: true,
|
||||
enter(node) {
|
||||
const stylesheet = this.block || this.stylesheet;
|
||||
const ruleId = (node.pseudoSignature || '') + '|' + node.prelude.children.first.id;
|
||||
let ruleMap;
|
||||
let shorts;
|
||||
|
||||
if (!stylesheetMap.hasOwnProperty(stylesheet.id)) {
|
||||
ruleMap = {
|
||||
lastShortSelector: null
|
||||
};
|
||||
stylesheetMap[stylesheet.id] = ruleMap;
|
||||
} else {
|
||||
ruleMap = stylesheetMap[stylesheet.id];
|
||||
}
|
||||
|
||||
if (ruleMap.hasOwnProperty(ruleId)) {
|
||||
shorts = ruleMap[ruleId];
|
||||
} else {
|
||||
shorts = {};
|
||||
ruleMap[ruleId] = shorts;
|
||||
}
|
||||
|
||||
ruleMap.lastShortSelector = processRule.call(this, node, shorts, shortDeclarations, ruleMap.lastShortSelector);
|
||||
}
|
||||
});
|
||||
|
||||
processShorthands(shortDeclarations, indexer.declaration);
|
||||
}
|
||||
|
||||
module.exports = restructBlock;
|
||||
307
node_modules/csso/cjs/restructure/6-restructBlock.cjs
generated
vendored
Executable file
307
node_modules/csso/cjs/restructure/6-restructBlock.cjs
generated
vendored
Executable file
@@ -0,0 +1,307 @@
|
||||
'use strict';
|
||||
|
||||
const cssTree = require('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 = cssTree.property(propertyName).basename;
|
||||
|
||||
if (realName === 'background') {
|
||||
return propertyName + ':' + cssTree.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 = cssTree.keyword(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 = cssTree.keyword(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 = cssTree.generate(declaration.value);
|
||||
}
|
||||
|
||||
fingerprints[declarationId] = fingerprint;
|
||||
}
|
||||
|
||||
return propertyName + fingerprint;
|
||||
}
|
||||
|
||||
function needless(props, declaration, fingerprints) {
|
||||
const property = cssTree.property(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);
|
||||
}
|
||||
}
|
||||
|
||||
function restructBlock(ast) {
|
||||
const stylesheetMap = {};
|
||||
const fingerprints = Object.create(null);
|
||||
|
||||
cssTree.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);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = restructBlock;
|
||||
90
node_modules/csso/cjs/restructure/7-mergeRuleset.cjs
generated
vendored
Executable file
90
node_modules/csso/cjs/restructure/7-mergeRuleset.cjs
generated
vendored
Executable file
@@ -0,0 +1,90 @@
|
||||
'use strict';
|
||||
|
||||
const cssTree = require('css-tree');
|
||||
const utils = require('./utils.cjs');
|
||||
|
||||
/*
|
||||
At this step all rules has single simple selector. We try to join by equal
|
||||
declaration blocks to first rule, e.g.
|
||||
|
||||
.a { color: red }
|
||||
b { ... }
|
||||
.b { color: red }
|
||||
->
|
||||
.a, .b { color: red }
|
||||
b { ... }
|
||||
*/
|
||||
|
||||
function processRule(node, item, list) {
|
||||
const selectors = node.prelude.children;
|
||||
const declarations = node.block.children;
|
||||
const nodeCompareMarker = selectors.first.compareMarker;
|
||||
const skippedCompareMarkers = {};
|
||||
|
||||
list.nextUntil(item.next, function(next, nextItem) {
|
||||
// skip non-ruleset node if safe
|
||||
if (next.type !== 'Rule') {
|
||||
return utils.unsafeToSkipNode.call(selectors, next);
|
||||
}
|
||||
|
||||
if (node.pseudoSignature !== next.pseudoSignature) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const nextFirstSelector = next.prelude.children.head;
|
||||
const nextDeclarations = next.block.children;
|
||||
const nextCompareMarker = nextFirstSelector.data.compareMarker;
|
||||
|
||||
// if next ruleset has same marked as one of skipped then stop joining
|
||||
if (nextCompareMarker in skippedCompareMarkers) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// try to join by selectors
|
||||
if (selectors.head === selectors.tail) {
|
||||
if (selectors.first.id === nextFirstSelector.data.id) {
|
||||
declarations.appendList(nextDeclarations);
|
||||
list.remove(nextItem);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// try to join by properties
|
||||
if (utils.isEqualDeclarations(declarations, nextDeclarations)) {
|
||||
const nextStr = nextFirstSelector.data.id;
|
||||
|
||||
selectors.some((data, item) => {
|
||||
const curStr = data.id;
|
||||
|
||||
if (nextStr < curStr) {
|
||||
selectors.insert(nextFirstSelector, item);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!item.next) {
|
||||
selectors.insert(nextFirstSelector);
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
list.remove(nextItem);
|
||||
return;
|
||||
}
|
||||
|
||||
// go to next ruleset if current one can be skipped (has no equal specificity nor element selector)
|
||||
if (nextCompareMarker === nodeCompareMarker) {
|
||||
return true;
|
||||
}
|
||||
|
||||
skippedCompareMarkers[nextCompareMarker] = true;
|
||||
});
|
||||
}
|
||||
|
||||
function mergeRule(ast) {
|
||||
cssTree.walk(ast, {
|
||||
visit: 'Rule',
|
||||
enter: processRule
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = mergeRule;
|
||||
175
node_modules/csso/cjs/restructure/8-restructRuleset.cjs
generated
vendored
Executable file
175
node_modules/csso/cjs/restructure/8-restructRuleset.cjs
generated
vendored
Executable file
@@ -0,0 +1,175 @@
|
||||
'use strict';
|
||||
|
||||
const cssTree = require('css-tree');
|
||||
const utils = require('./utils.cjs');
|
||||
|
||||
function calcSelectorLength(list) {
|
||||
return list.reduce((res, data) => res + data.id.length + 1, 0) - 1;
|
||||
}
|
||||
|
||||
function calcDeclarationsLength(tokens) {
|
||||
let length = 0;
|
||||
|
||||
for (const token of tokens) {
|
||||
length += token.length;
|
||||
}
|
||||
|
||||
return (
|
||||
length + // declarations
|
||||
tokens.length - 1 // delimeters
|
||||
);
|
||||
}
|
||||
|
||||
function processRule(node, item, list) {
|
||||
const avoidRulesMerge = this.block !== null ? this.block.avoidRulesMerge : false;
|
||||
const selectors = node.prelude.children;
|
||||
const block = node.block;
|
||||
const disallowDownMarkers = Object.create(null);
|
||||
let allowMergeUp = true;
|
||||
let allowMergeDown = true;
|
||||
|
||||
list.prevUntil(item.prev, function(prev, prevItem) {
|
||||
const prevBlock = prev.block;
|
||||
const prevType = prev.type;
|
||||
|
||||
if (prevType !== 'Rule') {
|
||||
const unsafe = utils.unsafeToSkipNode.call(selectors, prev);
|
||||
|
||||
if (!unsafe && prevType === 'Atrule' && prevBlock) {
|
||||
cssTree.walk(prevBlock, {
|
||||
visit: 'Rule',
|
||||
enter(node) {
|
||||
node.prelude.children.forEach((data) => {
|
||||
disallowDownMarkers[data.compareMarker] = true;
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return unsafe;
|
||||
}
|
||||
|
||||
if (node.pseudoSignature !== prev.pseudoSignature) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const prevSelectors = prev.prelude.children;
|
||||
|
||||
allowMergeDown = !prevSelectors.some((selector) =>
|
||||
selector.compareMarker in disallowDownMarkers
|
||||
);
|
||||
|
||||
// try prev ruleset if simpleselectors has no equal specifity and element selector
|
||||
if (!allowMergeDown && !allowMergeUp) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// try to join by selectors
|
||||
if (allowMergeUp && utils.isEqualSelectors(prevSelectors, selectors)) {
|
||||
prevBlock.children.appendList(block.children);
|
||||
list.remove(item);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// try to join by properties
|
||||
const diff = utils.compareDeclarations(block.children, prevBlock.children);
|
||||
|
||||
// console.log(diff.eq, diff.ne1, diff.ne2);
|
||||
|
||||
if (diff.eq.length) {
|
||||
if (!diff.ne1.length && !diff.ne2.length) {
|
||||
// equal blocks
|
||||
if (allowMergeDown) {
|
||||
utils.addSelectors(selectors, prevSelectors);
|
||||
list.remove(prevItem);
|
||||
}
|
||||
|
||||
return true;
|
||||
} else if (!avoidRulesMerge) { /* probably we don't need to prevent those merges for @keyframes
|
||||
TODO: need to be checked */
|
||||
|
||||
if (diff.ne1.length && !diff.ne2.length) {
|
||||
// prevBlock is subset block
|
||||
const selectorLength = calcSelectorLength(selectors);
|
||||
const blockLength = calcDeclarationsLength(diff.eq); // declarations length
|
||||
|
||||
if (allowMergeUp && selectorLength < blockLength) {
|
||||
utils.addSelectors(prevSelectors, selectors);
|
||||
block.children.fromArray(diff.ne1);
|
||||
}
|
||||
} else if (!diff.ne1.length && diff.ne2.length) {
|
||||
// node is subset of prevBlock
|
||||
const selectorLength = calcSelectorLength(prevSelectors);
|
||||
const blockLength = calcDeclarationsLength(diff.eq); // declarations length
|
||||
|
||||
if (allowMergeDown && selectorLength < blockLength) {
|
||||
utils.addSelectors(selectors, prevSelectors);
|
||||
prevBlock.children.fromArray(diff.ne2);
|
||||
}
|
||||
} else {
|
||||
// diff.ne1.length && diff.ne2.length
|
||||
// extract equal block
|
||||
const newSelector = {
|
||||
type: 'SelectorList',
|
||||
loc: null,
|
||||
children: utils.addSelectors(prevSelectors.copy(), selectors)
|
||||
};
|
||||
const newBlockLength = calcSelectorLength(newSelector.children) + 2; // selectors length + curly braces length
|
||||
const blockLength = calcDeclarationsLength(diff.eq); // declarations length
|
||||
|
||||
// create new ruleset if declarations length greater than
|
||||
// ruleset description overhead
|
||||
if (blockLength >= newBlockLength) {
|
||||
const newItem = list.createItem({
|
||||
type: 'Rule',
|
||||
loc: null,
|
||||
prelude: newSelector,
|
||||
block: {
|
||||
type: 'Block',
|
||||
loc: null,
|
||||
children: new cssTree.List().fromArray(diff.eq)
|
||||
},
|
||||
pseudoSignature: node.pseudoSignature
|
||||
});
|
||||
|
||||
block.children.fromArray(diff.ne1);
|
||||
prevBlock.children.fromArray(diff.ne2overrided);
|
||||
|
||||
if (allowMergeUp) {
|
||||
list.insert(newItem, prevItem);
|
||||
} else {
|
||||
list.insert(newItem, item);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (allowMergeUp) {
|
||||
// TODO: disallow up merge only if any property interception only (i.e. diff.ne2overrided.length > 0);
|
||||
// await property families to find property interception correctly
|
||||
allowMergeUp = !prevSelectors.some((prevSelector) =>
|
||||
selectors.some((selector) =>
|
||||
selector.compareMarker === prevSelector.compareMarker
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
prevSelectors.forEach((data) => {
|
||||
disallowDownMarkers[data.compareMarker] = true;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function restructRule(ast) {
|
||||
cssTree.walk(ast, {
|
||||
visit: 'Rule',
|
||||
reverse: true,
|
||||
enter: processRule
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = restructRule;
|
||||
39
node_modules/csso/cjs/restructure/index.cjs
generated
vendored
Executable file
39
node_modules/csso/cjs/restructure/index.cjs
generated
vendored
Executable file
@@ -0,0 +1,39 @@
|
||||
'use strict';
|
||||
|
||||
const index = require('./prepare/index.cjs');
|
||||
const _1MergeAtrule = require('./1-mergeAtrule.cjs');
|
||||
const _2InitialMergeRuleset = require('./2-initialMergeRuleset.cjs');
|
||||
const _3DisjoinRuleset = require('./3-disjoinRuleset.cjs');
|
||||
const _4RestructShorthand = require('./4-restructShorthand.cjs');
|
||||
const _6RestructBlock = require('./6-restructBlock.cjs');
|
||||
const _7MergeRuleset = require('./7-mergeRuleset.cjs');
|
||||
const _8RestructRuleset = require('./8-restructRuleset.cjs');
|
||||
|
||||
function restructure(ast, options) {
|
||||
// prepare ast for restructing
|
||||
const indexer = index(ast, options);
|
||||
options.logger('prepare', ast);
|
||||
|
||||
_1MergeAtrule(ast, options);
|
||||
options.logger('mergeAtrule', ast);
|
||||
|
||||
_2InitialMergeRuleset(ast);
|
||||
options.logger('initialMergeRuleset', ast);
|
||||
|
||||
_3DisjoinRuleset(ast);
|
||||
options.logger('disjoinRuleset', ast);
|
||||
|
||||
_4RestructShorthand(ast, indexer);
|
||||
options.logger('restructShorthand', ast);
|
||||
|
||||
_6RestructBlock(ast);
|
||||
options.logger('restructBlock', ast);
|
||||
|
||||
_7MergeRuleset(ast);
|
||||
options.logger('mergeRuleset', ast);
|
||||
|
||||
_8RestructRuleset(ast);
|
||||
options.logger('restructRuleset', ast);
|
||||
}
|
||||
|
||||
module.exports = restructure;
|
||||
34
node_modules/csso/cjs/restructure/prepare/createDeclarationIndexer.cjs
generated
vendored
Normal file
34
node_modules/csso/cjs/restructure/prepare/createDeclarationIndexer.cjs
generated
vendored
Normal file
@@ -0,0 +1,34 @@
|
||||
'use strict';
|
||||
|
||||
const cssTree = require('css-tree');
|
||||
|
||||
class Index {
|
||||
constructor() {
|
||||
this.map = new Map();
|
||||
}
|
||||
resolve(str) {
|
||||
let index = this.map.get(str);
|
||||
|
||||
if (index === undefined) {
|
||||
index = this.map.size + 1;
|
||||
this.map.set(str, index);
|
||||
}
|
||||
|
||||
return index;
|
||||
}
|
||||
}
|
||||
function createDeclarationIndexer() {
|
||||
const ids = new Index();
|
||||
|
||||
return function markDeclaration(node) {
|
||||
const id = cssTree.generate(node);
|
||||
|
||||
node.id = ids.resolve(id);
|
||||
node.length = id.length;
|
||||
node.fingerprint = null;
|
||||
|
||||
return node;
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = createDeclarationIndexer;
|
||||
45
node_modules/csso/cjs/restructure/prepare/index.cjs
generated
vendored
Normal file
45
node_modules/csso/cjs/restructure/prepare/index.cjs
generated
vendored
Normal file
@@ -0,0 +1,45 @@
|
||||
'use strict';
|
||||
|
||||
const cssTree = require('css-tree');
|
||||
const createDeclarationIndexer = require('./createDeclarationIndexer.cjs');
|
||||
const processSelector = require('./processSelector.cjs');
|
||||
|
||||
function prepare(ast, options) {
|
||||
const markDeclaration = createDeclarationIndexer();
|
||||
|
||||
cssTree.walk(ast, {
|
||||
visit: 'Rule',
|
||||
enter(node) {
|
||||
node.block.children.forEach(markDeclaration);
|
||||
processSelector(node, options.usage);
|
||||
}
|
||||
});
|
||||
|
||||
cssTree.walk(ast, {
|
||||
visit: 'Atrule',
|
||||
enter(node) {
|
||||
if (node.prelude) {
|
||||
node.prelude.id = null; // pre-init property to avoid multiple hidden class for generate
|
||||
node.prelude.id = cssTree.generate(node.prelude);
|
||||
}
|
||||
|
||||
// compare keyframe selectors by its values
|
||||
// NOTE: still no clarification about problems with keyframes selector grouping (issue #197)
|
||||
if (cssTree.keyword(node.name).basename === 'keyframes') {
|
||||
node.block.avoidRulesMerge = true; /* probably we don't need to prevent those merges for @keyframes
|
||||
TODO: need to be checked */
|
||||
node.block.children.forEach(function(rule) {
|
||||
rule.prelude.children.forEach(function(simpleselector) {
|
||||
simpleselector.compareMarker = simpleselector.id;
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
declaration: markDeclaration
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = prepare;
|
||||
101
node_modules/csso/cjs/restructure/prepare/processSelector.cjs
generated
vendored
Normal file
101
node_modules/csso/cjs/restructure/prepare/processSelector.cjs
generated
vendored
Normal file
@@ -0,0 +1,101 @@
|
||||
'use strict';
|
||||
|
||||
const cssTree = require('css-tree');
|
||||
const specificity = require('./specificity.cjs');
|
||||
|
||||
const nonFreezePseudoElements = new Set([
|
||||
'first-letter',
|
||||
'first-line',
|
||||
'after',
|
||||
'before'
|
||||
]);
|
||||
const nonFreezePseudoClasses = new Set([
|
||||
'link',
|
||||
'visited',
|
||||
'hover',
|
||||
'active',
|
||||
'first-letter',
|
||||
'first-line',
|
||||
'after',
|
||||
'before'
|
||||
]);
|
||||
|
||||
function processSelector(node, usageData) {
|
||||
const pseudos = new Set();
|
||||
|
||||
node.prelude.children.forEach(function(simpleSelector) {
|
||||
let tagName = '*';
|
||||
let scope = 0;
|
||||
|
||||
simpleSelector.children.forEach(function(node) {
|
||||
switch (node.type) {
|
||||
case 'ClassSelector':
|
||||
if (usageData && usageData.scopes) {
|
||||
const classScope = usageData.scopes[node.name] || 0;
|
||||
|
||||
if (scope !== 0 && classScope !== scope) {
|
||||
throw new Error('Selector can\'t has classes from different scopes: ' + cssTree.generate(simpleSelector));
|
||||
}
|
||||
|
||||
scope = classScope;
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case 'PseudoClassSelector': {
|
||||
const name = node.name.toLowerCase();
|
||||
|
||||
if (!nonFreezePseudoClasses.has(name)) {
|
||||
pseudos.add(`:${name}`);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case 'PseudoElementSelector': {
|
||||
const name = node.name.toLowerCase();
|
||||
|
||||
if (!nonFreezePseudoElements.has(name)) {
|
||||
pseudos.add(`::${name}`);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case 'TypeSelector':
|
||||
tagName = node.name.toLowerCase();
|
||||
break;
|
||||
|
||||
case 'AttributeSelector':
|
||||
if (node.flags) {
|
||||
pseudos.add(`[${node.flags.toLowerCase()}]`);
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case 'Combinator':
|
||||
tagName = '*';
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
simpleSelector.compareMarker = specificity(simpleSelector).toString();
|
||||
simpleSelector.id = null; // pre-init property to avoid multiple hidden class
|
||||
simpleSelector.id = cssTree.generate(simpleSelector);
|
||||
|
||||
if (scope) {
|
||||
simpleSelector.compareMarker += ':' + scope;
|
||||
}
|
||||
|
||||
if (tagName !== '*') {
|
||||
simpleSelector.compareMarker += ',' + tagName;
|
||||
}
|
||||
});
|
||||
|
||||
// add property to all rule nodes to avoid multiple hidden class
|
||||
node.pseudoSignature = pseudos.size > 0
|
||||
? [...pseudos].sort().join(',')
|
||||
: false;
|
||||
}
|
||||
|
||||
module.exports = processSelector;
|
||||
133
node_modules/csso/cjs/restructure/prepare/specificity.cjs
generated
vendored
Normal file
133
node_modules/csso/cjs/restructure/prepare/specificity.cjs
generated
vendored
Normal file
@@ -0,0 +1,133 @@
|
||||
'use strict';
|
||||
|
||||
const cssTree = require('css-tree');
|
||||
|
||||
function ensureSelectorList(node) {
|
||||
if (node.type === 'Raw') {
|
||||
return cssTree.parse(node.value, { context: 'selectorList' });
|
||||
}
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
function maxSpecificity(a, b) {
|
||||
for (let i = 0; i < 3; i++) {
|
||||
if (a[i] !== b[i]) {
|
||||
return a[i] > b[i] ? a : b;
|
||||
}
|
||||
}
|
||||
|
||||
return a;
|
||||
}
|
||||
|
||||
function maxSelectorListSpecificity(selectorList) {
|
||||
return ensureSelectorList(selectorList).children.reduce(
|
||||
(result, node) => maxSpecificity(specificity(node), result),
|
||||
[0, 0, 0]
|
||||
);
|
||||
}
|
||||
|
||||
// §16. Calculating a selector’s specificity
|
||||
// https://www.w3.org/TR/selectors-4/#specificity-rules
|
||||
function specificity(simpleSelector) {
|
||||
let A = 0;
|
||||
let B = 0;
|
||||
let C = 0;
|
||||
|
||||
// A selector’s specificity is calculated for a given element as follows:
|
||||
simpleSelector.children.forEach((node) => {
|
||||
switch (node.type) {
|
||||
// count the number of ID selectors in the selector (= A)
|
||||
case 'IdSelector':
|
||||
A++;
|
||||
break;
|
||||
|
||||
// count the number of class selectors, attributes selectors, ...
|
||||
case 'ClassSelector':
|
||||
case 'AttributeSelector':
|
||||
B++;
|
||||
break;
|
||||
|
||||
// ... and pseudo-classes in the selector (= B)
|
||||
case 'PseudoClassSelector':
|
||||
switch (node.name.toLowerCase()) {
|
||||
// The specificity of an :is(), :not(), or :has() pseudo-class is replaced
|
||||
// by the specificity of the most specific complex selector in its selector list argument.
|
||||
case 'not':
|
||||
case 'has':
|
||||
case 'is':
|
||||
// :matches() is used before it was renamed to :is()
|
||||
// https://github.com/w3c/csswg-drafts/issues/3258
|
||||
case 'matches':
|
||||
// Older browsers support :is() functionality as prefixed pseudo-class :any()
|
||||
// https://developer.mozilla.org/en-US/docs/Web/CSS/:is
|
||||
case '-webkit-any':
|
||||
case '-moz-any': {
|
||||
const [a, b, c] = maxSelectorListSpecificity(node.children.first);
|
||||
|
||||
A += a;
|
||||
B += b;
|
||||
C += c;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
// Analogously, the specificity of an :nth-child() or :nth-last-child() selector
|
||||
// is the specificity of the pseudo class itself (counting as one pseudo-class selector)
|
||||
// plus the specificity of the most specific complex selector in its selector list argument (if any).
|
||||
case 'nth-child':
|
||||
case 'nth-last-child': {
|
||||
const arg = node.children.first;
|
||||
|
||||
if (arg.type === 'Nth' && arg.selector) {
|
||||
const [a, b, c] = maxSelectorListSpecificity(arg.selector);
|
||||
|
||||
A += a;
|
||||
B += b + 1;
|
||||
C += c;
|
||||
} else {
|
||||
B++;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
// The specificity of a :where() pseudo-class is replaced by zero.
|
||||
case 'where':
|
||||
break;
|
||||
|
||||
// The four Level 2 pseudo-elements (::before, ::after, ::first-line, and ::first-letter) may,
|
||||
// for legacy reasons, be represented using the <pseudo-class-selector> grammar,
|
||||
// with only a single ":" character at their start.
|
||||
// https://www.w3.org/TR/selectors-4/#single-colon-pseudos
|
||||
case 'before':
|
||||
case 'after':
|
||||
case 'first-line':
|
||||
case 'first-letter':
|
||||
C++;
|
||||
break;
|
||||
|
||||
default:
|
||||
B++;
|
||||
}
|
||||
break;
|
||||
|
||||
// count the number of type selectors ...
|
||||
case 'TypeSelector':
|
||||
// ignore the universal selector
|
||||
if (!node.name.endsWith('*')) {
|
||||
C++;
|
||||
}
|
||||
break;
|
||||
|
||||
// ... and pseudo-elements in the selector (= C)
|
||||
case 'PseudoElementSelector':
|
||||
C++;
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
return [A, B, C];
|
||||
}
|
||||
|
||||
module.exports = specificity;
|
||||
151
node_modules/csso/cjs/restructure/utils.cjs
generated
vendored
Executable file
151
node_modules/csso/cjs/restructure/utils.cjs
generated
vendored
Executable file
@@ -0,0 +1,151 @@
|
||||
'use strict';
|
||||
|
||||
const { hasOwnProperty } = Object.prototype;
|
||||
|
||||
function isEqualSelectors(a, b) {
|
||||
let cursor1 = a.head;
|
||||
let cursor2 = b.head;
|
||||
|
||||
while (cursor1 !== null && cursor2 !== null && cursor1.data.id === cursor2.data.id) {
|
||||
cursor1 = cursor1.next;
|
||||
cursor2 = cursor2.next;
|
||||
}
|
||||
|
||||
return cursor1 === null && cursor2 === null;
|
||||
}
|
||||
|
||||
function isEqualDeclarations(a, b) {
|
||||
let cursor1 = a.head;
|
||||
let cursor2 = b.head;
|
||||
|
||||
while (cursor1 !== null && cursor2 !== null && cursor1.data.id === cursor2.data.id) {
|
||||
cursor1 = cursor1.next;
|
||||
cursor2 = cursor2.next;
|
||||
}
|
||||
|
||||
return cursor1 === null && cursor2 === null;
|
||||
}
|
||||
|
||||
function compareDeclarations(declarations1, declarations2) {
|
||||
const result = {
|
||||
eq: [],
|
||||
ne1: [],
|
||||
ne2: [],
|
||||
ne2overrided: []
|
||||
};
|
||||
|
||||
const fingerprints = Object.create(null);
|
||||
const declarations2hash = Object.create(null);
|
||||
|
||||
for (let cursor = declarations2.head; cursor; cursor = cursor.next) {
|
||||
declarations2hash[cursor.data.id] = true;
|
||||
}
|
||||
|
||||
for (let cursor = declarations1.head; cursor; cursor = cursor.next) {
|
||||
const data = cursor.data;
|
||||
|
||||
if (data.fingerprint) {
|
||||
fingerprints[data.fingerprint] = data.important;
|
||||
}
|
||||
|
||||
if (declarations2hash[data.id]) {
|
||||
declarations2hash[data.id] = false;
|
||||
result.eq.push(data);
|
||||
} else {
|
||||
result.ne1.push(data);
|
||||
}
|
||||
}
|
||||
|
||||
for (let cursor = declarations2.head; cursor; cursor = cursor.next) {
|
||||
const data = cursor.data;
|
||||
|
||||
if (declarations2hash[data.id]) {
|
||||
// when declarations1 has an overriding declaration, this is not a difference
|
||||
// unless no !important is used on prev and !important is used on the following
|
||||
if (!hasOwnProperty.call(fingerprints, data.fingerprint) ||
|
||||
(!fingerprints[data.fingerprint] && data.important)) {
|
||||
result.ne2.push(data);
|
||||
}
|
||||
|
||||
result.ne2overrided.push(data);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
function addSelectors(dest, source) {
|
||||
source.forEach((sourceData) => {
|
||||
const newStr = sourceData.id;
|
||||
let cursor = dest.head;
|
||||
|
||||
while (cursor) {
|
||||
const nextStr = cursor.data.id;
|
||||
|
||||
if (nextStr === newStr) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (nextStr > newStr) {
|
||||
break;
|
||||
}
|
||||
|
||||
cursor = cursor.next;
|
||||
}
|
||||
|
||||
dest.insert(dest.createItem(sourceData), cursor);
|
||||
});
|
||||
|
||||
return dest;
|
||||
}
|
||||
|
||||
// check if simpleselectors has no equal specificity and element selector
|
||||
function hasSimilarSelectors(selectors1, selectors2) {
|
||||
let cursor1 = selectors1.head;
|
||||
|
||||
while (cursor1 !== null) {
|
||||
let cursor2 = selectors2.head;
|
||||
|
||||
while (cursor2 !== null) {
|
||||
if (cursor1.data.compareMarker === cursor2.data.compareMarker) {
|
||||
return true;
|
||||
}
|
||||
|
||||
cursor2 = cursor2.next;
|
||||
}
|
||||
|
||||
cursor1 = cursor1.next;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// test node can't to be skipped
|
||||
function unsafeToSkipNode(node) {
|
||||
switch (node.type) {
|
||||
case 'Rule':
|
||||
// unsafe skip ruleset with selector similarities
|
||||
return hasSimilarSelectors(node.prelude.children, this);
|
||||
|
||||
case 'Atrule':
|
||||
// can skip at-rules with blocks
|
||||
if (node.block) {
|
||||
// unsafe skip at-rule if block contains something unsafe to skip
|
||||
return node.block.children.some(unsafeToSkipNode, this);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'Declaration':
|
||||
return false;
|
||||
}
|
||||
|
||||
// unsafe by default
|
||||
return true;
|
||||
}
|
||||
|
||||
exports.addSelectors = addSelectors;
|
||||
exports.compareDeclarations = compareDeclarations;
|
||||
exports.hasSimilarSelectors = hasSimilarSelectors;
|
||||
exports.isEqualDeclarations = isEqualDeclarations;
|
||||
exports.isEqualSelectors = isEqualSelectors;
|
||||
exports.unsafeToSkipNode = unsafeToSkipNode;
|
||||
Reference in New Issue
Block a user