From d3a410913d29f3b85273f36a8ea77b0c8911cf7c Mon Sep 17 00:00:00 2001 From: Simon Holthausen Date: Fri, 12 May 2023 15:52:23 +0200 Subject: [PATCH] Convert src/compiler/compile/css/Stylesheet.ts to JavaScript --- src/compiler/compile/css/Selector.js | 73 +- src/compiler/compile/css/Stylesheet.ts | 982 +++++++++++++------------ 2 files changed, 548 insertions(+), 507 deletions(-) diff --git a/src/compiler/compile/css/Selector.js b/src/compiler/compile/css/Selector.js index 177cb41cb4..7f26989bcb 100644 --- a/src/compiler/compile/css/Selector.js +++ b/src/compiler/compile/css/Selector.js @@ -1,22 +1,26 @@ import { gather_possible_values, UNKNOWN } from './gather_possible_values.js'; import compiler_errors from '../compiler_errors.js'; import { regex_starts_with_whitespace, regex_ends_with_whitespace } from '../../utils/patterns.js'; -let BlockAppliesToNode; -(function (BlockAppliesToNode) { - BlockAppliesToNode[(BlockAppliesToNode['NotPossible'] = 0)] = 'NotPossible'; - BlockAppliesToNode[(BlockAppliesToNode['Possible'] = 1)] = 'Possible'; - BlockAppliesToNode[(BlockAppliesToNode['UnknownSelectorType'] = 2)] = 'UnknownSelectorType'; -})(BlockAppliesToNode || (BlockAppliesToNode = {})); -let NodeExist; -(function (NodeExist) { - NodeExist[(NodeExist['Probably'] = 1)] = 'Probably'; - NodeExist[(NodeExist['Definitely'] = 2)] = 'Definitely'; -})(NodeExist || (NodeExist = {})); + +const BlockAppliesToNode = /** @type {const} */ ({ + NotPossible: 0, + Possible: 1, + UnknownSelectorType: 2 +}); + +const NodeExist = /** @type {const} */ ({ + Probably: 0, + Definitely: 1 +}); + +/** @typedef {typeof NodeExist[keyof typeof NodeExist]} NodeExistsValue */ + const whitelist_attribute_selector = new Map([ ['details', new Set(['open'])], ['dialog', new Set(['open'])] ]); const regex_is_single_css_selector = /[^\\],(?!([^([]+[^\\]|[^([\\])[)\]])/; + export default class Selector { /** @type {import('./private.js').CssNode} */ node; @@ -217,6 +221,7 @@ export default class Selector { } } } + get_amount_class_specificity_increased() { let count = 0; for (const block of this.blocks) { @@ -314,12 +319,13 @@ function apply_selector(blocks, node, to_encapsulate) { to_encapsulate.push({ node, block }); return true; } + const regex_backslash_and_following_character = /\\(.)/g; /** * @param {Block} block * @param {import('../nodes/Element.js').default} node - * @returns {BlockAppliesToNode} + * @returns {typeof BlockAppliesToNode[keyof typeof BlockAppliesToNode]} */ function block_might_apply_to_node(block, node) { let i = block.selectors.length; @@ -519,6 +525,7 @@ function get_element_parent(node) { while ((parent = parent.parent) && parent.type !== 'Element'); return /** @type {import('../nodes/Element.js').default | null} */ (parent); } + /** * Finds the given node's previous sibling in the DOM * @@ -559,10 +566,10 @@ function find_previous_sibling(node) { /** * @param {import('../nodes/interfaces.js').INode} node * @param {boolean} adjacent_only - * @returns {Map} + * @returns {Map} */ function get_possible_element_siblings(node, adjacent_only) { - /** @type {Map} */ + /** @type {Map} */ const result = new Map(); /** @type {import('../nodes/interfaces.js').INode} */ @@ -622,16 +629,18 @@ function get_possible_element_siblings(node, adjacent_only) { /** * @param {import('../nodes/EachBlock.js').default | import('../nodes/IfBlock.js').default | import('../nodes/AwaitBlock.js').default} block * @param {boolean} adjacent_only - * @returns {Map} + * @returns {Map} */ function get_possible_last_child(block, adjacent_only) { - /** @type {Map} */ + /** @typedef {Map} NodeMap */ + + /** @type {NodeMap} */ const result = new Map(); if (block.type === 'EachBlock') { - /** @type {Map} */ + /** @type {NodeMap} */ const each_result = loop_child(block.children, adjacent_only); - /** @type {Map} */ + /** @type {NodeMap} */ const else_result = block.else ? loop_child(block.else.children, adjacent_only) : new Map(); const not_exhaustive = !has_definite_elements(else_result); if (not_exhaustive) { @@ -641,10 +650,10 @@ function get_possible_last_child(block, adjacent_only) { add_to_map(each_result, result); add_to_map(else_result, result); } else if (block.type === 'IfBlock') { - /** @type {Map} */ + /** @type {NodeMap} */ const if_result = loop_child(block.children, adjacent_only); - /** @type {Map} */ + /** @type {NodeMap} */ const else_result = block.else ? loop_child(block.else.children, adjacent_only) : new Map(); const not_exhaustive = !has_definite_elements(if_result) || !has_definite_elements(else_result); if (not_exhaustive) { @@ -654,15 +663,15 @@ function get_possible_last_child(block, adjacent_only) { add_to_map(if_result, result); add_to_map(else_result, result); } else if (block.type === 'AwaitBlock') { - /** @type {Map} */ + /** @type {NodeMap} */ const pending_result = block.pending ? loop_child(block.pending.children, adjacent_only) : new Map(); - /** @type {Map} */ + /** @type {NodeMap} */ const then_result = block.then ? loop_child(block.then.children, adjacent_only) : new Map(); - /** @type {Map} */ + /** @type {NodeMap} */ const catch_result = block.catch ? loop_child(block.catch.children, adjacent_only) : new Map(); const not_exhaustive = !has_definite_elements(pending_result) || @@ -681,7 +690,7 @@ function get_possible_last_child(block, adjacent_only) { } /** - * @param {Map} result + * @param {Map} result * @returns {boolean} */ function has_definite_elements(result) { @@ -695,8 +704,9 @@ function has_definite_elements(result) { } /** - * @param {Map} from - * @param {Map} to + * @param {Map} from + * @param {Map} to + * @returns {void} */ function add_to_map(from, to) { from.forEach((exist, element) => { @@ -705,16 +715,16 @@ function add_to_map(from, to) { } /** - * @param {NodeExist | null} exist1 - * @param {NodeExist | null} exist2 - * @returns {NodeExist} + * @param {NodeExistsValue | null} exist1 + * @param {NodeExistsValue | null} exist2 + * @returns {NodeExistsValue} */ function higher_existence(exist1, exist2) { if (exist1 === undefined || exist2 === undefined) return exist1 || exist2; return exist1 > exist2 ? exist1 : exist2; } -/** @param {Map} result */ +/** @param {Map} result */ function mark_as_probably(result) { for (const key of result.keys()) { result.set(key, NodeExist.Probably); @@ -726,7 +736,7 @@ function mark_as_probably(result) { * @param {boolean} adjacent_only */ function loop_child(children, adjacent_only) { - /** @type {Map} */ + /** @type {Map} */ const result = new Map(); for (let i = children.length - 1; i >= 0; i--) { const child = children[i]; @@ -749,6 +759,7 @@ function loop_child(children, adjacent_only) { } return result; } + class Block { /** @type {boolean} */ host; diff --git a/src/compiler/compile/css/Stylesheet.ts b/src/compiler/compile/css/Stylesheet.ts index 6dc4df9548..79251276a6 100644 --- a/src/compiler/compile/css/Stylesheet.ts +++ b/src/compiler/compile/css/Stylesheet.ts @@ -1,493 +1,523 @@ import MagicString from 'magic-string'; import { walk } from 'estree-walker'; -import Selector from './Selector'; -import Element from '../nodes/Element'; -import { Ast, CssHashGetter } from '../../interfaces'; -import Component from '../Component'; -import { CssNode } from './private'; -import hash from '../utils/hash'; -import compiler_warnings from '../compiler_warnings'; -import { extract_ignores_above_position } from '../../utils/extract_svelte_ignore'; -import { push_array } from '../../utils/push_array'; -import { regex_only_whitespaces, regex_whitespace } from '../../utils/patterns'; - +import Selector from './Selector.js'; +import hash from '../utils/hash.js'; +import compiler_warnings from '../compiler_warnings.js'; +import { extract_ignores_above_position } from '../../utils/extract_svelte_ignore.js'; +import { push_array } from '../../utils/push_array.js'; +import { regex_only_whitespaces, regex_whitespace } from '../../utils/patterns.js'; const regex_css_browser_prefix = /^-((webkit)|(moz)|(o)|(ms))-/; -function remove_css_prefix(name: string): string { - return name.replace(regex_css_browser_prefix, ''); +/** + * @param {string} name + * @returns {string} + */ +function remove_css_prefix(name) { + return name.replace(regex_css_browser_prefix, ''); } -const is_keyframes_node = (node: CssNode) => remove_css_prefix(node.name) === 'keyframes'; - -const at_rule_has_declaration = ({ block }: CssNode): true => - block && block.children && block.children.find((node: CssNode) => node.type === 'Declaration'); - -function minify_declarations( - code: MagicString, - start: number, - declarations: Declaration[] -): number { - let c = start; - - declarations.forEach((declaration, i) => { - const separator = i > 0 ? ';' : ''; - if (declaration.node.start - c > separator.length) { - code.update(c, declaration.node.start, separator); - } - declaration.minify(code); - c = declaration.node.end; - }); - - return c; +/** @param {import('./private.js').CssNode} node */ +const is_keyframes_node = (node) => remove_css_prefix(node.name) === 'keyframes'; + +/** + * @param {import('./private.js').CssNode} + * @returns {true} + */ +const at_rule_has_declaration = ({ block }) => block && block.children && block.children.find((node) => node.type === 'Declaration'); + +/** + * @param {import('magic-string').default} code + * @param {number} start + * @param {Declaration[]} declarations + * @returns {number} + */ +function minify_declarations(code, start, declarations) { + let c = start; + declarations.forEach((declaration, i) => { + const separator = i > 0 ? ';' : ''; + if (declaration.node.start - c > separator.length) { + code.update(c, declaration.node.start, separator); + } + declaration.minify(code); + c = declaration.node.end; + }); + return c; } - class Rule { - selectors: Selector[]; - declarations: Declaration[]; - node: CssNode; - parent: Atrule; - - constructor(node: CssNode, stylesheet, parent?: Atrule) { - this.node = node; - this.parent = parent; - this.selectors = node.prelude.children.map((node: CssNode) => new Selector(node, stylesheet)); - this.declarations = node.block.children.map((node: CssNode) => new Declaration(node)); - } - - apply(node: Element) { - this.selectors.forEach((selector) => selector.apply(node)); // TODO move the logic in here? - } - - is_used(dev: boolean) { - if (this.parent && this.parent.node.type === 'Atrule' && is_keyframes_node(this.parent.node)) - return true; - if (this.declarations.length === 0) return dev; - return this.selectors.some((s) => s.used); - } - - minify(code: MagicString, _dev: boolean) { - let c = this.node.start; - let started = false; - - this.selectors.forEach((selector) => { - if (selector.used) { - const separator = started ? ',' : ''; - if (selector.node.start - c > separator.length) { - code.update(c, selector.node.start, separator); - } - - selector.minify(code); - c = selector.node.end; - - started = true; - } - }); - - code.remove(c, this.node.block.start); - - c = this.node.block.start + 1; - c = minify_declarations(code, c, this.declarations); - - code.remove(c, this.node.block.end - 1); - } - - transform( - code: MagicString, - id: string, - keyframes: Map, - max_amount_class_specificity_increased: number - ) { - if (this.parent && this.parent.node.type === 'Atrule' && is_keyframes_node(this.parent.node)) - return true; - - const attr = `.${id}`; - - this.selectors.forEach((selector) => - selector.transform(code, attr, max_amount_class_specificity_increased) - ); - this.declarations.forEach((declaration) => declaration.transform(code, keyframes)); - } - - validate(component: Component) { - this.selectors.forEach((selector) => { - selector.validate(component); - }); - } - - warn_on_unused_selector(handler: (selector: Selector) => void) { - this.selectors.forEach((selector) => { - if (!selector.used) handler(selector); - }); - } - - get_max_amount_class_specificity_increased() { - return Math.max( - ...this.selectors.map((selector) => selector.get_amount_class_specificity_increased()) - ); - } -} -class Declaration { - node: CssNode; - - constructor(node: CssNode) { - this.node = node; - } - - transform(code: MagicString, keyframes: Map) { - const property = this.node.property && remove_css_prefix(this.node.property.toLowerCase()); - if (property === 'animation' || property === 'animation-name') { - this.node.value.children.forEach((block: CssNode) => { - if (block.type === 'Identifier') { - const name = block.name; - if (keyframes.has(name)) { - code.update(block.start, block.end, keyframes.get(name)); - } - } - }); - } - } - - minify(code: MagicString) { - if (!this.node.property) return; // @apply, and possibly other weird cases? - - const c = this.node.start + this.node.property.length; - const first = this.node.value.children ? this.node.value.children[0] : this.node.value; - - // Don't minify whitespace in custom properties, since some browsers (Chromium < 99) - // treat --foo: ; and --foo:; differently - if (first.type === 'Raw' && regex_only_whitespaces.test(first.value)) return; - - let start = first.start; - while (regex_whitespace.test(code.original[start])) start += 1; - - if (start - c > 1) { - code.update(c, start, ':'); - } - } + /** @type {import('./Selector.js').default[]} */ + selectors; + + /** @type {Declaration[]} */ + declarations; + + /** @type {import('./private.js').CssNode} */ + node; + + /** @type {Atrule} */ + parent; + + /** + * @param {import('./private.js').CssNode} node + * @param {any} stylesheet + * @param {Atrule} [parent] + */ + constructor(node, stylesheet, parent) { + this.node = node; + this.parent = parent; + this.selectors = node.prelude.children.map((node) => new Selector(node, stylesheet)); + this.declarations = node.block.children.map((node) => new Declaration(node)); + } + + /** @param {import('../nodes/Element.js').default} node */ + apply(node) { + this.selectors.forEach((selector) => selector.apply(node)); // TODO move the logic in here? + } + + /** @param {boolean} dev */ + is_used(dev) { + if (this.parent && this.parent.node.type === 'Atrule' && is_keyframes_node(this.parent.node)) + return true; + if (this.declarations.length === 0) + return dev; + return this.selectors.some((s) => s.used); + } + + /** + * @param {import('magic-string').default} code + * @param {boolean} _dev + */ + minify(code, _dev) { + let c = this.node.start; + let started = false; + this.selectors.forEach((selector) => { + if (selector.used) { + const separator = started ? ',' : ''; + if (selector.node.start - c > separator.length) { + code.update(c, selector.node.start, separator); + } + selector.minify(code); + c = selector.node.end; + started = true; + } + }); + code.remove(c, this.node.block.start); + c = this.node.block.start + 1; + c = minify_declarations(code, c, this.declarations); + code.remove(c, this.node.block.end - 1); + } + + /** + * @param {import('magic-string').default} code + * @param {string} id + * @param {Map} keyframes + * @param {number} max_amount_class_specificity_increased + */ + transform(code, id, keyframes, max_amount_class_specificity_increased) { + if (this.parent && this.parent.node.type === 'Atrule' && is_keyframes_node(this.parent.node)) + return true; + const attr = `.${id}`; + this.selectors.forEach((selector) => selector.transform(code, attr, max_amount_class_specificity_increased)); + this.declarations.forEach((declaration) => declaration.transform(code, keyframes)); + } + + /** @param {import('../Component.js').default} component */ + validate(component) { + this.selectors.forEach((selector) => { + selector.validate(component); + }); + } + + /** @param {(selector: import('./Selector.js').default) => void} handler */ + warn_on_unused_selector(handler) { + this.selectors.forEach((selector) => { + if (!selector.used) + handler(selector); + }); + } + get_max_amount_class_specificity_increased() { + return Math.max(...this.selectors.map((selector) => selector.get_amount_class_specificity_increased())); + } } +class Declaration { + /** @type {import('./private.js').CssNode} */ + node; + + /** @param {import('./private.js').CssNode} node */ + constructor(node) { + this.node = node; + } + + /** + * @param {import('magic-string').default} code + * @param {Map} keyframes + */ + transform(code, keyframes) { + const property = this.node.property && remove_css_prefix(this.node.property.toLowerCase()); + if (property === 'animation' || property === 'animation-name') { + this.node.value.children.forEach((block) => { + if (block.type === 'Identifier') { + const name = block.name; + if (keyframes.has(name)) { + code.update(block.start, block.end, keyframes.get(name)); + } + } + }); + } + } + + /** @param {import('magic-string').default} code */ + minify(code) { + if (!this.node.property) + return; // @apply, and possibly other weird cases? + const c = this.node.start + this.node.property.length; + const first = this.node.value.children ? this.node.value.children[0] : this.node.value; + // Don't minify whitespace in custom properties, since some browsers (Chromium < 99) + // treat --foo: ; and --foo:; differently + if (first.type === 'Raw' && regex_only_whitespaces.test(first.value)) + return; + let start = first.start; + while (regex_whitespace.test(code.original[start])) + start += 1; + if (start - c > 1) { + code.update(c, start, ':'); + } + } +} class Atrule { - node: CssNode; - children: Array; - declarations: Declaration[]; - - constructor(node: CssNode) { - this.node = node; - this.children = []; - this.declarations = []; - } - - apply(node: Element) { - if ( - this.node.name === 'container' || - this.node.name === 'media' || - this.node.name === 'supports' || - this.node.name === 'layer' - ) { - this.children.forEach((child) => { - child.apply(node); - }); - } else if (is_keyframes_node(this.node)) { - this.children.forEach((rule: Rule) => { - rule.selectors.forEach((selector) => { - selector.used = true; - }); - }); - } - } - - is_used(_dev: boolean) { - return true; // TODO - } - - minify(code: MagicString, dev: boolean) { - if (this.node.name === 'media') { - const expression_char = code.original[this.node.prelude.start]; - let c = this.node.start + (expression_char === '(' ? 6 : 7); - if (this.node.prelude.start > c) code.remove(c, this.node.prelude.start); - - this.node.prelude.children.forEach((query: CssNode) => { - // TODO minify queries - c = query.end; - }); - - code.remove(c, this.node.block.start); - } else if (this.node.name === 'supports') { - let c = this.node.start + 9; - if (this.node.prelude.start - c > 1) code.update(c, this.node.prelude.start, ' '); - this.node.prelude.children.forEach((query: CssNode) => { - // TODO minify queries - c = query.end; - }); - code.remove(c, this.node.block.start); - } else { - let c = this.node.start + this.node.name.length + 1; - if (this.node.prelude) { - if (this.node.prelude.start - c > 1) code.update(c, this.node.prelude.start, ' '); - c = this.node.prelude.end; - } - if (this.node.block && this.node.block.start - c > 0) { - code.remove(c, this.node.block.start); - } - } - - // TODO other atrules - - if (this.node.block) { - let c = this.node.block.start + 1; - if (this.declarations.length) { - c = minify_declarations(code, c, this.declarations); - // if the atrule has children, leave the last declaration semicolon alone - if (this.children.length) c++; - } - - this.children.forEach((child) => { - if (child.is_used(dev)) { - code.remove(c, child.node.start); - child.minify(code, dev); - c = child.node.end; - } - }); - - code.remove(c, this.node.block.end - 1); - } - } - - transform( - code: MagicString, - id: string, - keyframes: Map, - max_amount_class_specificity_increased: number - ) { - if (is_keyframes_node(this.node)) { - this.node.prelude.children.forEach(({ type, name, start, end }: CssNode) => { - if (type === 'Identifier') { - if (name.startsWith('-global-')) { - code.remove(start, start + 8); - this.children.forEach((rule: Rule) => { - rule.selectors.forEach((selector) => { - selector.used = true; - }); - }); - } else { - code.update(start, end, keyframes.get(name)); - } - } - }); - } - - this.children.forEach((child) => { - child.transform(code, id, keyframes, max_amount_class_specificity_increased); - }); - } - - validate(component: Component) { - this.children.forEach((child) => { - child.validate(component); - }); - } - - warn_on_unused_selector(handler: (selector: Selector) => void) { - if (this.node.name !== 'media') return; - - this.children.forEach((child) => { - child.warn_on_unused_selector(handler); - }); - } - - get_max_amount_class_specificity_increased() { - return Math.max( - ...this.children.map((rule) => rule.get_max_amount_class_specificity_increased()) - ); - } + + /** @type {import('./private.js').CssNode} */ + node; + + /** @type {Array} */ + children; + + /** @type {Declaration[]} */ + declarations; + + /** @param {import('./private.js').CssNode} node */ + constructor(node) { + this.node = node; + this.children = []; + this.declarations = []; + } + + /** @param {import('../nodes/Element.js').default} node */ + apply(node) { + if (this.node.name === 'container' || + this.node.name === 'media' || + this.node.name === 'supports' || + this.node.name === 'layer') { + this.children.forEach((child) => { + child.apply(node); + }); + } + else if (is_keyframes_node(this.node)) { + this.children.forEach((rule) => { + rule.selectors.forEach((selector) => { + selector.used = true; + }); + }); + } + } + + /** @param {boolean} _dev */ + is_used(_dev) { + return true; // TODO + } + + /** + * @param {import('magic-string').default} code + * @param {boolean} dev + */ + minify(code, dev) { + if (this.node.name === 'media') { + const expression_char = code.original[this.node.prelude.start]; + let c = this.node.start + (expression_char === '(' ? 6 : 7); + if (this.node.prelude.start > c) + code.remove(c, this.node.prelude.start); + this.node.prelude.children.forEach((query) => { + // TODO minify queries + c = query.end; + }); + code.remove(c, this.node.block.start); + } + else if (this.node.name === 'supports') { + let c = this.node.start + 9; + if (this.node.prelude.start - c > 1) + code.update(c, this.node.prelude.start, ' '); + this.node.prelude.children.forEach((query) => { + // TODO minify queries + c = query.end; + }); + code.remove(c, this.node.block.start); + } + else { + let c = this.node.start + this.node.name.length + 1; + if (this.node.prelude) { + if (this.node.prelude.start - c > 1) + code.update(c, this.node.prelude.start, ' '); + c = this.node.prelude.end; + } + if (this.node.block && this.node.block.start - c > 0) { + code.remove(c, this.node.block.start); + } + } + // TODO other atrules + if (this.node.block) { + let c = this.node.block.start + 1; + if (this.declarations.length) { + c = minify_declarations(code, c, this.declarations); + // if the atrule has children, leave the last declaration semicolon alone + if (this.children.length) + c++; + } + this.children.forEach((child) => { + if (child.is_used(dev)) { + code.remove(c, child.node.start); + child.minify(code, dev); + c = child.node.end; + } + }); + code.remove(c, this.node.block.end - 1); + } + } + + /** + * @param {import('magic-string').default} code + * @param {string} id + * @param {Map} keyframes + * @param {number} max_amount_class_specificity_increased + */ + transform(code, id, keyframes, max_amount_class_specificity_increased) { + if (is_keyframes_node(this.node)) { + this.node.prelude.children.forEach(({ type, name, start, end }) => { + if (type === 'Identifier') { + if (name.startsWith('-global-')) { + code.remove(start, start + 8); + this.children.forEach((rule) => { + rule.selectors.forEach((selector) => { + selector.used = true; + }); + }); + } + else { + code.update(start, end, keyframes.get(name)); + } + } + }); + } + this.children.forEach((child) => { + child.transform(code, id, keyframes, max_amount_class_specificity_increased); + }); + } + + /** @param {import('../Component.js').default} component */ + validate(component) { + this.children.forEach((child) => { + child.validate(component); + }); + } + + /** @param {(selector: import('./Selector.js').default) => void} handler */ + warn_on_unused_selector(handler) { + if (this.node.name !== 'media') + return; + this.children.forEach((child) => { + child.warn_on_unused_selector(handler); + }); + } + get_max_amount_class_specificity_increased() { + return Math.max(...this.children.map((rule) => rule.get_max_amount_class_specificity_increased())); + } } -const get_default_css_hash: CssHashGetter = ({ css, hash }) => { - return `svelte-${hash(css)}`; +/** @param {any} */ +const get_default_css_hash = ({ css, hash }) => { + return `svelte-${hash(css)}`; }; - export default class Stylesheet { - source: string; - ast: Ast; - filename: string; - dev: boolean; - - has_styles: boolean; - id: string; - - children: Array = []; - keyframes: Map = new Map(); - - nodes_with_css_class: Set = new Set(); - - constructor({ - source, - ast, - component_name, - filename, - dev, - get_css_hash = get_default_css_hash - }: { - source: string; - ast: Ast; - filename: string | undefined; - component_name: string | undefined; - dev: boolean; - get_css_hash: CssHashGetter; - }) { - this.source = source; - this.ast = ast; - this.filename = filename; - this.dev = dev; - - if (ast.css && ast.css.children.length) { - this.id = get_css_hash({ - filename, - name: component_name, - css: ast.css.content.styles, - hash - }); - - this.has_styles = true; - - const stack: Atrule[] = []; - let depth = 0; - 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') { - const rule = new Rule(node, this, current_atrule); - - if (current_atrule) { - current_atrule.children.push(rule); - } else if (depth <= 1) { - this.children.push(rule); - } - } - - depth += 1; - }, - - leave: (node: any) => { - if (node.type === 'Atrule') { - stack.pop(); - current_atrule = stack[stack.length - 1]; - } - - depth -= 1; - } - }); - } else { - this.has_styles = false; - } - } - - apply(node: Element) { - if (!this.has_styles) return; - - for (let i = 0; i < this.children.length; i += 1) { - const child = this.children[i]; - child.apply(node); - } - } - - reify() { - this.nodes_with_css_class.forEach((node: Element) => { - node.add_css_class(); - }); - } - - render(file: string) { - if (!this.has_styles) { - return { code: null, map: null }; - } - - const code = new MagicString(this.source); - - walk(this.ast.css as any, { - enter: (node: any) => { - code.addSourcemapLocation(node.start); - code.addSourcemapLocation(node.end); - } - }); - - const max = Math.max( - ...this.children.map((rule) => rule.get_max_amount_class_specificity_increased()) - ); - this.children.forEach((child: Atrule | Rule) => { - child.transform(code, this.id, this.keyframes, max); - }); - - let c = 0; - this.children.forEach((child) => { - if (child.is_used(this.dev)) { - code.remove(c, child.node.start); - child.minify(code, this.dev); - c = child.node.end; - } - }); - - code.remove(c, this.source.length); - - return { - code: code.toString(), - map: code.generateMap({ - includeContent: true, - source: this.filename, - file - }) - }; - } - - validate(component: Component) { - this.children.forEach((child) => { - child.validate(component); - }); - } - - warn_on_unused_selectors(component: Component) { - const ignores = !this.ast.css - ? [] - : extract_ignores_above_position(this.ast.css.start, this.ast.html.children); - component.push_ignores(ignores); - this.children.forEach((child) => { - child.warn_on_unused_selector((selector: Selector) => { - component.warn( - selector.node, - compiler_warnings.css_unused_selector( - this.source.slice(selector.node.start, selector.node.end) - ) - ); - }); - }); - component.pop_ignores(); - } + + /** @type {string} */ + source; + + /** @type {import('../../interfaces.js').Ast} */ + ast; + + /** @type {string} */ + filename; + + /** @type {boolean} */ + dev; + + /** @type {boolean} */ + has_styles; + + /** @type {string} */ + id; + + /** @type {Array} */ + children = []; + + /** @type {Map} */ + keyframes = new Map(); + + /** @type {Set} */ + nodes_with_css_class = new Set(); + /** + * @param {{ + * source: string; + * ast: import('../../interfaces.js').Ast; + * filename: string | undefined; + * component_name: string | undefined; + * dev: boolean; + * get_css_hash: import('../../interfaces.js').CssHashGetter; + * }} + */ + constructor({ source, ast, component_name, filename, dev, get_css_hash = get_default_css_hash }) { + this.source = source; + this.ast = ast; + this.filename = filename; + this.dev = dev; + if (ast.css && ast.css.children.length) { + this.id = get_css_hash({ + filename, + name: component_name, + css: ast.css.content.styles, + hash + }); + this.has_styles = true; + + /** @type {Atrule[]} */ + const stack = []; + let depth = 0; + + /** @type {Atrule} */ + let current_atrule = null; + walk(/** @type {any} */ (ast.css), { + enter: (node) => { + 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) => { + 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') { + const rule = new Rule(node, this, current_atrule); + if (current_atrule) { + current_atrule.children.push(rule); + } + else if (depth <= 1) { + this.children.push(rule); + } + } + depth += 1; + }, + leave: (node) => { + if (node.type === 'Atrule') { + stack.pop(); + current_atrule = stack[stack.length - 1]; + } + depth -= 1; + } + }); + } + else { + this.has_styles = false; + } + } + + /** @param {import('../nodes/Element.js').default} node */ + apply(node) { + if (!this.has_styles) + return; + for (let i = 0; i < this.children.length; i += 1) { + const child = this.children[i]; + child.apply(node); + } + } + reify() { + this.nodes_with_css_class.forEach((node) => { + node.add_css_class(); + }); + } + + /** @param {string} file */ + render(file) { + if (!this.has_styles) { + return { code: null, map: null }; + } + const code = new MagicString(this.source); + walk(/** @type {any} */ (this.ast.css), { + enter: (node) => { + code.addSourcemapLocation(node.start); + code.addSourcemapLocation(node.end); + } + }); + const max = Math.max(...this.children.map((rule) => rule.get_max_amount_class_specificity_increased())); + this.children.forEach((child) => { + child.transform(code, this.id, this.keyframes, max); + }); + let c = 0; + this.children.forEach((child) => { + if (child.is_used(this.dev)) { + code.remove(c, child.node.start); + child.minify(code, this.dev); + c = child.node.end; + } + }); + code.remove(c, this.source.length); + return { + code: code.toString(), + map: code.generateMap({ + includeContent: true, + source: this.filename, + file + }) + }; + } + + /** @param {import('../Component.js').default} component */ + validate(component) { + this.children.forEach((child) => { + child.validate(component); + }); + } + + /** @param {import('../Component.js').default} component */ + warn_on_unused_selectors(component) { + const ignores = !this.ast.css + ? [] + : extract_ignores_above_position(this.ast.css.start, this.ast.html.children); + component.push_ignores(ignores); + this.children.forEach((child) => { + child.warn_on_unused_selector((selector) => { + component.warn(selector.node, compiler_warnings.css_unused_selector(this.source.slice(selector.node.start, selector.node.end))); + }); + }); + component.pop_ignores(); + } } + + + +