refactor: tidy up compile/css

pull/8000/head
Simon He 4 years ago
parent b05b684b95
commit dd248bd15a

@ -42,11 +42,7 @@ export default class Selector {
this.blocks = group_selectors(node); this.blocks = group_selectors(node);
// take trailing :global(...) selectors out of consideration // take trailing :global(...) selectors out of consideration
let i = this.blocks.length; const i = this.blocks.findIndex(block => !block.global);
while (i > 0) {
if (!this.blocks[i - 1].global) break;
i -= 1;
}
this.local_blocks = this.blocks.slice(0, i); this.local_blocks = this.blocks.slice(0, i);
@ -74,10 +70,8 @@ export default class Selector {
minify(code: MagicString) { minify(code: MagicString) {
let c: number = null; let c: number = null;
this.blocks.forEach((block, i) => { this.blocks.forEach((block, i) => {
if (i > 0) { if (i > 0 && block.start - c > 1) {
if (block.start - c > 1) { code.overwrite(c, block.start, block.combinator.name || ' ');
code.overwrite(c, block.start, block.combinator.name || ' ');
}
} }
c = block.end; c = block.end;
@ -105,8 +99,8 @@ export default class Selector {
while (i--) { while (i--) {
const selector = block.selectors[i]; const selector = block.selectors[i];
if (selector.type === 'PseudoElementSelector' || selector.type === 'PseudoClassSelector') { if (selector.type === 'PseudoElementSelector' || selector.type === 'PseudoClassSelector') {
if (selector.name !== 'root' && selector.name !== 'host') { if (selector.name !== 'root' && selector.name !== 'host' && i === 0) {
if (i === 0) code.prependRight(selector.start, attr); code.prependRight(selector.start, attr);
} }
continue; continue;
} }
@ -159,10 +153,8 @@ export default class Selector {
for (const block of this.blocks) { for (const block of this.blocks) {
for (const selector of block.selectors) { for (const selector of block.selectors) {
if (selector.type === 'PseudoClassSelector' && selector.name === 'global') { if (selector.type === 'PseudoClassSelector' && selector.name === 'global' && regex_is_single_css_selector.test(selector.children[0].value)) {
if (regex_is_single_css_selector.test(selector.children[0].value)) { component.error(selector, compiler_errors.css_invalid_global_selector);
component.error(selector, compiler_errors.css_invalid_global_selector);
}
} }
} }
} }
@ -183,7 +175,7 @@ export default class Selector {
let count = 0; let count = 0;
for (const block of this.blocks) { for (const block of this.blocks) {
if (block.should_encapsulate) { if (block.should_encapsulate) {
count ++; count++;
} }
} }
return count; return count;
@ -206,7 +198,7 @@ function apply_selector(blocks: Block[], node: Element, to_encapsulate: Array<{
return false; return false;
case BlockAppliesToNode.UnknownSelectorType: case BlockAppliesToNode.UnknownSelectorType:
// bail. TODO figure out what these could be // bail. TODO figure out what these could be
to_encapsulate.push({ node, block }); to_encapsulate.push({ node, block });
return true; return true;
} }
@ -293,11 +285,10 @@ function block_might_apply_to_node(block: Block, node: Element): BlockAppliesToN
const selector = block.selectors[i]; const selector = block.selectors[i];
const name = typeof selector.name === 'string' && selector.name.replace(regex_backslash_and_following_character, '$1'); const name = typeof selector.name === 'string' && selector.name.replace(regex_backslash_and_following_character, '$1');
if (selector.type === 'PseudoClassSelector' && (name === 'host' || name === 'root')) { if ((
return BlockAppliesToNode.NotPossible; selector.type === 'PseudoClassSelector' && (name === 'host' || name === 'root')) ||
} block.selectors.length === 1 && selector.type === 'PseudoClassSelector' && name === 'global'
) {
if (block.selectors.length === 1 && selector.type === 'PseudoClassSelector' && name === 'global') {
return BlockAppliesToNode.NotPossible; return BlockAppliesToNode.NotPossible;
} }
@ -343,9 +334,7 @@ function test_attribute(operator, expected_value, case_insensitive, value) {
function attribute_matches(node: CssNode, name: string, expected_value: string, operator: string, case_insensitive: boolean) { function attribute_matches(node: CssNode, name: string, expected_value: string, operator: string, case_insensitive: boolean) {
const spread = node.attributes.find(attr => attr.type === 'Spread'); const spread = node.attributes.find(attr => attr.type === 'Spread');
if (spread) return true; if (spread || node.bindings.some((binding: CssNode) => binding.name === name)) return true;
if (node.bindings.some((binding: CssNode) => binding.name === name)) return true;
const attr = node.attributes.find((attr: CssNode) => attr.name === name); const attr = node.attributes.find((attr: CssNode) => attr.name === name);
if (!attr) return false; if (!attr) return false;
@ -428,13 +417,10 @@ function attribute_matches(node: CssNode, name: string, expected_value: string,
} }
prev_values.forEach(prev_value => possible_values.add(prev_value)); prev_values.forEach(prev_value => possible_values.add(prev_value));
if (possible_values.has(UNKNOWN)) return true; return possible_values.has(UNKNOWN) ||
[...possible_values].some(value =>
for (const value of possible_values) { test_attribute(operator, expected_value, case_insensitive, value)
if (test_attribute(operator, expected_value, case_insensitive, value)) return true; );
}
return false;
} }
function unquote(value: CssNode) { function unquote(value: CssNode) {
@ -469,8 +455,8 @@ function get_possible_element_siblings(node: INode, adjacent_only: boolean): Map
add_to_map(possible_last_child, result); add_to_map(possible_last_child, result);
if (adjacent_only && has_definite_elements(possible_last_child)) { if (adjacent_only && has_definite_elements(possible_last_child)) {
return result; return result;
} }
} }
} }
@ -552,24 +538,21 @@ function get_possible_last_child(block: EachBlock | IfBlock | AwaitBlock, adjace
} }
function has_definite_elements(result: Map<Element, NodeExist>): boolean { function has_definite_elements(result: Map<Element, NodeExist>): boolean {
if (result.size === 0) return false; return result.size === 0
for (const exist of result.values()) { ? false
if (exist === NodeExist.Definitely) { : [...result.values()].some(exist => exist === NodeExist.Definitely);
return true;
}
}
return false;
} }
function add_to_map(from: Map<Element, NodeExist>, to: Map<Element, NodeExist>) { function add_to_map(from: Map<Element, NodeExist>, to: Map<Element, NodeExist>) {
from.forEach((exist, element) => { from.forEach((exist, element) =>
to.set(element, higher_existence(exist, to.get(element))); to.set(element, higher_existence(exist, to.get(element)))
}); );
} }
function higher_existence(exist1: NodeExist | null, exist2: NodeExist | null): NodeExist { function higher_existence(exist1: NodeExist | null, exist2: NodeExist | null): NodeExist {
if (exist1 === undefined || exist2 === undefined) return exist1 || exist2; return exist1 === undefined || exist2 === undefined
return exist1 > exist2 ? exist1 : exist2; ? exist1 || exist2
: Math.max(exist1, exist2);
} }
function mark_as_probably(result: Map<Element, NodeExist>) { function mark_as_probably(result: Map<Element, NodeExist>) {

@ -30,18 +30,14 @@ function minify_declarations(
start: number, start: number,
declarations: Declaration[] declarations: Declaration[]
): number { ): number {
let c = start; return declarations.reduce((result, declaration, i) => {
declarations.forEach((declaration, i) => {
const separator = i > 0 ? ';' : ''; const separator = i > 0 ? ';' : '';
if ((declaration.node.start - c) > separator.length) { if ((declaration.node.start - result) > separator.length) {
code.overwrite(c, declaration.node.start, separator); code.overwrite(result, declaration.node.start, separator);
} }
declaration.minify(code); declaration.minify(code);
c = declaration.node.end; return declaration.node.end;
}); }, start);
return c;
} }
class Rule { class Rule {
@ -72,17 +68,16 @@ class Rule {
let started = false; let started = false;
this.selectors.forEach((selector) => { this.selectors.forEach((selector) => {
if (selector.used) { if (!selector.used) return;
const separator = started ? ',' : ''; const separator = started ? ',' : '';
if ((selector.node.start - c) > separator.length) { if ((selector.node.start - c) > separator.length) {
code.overwrite(c, selector.node.start, separator); code.overwrite(c, selector.node.start, separator);
} }
selector.minify(code); selector.minify(code);
c = selector.node.end; c = selector.node.end;
started = true; started = true;
}
}); });
code.remove(c, this.node.block.start); code.remove(c, this.node.block.start);
@ -128,16 +123,14 @@ class Declaration {
transform(code: MagicString, keyframes: Map<string, string>) { transform(code: MagicString, keyframes: Map<string, string>) {
const property = this.node.property && remove_css_prefix(this.node.property.toLowerCase()); const property = this.node.property && remove_css_prefix(this.node.property.toLowerCase());
if (property === 'animation' || property === 'animation-name') { if (property !== 'animation' && property !== 'animation-name') return;
this.node.value.children.forEach((block: CssNode) => { this.node.value.children.forEach((block: CssNode) => {
if (block.type === 'Identifier') { if (block.type !== 'Identifier') return;
const name = block.name; const name = block.name;
if (keyframes.has(name)) { if (keyframes.has(name)) {
code.overwrite(block.start, block.end, keyframes.get(name)); code.overwrite(block.start, block.end, keyframes.get(name));
} }
} });
});
}
} }
minify(code: MagicString) { minify(code: MagicString) {
@ -174,9 +167,7 @@ class Atrule {
apply(node: Element) { apply(node: Element) {
if (this.node.name === 'media' || this.node.name === 'supports' || this.node.name === 'layer') { if (this.node.name === 'media' || this.node.name === 'supports' || this.node.name === 'layer') {
this.children.forEach(child => { this.children.forEach(child => child.apply(node));
child.apply(node);
});
} else if (is_keyframes_node(this.node)) { } else if (is_keyframes_node(this.node)) {
this.children.forEach((rule: Rule) => { this.children.forEach((rule: Rule) => {
rule.selectors.forEach(selector => { rule.selectors.forEach(selector => {
@ -246,17 +237,16 @@ class Atrule {
transform(code: MagicString, id: string, keyframes: Map<string, string>, max_amount_class_specificity_increased: number) { transform(code: MagicString, id: string, keyframes: Map<string, string>, max_amount_class_specificity_increased: number) {
if (is_keyframes_node(this.node)) { if (is_keyframes_node(this.node)) {
this.node.prelude.children.forEach(({ type, name, start, end }: CssNode) => { this.node.prelude.children.forEach(({ type, name, start, end }: CssNode) => {
if (type === 'Identifier') { if (type !== 'Identifier') return;
if (name.startsWith('-global-')) { if (name.startsWith('-global-')) {
code.remove(start, start + 8); code.remove(start, start + 8);
this.children.forEach((rule: Rule) => { this.children.forEach((rule: Rule) => {
rule.selectors.forEach(selector => { rule.selectors.forEach(selector => {
selector.used = true; selector.used = true;
});
}); });
} else { });
code.overwrite(start, end, keyframes.get(name)); } else {
} code.overwrite(start, end, keyframes.get(name));
} }
}); });
} }
@ -267,17 +257,12 @@ class Atrule {
} }
validate(component: Component) { validate(component: Component) {
this.children.forEach(child => { this.children.forEach(child => child.validate(component));
child.validate(component);
});
} }
warn_on_unused_selector(handler: (selector: Selector) => void) { warn_on_unused_selector(handler: (selector: Selector) => void) {
if (this.node.name !== 'media') return; if (this.node.name !== 'media') return;
this.children.forEach(child => child.warn_on_unused_selector(handler));
this.children.forEach(child => {
child.warn_on_unused_selector(handler);
});
} }
get_max_amount_class_specificity_increased() { get_max_amount_class_specificity_increased() {
@ -322,74 +307,75 @@ export default class Stylesheet {
this.ast = ast; this.ast = ast;
this.filename = filename; this.filename = filename;
this.dev = dev; this.dev = dev;
if (!ast.css || !ast.css.children.length) {
this.has_styles = false;
return;
}
if (ast.css && ast.css.children.length) { this.id = get_css_hash({
this.id = get_css_hash({ filename,
filename, name: component_name,
name: component_name, css: ast.css.content.styles,
css: ast.css.content.styles, hash
hash });
});
this.has_styles = true; this.has_styles = true;
const stack: Atrule[] = []; const stack: Atrule[] = [];
let depth = 0; let depth = 0;
let current_atrule: Atrule = null; let current_atrule: Atrule = null;
walk(ast.css as any, {
enter: (node: any) => {
if (node.type === 'Atrule') {
const atrule = new Atrule(node);
stack.push(atrule);
if (current_atrule) {
current_atrule.children.push(atrule);
} else if (depth <= 1) {
this.children.push(atrule);
}
if (is_keyframes_node(node)) {
node.prelude.children.forEach((expression: CssNode) => {
if (expression.type === 'Identifier' && !expression.name.startsWith('-global-')) {
this.keyframes.set(expression.name, `${this.id}-${expression.name}`);
}
});
} else if (at_rule_has_declaration(node)) {
const at_rule_declarations = node.block.children
.filter(node => node.type === 'Declaration')
.map(node => new Declaration(node));
push_array(atrule.declarations, at_rule_declarations);
}
current_atrule = atrule;
}
if (node.type === 'Rule') { walk(ast.css as any, {
const rule = new Rule(node, this, current_atrule); enter: (node: any) => {
if (node.type === 'Atrule') {
const atrule = new Atrule(node);
stack.push(atrule);
if (current_atrule) {
current_atrule.children.push(atrule);
} else if (depth <= 1) {
this.children.push(atrule);
}
if (current_atrule) { if (is_keyframes_node(node)) {
current_atrule.children.push(rule); node.prelude.children.forEach((expression: CssNode) => {
} else if (depth <= 1) { if (expression.type === 'Identifier' && !expression.name.startsWith('-global-')) {
this.children.push(rule); this.keyframes.set(expression.name, `${this.id}-${expression.name}`);
} }
});
} else if (at_rule_has_declaration(node)) {
const at_rule_declarations = node.block.children
.filter(node => node.type === 'Declaration')
.map(node => new Declaration(node));
push_array(atrule.declarations, at_rule_declarations);
} }
depth += 1; current_atrule = atrule;
}, }
leave: (node: any) => { if (node.type === 'Rule') {
if (node.type === 'Atrule') { const rule = new Rule(node, this, current_atrule);
stack.pop();
current_atrule = stack[stack.length - 1]; if (current_atrule) {
current_atrule.children.push(rule);
} else if (depth <= 1) {
this.children.push(rule);
} }
}
depth += 1;
},
depth -= 1; leave: (node: any) => {
if (node.type === 'Atrule') {
stack.pop();
current_atrule = stack[stack.length - 1];
} }
});
} else { depth -= 1;
this.has_styles = false; }
} });
} }
apply(node: Element) { apply(node: Element) {
@ -402,15 +388,13 @@ export default class Stylesheet {
} }
reify() { reify() {
this.nodes_with_css_class.forEach((node: Element) => { this.nodes_with_css_class.forEach((node: Element) => node.add_css_class());
node.add_css_class();
});
} }
render(file: string, should_transform_selectors: boolean) { render(file: string, should_transform_selectors: boolean) {
if (!this.has_styles) { if (!this.has_styles) {
return { code: null, map: null }; return { code: null, map: null };
} }
const code = new MagicString(this.source); const code = new MagicString(this.source);
@ -450,9 +434,7 @@ export default class Stylesheet {
} }
validate(component: Component) { validate(component: Component) {
this.children.forEach(child => { this.children.forEach(child => child.validate(component));
child.validate(component);
});
} }
warn_on_unused_selectors(component: Component) { warn_on_unused_selectors(component: Component) {

Loading…
Cancel
Save