From e714b895455569073f46cee2f5f11d25f2b9ca35 Mon Sep 17 00:00:00 2001 From: paoloricciuti Date: Wed, 5 Mar 2025 16:02:58 +0100 Subject: [PATCH 01/24] feat: add `hasUnscopedGlobalCss` to `compile` metadata --- .changeset/plenty-hotels-mix.md | 5 ++ .../phases/2-analyze/css/css-analyze.js | 53 ++++++++++++++++++- .../src/compiler/phases/2-analyze/index.js | 3 +- .../src/compiler/phases/3-transform/index.js | 12 +++-- .../svelte/src/compiler/phases/types.d.ts | 1 + packages/svelte/src/compiler/types/index.d.ts | 5 ++ packages/svelte/types/index.d.ts | 5 ++ 7 files changed, 77 insertions(+), 7 deletions(-) create mode 100644 .changeset/plenty-hotels-mix.md diff --git a/.changeset/plenty-hotels-mix.md b/.changeset/plenty-hotels-mix.md new file mode 100644 index 000000000000..226872f99c02 --- /dev/null +++ b/.changeset/plenty-hotels-mix.md @@ -0,0 +1,5 @@ +--- +'svelte': minor +--- + +feat: add `hasUnscopedGlobalCss` to `compile` metadata diff --git a/packages/svelte/src/compiler/phases/2-analyze/css/css-analyze.js b/packages/svelte/src/compiler/phases/2-analyze/css/css-analyze.js index ed228385820a..0b83053cef5f 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/css/css-analyze.js +++ b/packages/svelte/src/compiler/phases/2-analyze/css/css-analyze.js @@ -7,11 +7,13 @@ import { is_keyframes_node } from '../../css.js'; import { is_global, is_unscoped_pseudo_class } from './utils.js'; /** + * We need to use an object for `has_global_unscoped` since state is spread * @typedef {Visitors< * AST.CSS.Node, * { * keyframes: string[]; * rule: AST.CSS.Rule | null; + * has_global_unscoped: { value: boolean }; * } * >} CssVisitors */ @@ -28,6 +30,30 @@ function is_global_block_selector(simple_selector) { ); } +/** + * @param {import('../types.js').Context["path"]} path + * @param {AST.CSS.Rule | null} [rule] + * @returns + */ +function is_unscoped_global(path, rule) { + // remove every at rule or stylesheet and the current rule in case is passed in from `ComplexSelector` + const parents = path.filter( + (parent) => parent.type !== 'Atrule' && parent.type !== 'StyleSheet' && parent !== rule + ); + + let unscoped_global = true; + + // no parents means we are on top + if (parents.length > 0) { + // let's check that everything in the path is not a global block + unscoped_global = parents.every((parent) => { + return parent.type !== 'Rule' || parent.metadata.is_global_block; + }); + } + + return unscoped_global; +} + /** * * @param {Array} path @@ -64,6 +90,16 @@ const css_visitors = { } } } + + if (idx === 0) { + if ( + is_unscoped_global(context.path, context.state.rule) && + context.state.rule && + context.state.rule.block.children.length > 0 + ) { + context.state.has_global_unscoped.value = true; + } + } } } @@ -174,7 +210,8 @@ const css_visitors = { node.metadata.is_global_block = node.prelude.children.some((selector) => { let is_global_block = false; - for (const child of selector.children) { + for (let i = 0; i < selector.children.length; i++) { + const child = selector.children[i]; const idx = child.selectors.findIndex(is_global_block_selector); if (is_global_block) { @@ -184,6 +221,11 @@ const css_visitors = { if (idx !== -1) { is_global_block = true; + if (i === 0) { + if (is_unscoped_global(context.path) && node.block.children.length > 0) { + context.state.has_global_unscoped.value = true; + } + } for (let i = idx + 1; i < child.selectors.length; i++) { walk(/** @type {AST.CSS.Node} */ (child.selectors[i]), null, { ComplexSelector(node) { @@ -283,5 +325,12 @@ const css_visitors = { * @param {ComponentAnalysis} analysis */ export function analyze_css(stylesheet, analysis) { - walk(stylesheet, { keyframes: analysis.css.keyframes, rule: null }, css_visitors); + const css_state = { + keyframes: analysis.css.keyframes, + rule: null, + // we need to use an object since state is spread + has_global_unscoped: { value: false } + }; + walk(stylesheet, css_state, css_visitors); + analysis.css.has_global_unscoped = css_state.has_global_unscoped.value; } diff --git a/packages/svelte/src/compiler/phases/2-analyze/index.js b/packages/svelte/src/compiler/phases/2-analyze/index.js index 322293bf6b91..8e6cc8fd134f 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/index.js +++ b/packages/svelte/src/compiler/phases/2-analyze/index.js @@ -455,7 +455,8 @@ export function analyze_component(root, source, options) { hash }) : '', - keyframes: [] + keyframes: [], + has_global_unscoped: false }, source, undefined_exports: new Map(), diff --git a/packages/svelte/src/compiler/phases/3-transform/index.js b/packages/svelte/src/compiler/phases/3-transform/index.js index f96fd64ec7a9..9e02675feaed 100644 --- a/packages/svelte/src/compiler/phases/3-transform/index.js +++ b/packages/svelte/src/compiler/phases/3-transform/index.js @@ -21,7 +21,8 @@ export function transform_component(analysis, source, options) { css: null, warnings: state.warnings, // set afterwards metadata: { - runes: analysis.runes + runes: analysis.runes, + hasUnscopedGlobalCss: analysis.css.has_global_unscoped }, ast: /** @type {any} */ (null) // set afterwards }; @@ -53,7 +54,8 @@ export function transform_component(analysis, source, options) { css, warnings: state.warnings, // set afterwards. TODO apply preprocessor sourcemap metadata: { - runes: analysis.runes + runes: analysis.runes, + hasUnscopedGlobalCss: analysis.css.has_global_unscoped }, ast: /** @type {any} */ (null) // set afterwards }; @@ -72,7 +74,8 @@ export function transform_module(analysis, source, options) { css: null, warnings: state.warnings, // set afterwards metadata: { - runes: true + runes: true, + hasUnscopedGlobalCss: false }, ast: /** @type {any} */ (null) // set afterwards }; @@ -102,7 +105,8 @@ export function transform_module(analysis, source, options) { }), css: null, metadata: { - runes: true + runes: true, + hasUnscopedGlobalCss: false }, warnings: state.warnings, // set afterwards ast: /** @type {any} */ (null) // set afterwards diff --git a/packages/svelte/src/compiler/phases/types.d.ts b/packages/svelte/src/compiler/phases/types.d.ts index abe2b115de02..f5b480cb317d 100644 --- a/packages/svelte/src/compiler/phases/types.d.ts +++ b/packages/svelte/src/compiler/phases/types.d.ts @@ -73,6 +73,7 @@ export interface ComponentAnalysis extends Analysis { ast: AST.CSS.StyleSheet | null; hash: string; keyframes: string[]; + has_global_unscoped: boolean; }; source: string; undefined_exports: Map; diff --git a/packages/svelte/src/compiler/types/index.d.ts b/packages/svelte/src/compiler/types/index.d.ts index eec41bad9d25..7ee97954a5a4 100644 --- a/packages/svelte/src/compiler/types/index.d.ts +++ b/packages/svelte/src/compiler/types/index.d.ts @@ -35,6 +35,11 @@ export interface CompileResult { * For `compileModule`, this is always `true` */ runes: boolean; + /** + * Whether the component contains a top level :global selector or not + * For `compileModule`, this is always `true` + */ + hasUnscopedGlobalCss: boolean; }; /** The AST */ ast: any; diff --git a/packages/svelte/types/index.d.ts b/packages/svelte/types/index.d.ts index c3dbdcac791e..03ed49050c7a 100644 --- a/packages/svelte/types/index.d.ts +++ b/packages/svelte/types/index.d.ts @@ -769,6 +769,11 @@ declare module 'svelte/compiler' { * For `compileModule`, this is always `true` */ runes: boolean; + /** + * Whether the component contains a top level :global selector or not + * For `compileModule`, this is always `true` + */ + hasUnscopedGlobalCss: boolean; }; /** The AST */ ast: any; From 89682a21b8f3371d28ea1bcfad1b58028a5fb5ac Mon Sep 17 00:00:00 2001 From: paoloricciuti Date: Wed, 5 Mar 2025 16:06:53 +0100 Subject: [PATCH 02/24] chore: rename to `has_unscoped_global` --- .../src/compiler/phases/2-analyze/css/css-analyze.js | 10 +++++----- packages/svelte/src/compiler/phases/2-analyze/index.js | 2 +- .../svelte/src/compiler/phases/3-transform/index.js | 4 ++-- packages/svelte/src/compiler/phases/types.d.ts | 2 +- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/packages/svelte/src/compiler/phases/2-analyze/css/css-analyze.js b/packages/svelte/src/compiler/phases/2-analyze/css/css-analyze.js index 0b83053cef5f..87bc2cc35ae4 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/css/css-analyze.js +++ b/packages/svelte/src/compiler/phases/2-analyze/css/css-analyze.js @@ -13,7 +13,7 @@ import { is_global, is_unscoped_pseudo_class } from './utils.js'; * { * keyframes: string[]; * rule: AST.CSS.Rule | null; - * has_global_unscoped: { value: boolean }; + * has_unscoped_global: { value: boolean }; * } * >} CssVisitors */ @@ -97,7 +97,7 @@ const css_visitors = { context.state.rule && context.state.rule.block.children.length > 0 ) { - context.state.has_global_unscoped.value = true; + context.state.has_unscoped_global.value = true; } } } @@ -223,7 +223,7 @@ const css_visitors = { is_global_block = true; if (i === 0) { if (is_unscoped_global(context.path) && node.block.children.length > 0) { - context.state.has_global_unscoped.value = true; + context.state.has_unscoped_global.value = true; } } for (let i = idx + 1; i < child.selectors.length; i++) { @@ -329,8 +329,8 @@ export function analyze_css(stylesheet, analysis) { keyframes: analysis.css.keyframes, rule: null, // we need to use an object since state is spread - has_global_unscoped: { value: false } + has_unscoped_global: { value: false } }; walk(stylesheet, css_state, css_visitors); - analysis.css.has_global_unscoped = css_state.has_global_unscoped.value; + analysis.css.has_unscoped_global = css_state.has_unscoped_global.value; } diff --git a/packages/svelte/src/compiler/phases/2-analyze/index.js b/packages/svelte/src/compiler/phases/2-analyze/index.js index 8e6cc8fd134f..20c2e6c94b18 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/index.js +++ b/packages/svelte/src/compiler/phases/2-analyze/index.js @@ -456,7 +456,7 @@ export function analyze_component(root, source, options) { }) : '', keyframes: [], - has_global_unscoped: false + has_unscoped_global: false }, source, undefined_exports: new Map(), diff --git a/packages/svelte/src/compiler/phases/3-transform/index.js b/packages/svelte/src/compiler/phases/3-transform/index.js index 9e02675feaed..87aab46c293f 100644 --- a/packages/svelte/src/compiler/phases/3-transform/index.js +++ b/packages/svelte/src/compiler/phases/3-transform/index.js @@ -22,7 +22,7 @@ export function transform_component(analysis, source, options) { warnings: state.warnings, // set afterwards metadata: { runes: analysis.runes, - hasUnscopedGlobalCss: analysis.css.has_global_unscoped + hasUnscopedGlobalCss: analysis.css.has_unscoped_global }, ast: /** @type {any} */ (null) // set afterwards }; @@ -55,7 +55,7 @@ export function transform_component(analysis, source, options) { warnings: state.warnings, // set afterwards. TODO apply preprocessor sourcemap metadata: { runes: analysis.runes, - hasUnscopedGlobalCss: analysis.css.has_global_unscoped + hasUnscopedGlobalCss: analysis.css.has_unscoped_global }, ast: /** @type {any} */ (null) // set afterwards }; diff --git a/packages/svelte/src/compiler/phases/types.d.ts b/packages/svelte/src/compiler/phases/types.d.ts index f5b480cb317d..341c5ad157cb 100644 --- a/packages/svelte/src/compiler/phases/types.d.ts +++ b/packages/svelte/src/compiler/phases/types.d.ts @@ -73,7 +73,7 @@ export interface ComponentAnalysis extends Analysis { ast: AST.CSS.StyleSheet | null; hash: string; keyframes: string[]; - has_global_unscoped: boolean; + has_unscoped_global: boolean; }; source: string; undefined_exports: Map; From 9f29b81314c307549a0bcb46afd46014a4f8b0ad Mon Sep 17 00:00:00 2001 From: paoloricciuti Date: Wed, 5 Mar 2025 16:25:02 +0100 Subject: [PATCH 03/24] fix: handle `-global` keyframes --- .../svelte/src/compiler/phases/2-analyze/css/css-analyze.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/packages/svelte/src/compiler/phases/2-analyze/css/css-analyze.js b/packages/svelte/src/compiler/phases/2-analyze/css/css-analyze.js index 87bc2cc35ae4..b1b6fa55e240 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/css/css-analyze.js +++ b/packages/svelte/src/compiler/phases/2-analyze/css/css-analyze.js @@ -68,6 +68,11 @@ const css_visitors = { if (is_keyframes_node(node)) { if (!node.prelude.startsWith('-global-') && !is_in_global_block(context.path)) { context.state.keyframes.push(node.prelude); + } else if (node.prelude.startsWith('-global-')) { + // we don't check if the block.children.length because the keyframe is still added even if empty + if (is_unscoped_global(context.path)) { + context.state.has_unscoped_global.value = true; + } } } From ee4869f7c58aa1f5d38414b8dbc786fa86d87357 Mon Sep 17 00:00:00 2001 From: paoloricciuti Date: Wed, 5 Mar 2025 16:26:54 +0100 Subject: [PATCH 04/24] chore: guard the check if the value is already true --- .../src/compiler/phases/2-analyze/css/css-analyze.js | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/packages/svelte/src/compiler/phases/2-analyze/css/css-analyze.js b/packages/svelte/src/compiler/phases/2-analyze/css/css-analyze.js index b1b6fa55e240..e6a6f19afbf7 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/css/css-analyze.js +++ b/packages/svelte/src/compiler/phases/2-analyze/css/css-analyze.js @@ -70,7 +70,7 @@ const css_visitors = { context.state.keyframes.push(node.prelude); } else if (node.prelude.startsWith('-global-')) { // we don't check if the block.children.length because the keyframe is still added even if empty - if (is_unscoped_global(context.path)) { + if (!context.state.has_unscoped_global.value && is_unscoped_global(context.path)) { context.state.has_unscoped_global.value = true; } } @@ -98,6 +98,7 @@ const css_visitors = { if (idx === 0) { if ( + !context.state.has_unscoped_global.value && is_unscoped_global(context.path, context.state.rule) && context.state.rule && context.state.rule.block.children.length > 0 @@ -227,7 +228,11 @@ const css_visitors = { if (idx !== -1) { is_global_block = true; if (i === 0) { - if (is_unscoped_global(context.path) && node.block.children.length > 0) { + if ( + !context.state.has_unscoped_global.value && + is_unscoped_global(context.path) && + node.block.children.length > 0 + ) { context.state.has_unscoped_global.value = true; } } From 53722795cdc03ef578c9ff99ec328328645a1dcf Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Thu, 10 Apr 2025 20:22:24 -0400 Subject: [PATCH 05/24] update types --- .../svelte/src/compiler/phases/3-transform/index.js | 12 ++++-------- packages/svelte/src/compiler/types/index.d.ts | 7 ++----- 2 files changed, 6 insertions(+), 13 deletions(-) diff --git a/packages/svelte/src/compiler/phases/3-transform/index.js b/packages/svelte/src/compiler/phases/3-transform/index.js index 87aab46c293f..f96fd64ec7a9 100644 --- a/packages/svelte/src/compiler/phases/3-transform/index.js +++ b/packages/svelte/src/compiler/phases/3-transform/index.js @@ -21,8 +21,7 @@ export function transform_component(analysis, source, options) { css: null, warnings: state.warnings, // set afterwards metadata: { - runes: analysis.runes, - hasUnscopedGlobalCss: analysis.css.has_unscoped_global + runes: analysis.runes }, ast: /** @type {any} */ (null) // set afterwards }; @@ -54,8 +53,7 @@ export function transform_component(analysis, source, options) { css, warnings: state.warnings, // set afterwards. TODO apply preprocessor sourcemap metadata: { - runes: analysis.runes, - hasUnscopedGlobalCss: analysis.css.has_unscoped_global + runes: analysis.runes }, ast: /** @type {any} */ (null) // set afterwards }; @@ -74,8 +72,7 @@ export function transform_module(analysis, source, options) { css: null, warnings: state.warnings, // set afterwards metadata: { - runes: true, - hasUnscopedGlobalCss: false + runes: true }, ast: /** @type {any} */ (null) // set afterwards }; @@ -105,8 +102,7 @@ export function transform_module(analysis, source, options) { }), css: null, metadata: { - runes: true, - hasUnscopedGlobalCss: false + runes: true }, warnings: state.warnings, // set afterwards ast: /** @type {any} */ (null) // set afterwards diff --git a/packages/svelte/src/compiler/types/index.d.ts b/packages/svelte/src/compiler/types/index.d.ts index 7ee97954a5a4..616c346ad35a 100644 --- a/packages/svelte/src/compiler/types/index.d.ts +++ b/packages/svelte/src/compiler/types/index.d.ts @@ -18,6 +18,8 @@ export interface CompileResult { code: string; /** A source map */ map: SourceMap; + /** Whether or not the CSS includes global rules */ + hasGlobal: boolean; }; /** * An array of warning objects that were generated during compilation. Each warning has several properties: @@ -35,11 +37,6 @@ export interface CompileResult { * For `compileModule`, this is always `true` */ runes: boolean; - /** - * Whether the component contains a top level :global selector or not - * For `compileModule`, this is always `true` - */ - hasUnscopedGlobalCss: boolean; }; /** The AST */ ast: any; From 0cac25c0c15acb4f4de128e03147400f316742c6 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Thu, 10 Apr 2025 20:33:10 -0400 Subject: [PATCH 06/24] add tests --- .../svelte/src/compiler/phases/3-transform/css/index.js | 3 ++- .../svelte/tests/css/samples/global-keyframes/_config.js | 5 +++++ .../tests/css/samples/global-with-nesting/_config.js | 4 +++- packages/svelte/tests/css/samples/global/_config.js | 5 +++++ packages/svelte/tests/css/test.ts | 9 +++++++++ packages/svelte/tests/helpers.js | 4 ++++ 6 files changed, 28 insertions(+), 2 deletions(-) create mode 100644 packages/svelte/tests/css/samples/global-keyframes/_config.js create mode 100644 packages/svelte/tests/css/samples/global/_config.js diff --git a/packages/svelte/src/compiler/phases/3-transform/css/index.js b/packages/svelte/src/compiler/phases/3-transform/css/index.js index 5b0dcd558893..478c22e716ea 100644 --- a/packages/svelte/src/compiler/phases/3-transform/css/index.js +++ b/packages/svelte/src/compiler/phases/3-transform/css/index.js @@ -59,7 +59,8 @@ export function render_stylesheet(source, analysis, options) { // generateMap takes care of calculating source relative to file source: options.filename, file: options.cssOutputFilename || options.filename - }) + }), + hasGlobal: analysis.css.has_unscoped_global }; merge_with_preprocessor_map(css, options, css.map.sources[0]); diff --git a/packages/svelte/tests/css/samples/global-keyframes/_config.js b/packages/svelte/tests/css/samples/global-keyframes/_config.js new file mode 100644 index 000000000000..30953854ad59 --- /dev/null +++ b/packages/svelte/tests/css/samples/global-keyframes/_config.js @@ -0,0 +1,5 @@ +import { test } from '../../test'; + +export default test({ + hasGlobal: true +}); diff --git a/packages/svelte/tests/css/samples/global-with-nesting/_config.js b/packages/svelte/tests/css/samples/global-with-nesting/_config.js index 292c6c49ac9d..6cec7c236045 100644 --- a/packages/svelte/tests/css/samples/global-with-nesting/_config.js +++ b/packages/svelte/tests/css/samples/global-with-nesting/_config.js @@ -1,5 +1,7 @@ import { test } from '../../test'; export default test({ - warnings: [] + warnings: [], + + hasGlobal: false }); diff --git a/packages/svelte/tests/css/samples/global/_config.js b/packages/svelte/tests/css/samples/global/_config.js new file mode 100644 index 000000000000..30953854ad59 --- /dev/null +++ b/packages/svelte/tests/css/samples/global/_config.js @@ -0,0 +1,5 @@ +import { test } from '../../test'; + +export default test({ + hasGlobal: true +}); diff --git a/packages/svelte/tests/css/test.ts b/packages/svelte/tests/css/test.ts index dd51f52eabc3..8846b1d9862e 100644 --- a/packages/svelte/tests/css/test.ts +++ b/packages/svelte/tests/css/test.ts @@ -34,6 +34,7 @@ interface CssTest extends BaseTest { compileOptions?: Partial; warnings?: Warning[]; props?: Record; + hasGlobal?: boolean; } /** @@ -78,6 +79,14 @@ const { test, run } = suite(async (config, cwd) => { // assert_html_equal(actual_ssr, expected.html); } + if (config.hasGlobal !== undefined) { + const metadata = JSON.parse( + fs.readFileSync(`${cwd}/_output/client/input.svelte.css.json`, 'utf-8') + ); + + assert.equal(metadata.hasGlobal, config.hasGlobal); + } + const dom_css = fs.readFileSync(`${cwd}/_output/client/input.svelte.css`, 'utf-8').trim(); const ssr_css = fs.readFileSync(`${cwd}/_output/server/input.svelte.css`, 'utf-8').trim(); diff --git a/packages/svelte/tests/helpers.js b/packages/svelte/tests/helpers.js index 87bcb473e7e2..f853d5873c57 100644 --- a/packages/svelte/tests/helpers.js +++ b/packages/svelte/tests/helpers.js @@ -146,6 +146,10 @@ export async function compile_directory( if (compiled.css) { write(`${output_dir}/${file}.css`, compiled.css.code); + write( + `${output_dir}/${file}.css.json`, + JSON.stringify({ hasGlobal: compiled.css.hasGlobal }) + ); if (output_map) { write(`${output_dir}/${file}.css.map`, JSON.stringify(compiled.css.map, null, '\t')); } From 212fe9c5db56b7c9e6bf39d22861b01ab054734c Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Thu, 10 Apr 2025 20:41:14 -0400 Subject: [PATCH 07/24] tweak --- .../compiler/phases/2-analyze/css/css-analyze.js | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/packages/svelte/src/compiler/phases/2-analyze/css/css-analyze.js b/packages/svelte/src/compiler/phases/2-analyze/css/css-analyze.js index e6a6f19afbf7..abe324aa6e92 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/css/css-analyze.js +++ b/packages/svelte/src/compiler/phases/2-analyze/css/css-analyze.js @@ -70,9 +70,7 @@ const css_visitors = { context.state.keyframes.push(node.prelude); } else if (node.prelude.startsWith('-global-')) { // we don't check if the block.children.length because the keyframe is still added even if empty - if (!context.state.has_unscoped_global.value && is_unscoped_global(context.path)) { - context.state.has_unscoped_global.value = true; - } + context.state.has_unscoped_global.value ||= is_unscoped_global(context.path); } } @@ -97,14 +95,10 @@ const css_visitors = { } if (idx === 0) { - if ( - !context.state.has_unscoped_global.value && - is_unscoped_global(context.path, context.state.rule) && - context.state.rule && - context.state.rule.block.children.length > 0 - ) { - context.state.has_unscoped_global.value = true; - } + context.state.has_unscoped_global.value ||= + !!context.state.rule && + context.state.rule.block.children.length > 0 && + is_unscoped_global(context.path, context.state.rule); } } } From 4289b720ab2e3877d0787fe7e931d5edb014b90b Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Thu, 10 Apr 2025 20:43:02 -0400 Subject: [PATCH 08/24] tweak --- .../src/compiler/phases/2-analyze/css/css-analyze.js | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/packages/svelte/src/compiler/phases/2-analyze/css/css-analyze.js b/packages/svelte/src/compiler/phases/2-analyze/css/css-analyze.js index abe324aa6e92..fcd67694c7cc 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/css/css-analyze.js +++ b/packages/svelte/src/compiler/phases/2-analyze/css/css-analyze.js @@ -221,15 +221,12 @@ const css_visitors = { if (idx !== -1) { is_global_block = true; + if (i === 0) { - if ( - !context.state.has_unscoped_global.value && - is_unscoped_global(context.path) && - node.block.children.length > 0 - ) { - context.state.has_unscoped_global.value = true; - } + context.state.has_unscoped_global.value ||= + node.block.children.length > 0 && is_unscoped_global(context.path); } + for (let i = idx + 1; i < child.selectors.length; i++) { walk(/** @type {AST.CSS.Node} */ (child.selectors[i]), null, { ComplexSelector(node) { From 263984c3bb95a5b7669e3d02c08b791974de9d6d Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Thu, 10 Apr 2025 20:44:46 -0400 Subject: [PATCH 09/24] regenerate types --- packages/svelte/types/index.d.ts | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/packages/svelte/types/index.d.ts b/packages/svelte/types/index.d.ts index 03ed49050c7a..8eefe741adce 100644 --- a/packages/svelte/types/index.d.ts +++ b/packages/svelte/types/index.d.ts @@ -752,6 +752,8 @@ declare module 'svelte/compiler' { code: string; /** A source map */ map: SourceMap; + /** Whether or not the CSS includes global rules */ + hasGlobal: boolean; }; /** * An array of warning objects that were generated during compilation. Each warning has several properties: @@ -769,11 +771,6 @@ declare module 'svelte/compiler' { * For `compileModule`, this is always `true` */ runes: boolean; - /** - * Whether the component contains a top level :global selector or not - * For `compileModule`, this is always `true` - */ - hasUnscopedGlobalCss: boolean; }; /** The AST */ ast: any; From 23f797ad5fc0c46c9a2b23b0c6d4f6bf9cdf20aa Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Thu, 10 Apr 2025 20:45:20 -0400 Subject: [PATCH 10/24] Update .changeset/plenty-hotels-mix.md --- .changeset/plenty-hotels-mix.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/plenty-hotels-mix.md b/.changeset/plenty-hotels-mix.md index 226872f99c02..5e7aa834da77 100644 --- a/.changeset/plenty-hotels-mix.md +++ b/.changeset/plenty-hotels-mix.md @@ -2,4 +2,4 @@ 'svelte': minor --- -feat: add `hasUnscopedGlobalCss` to `compile` metadata +feat: add `css.hasGlobal` to `compile` output From f22719589328de11ee5a39d2ed9976f33d593f43 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Thu, 10 Apr 2025 21:12:11 -0400 Subject: [PATCH 11/24] fix test, add failing test --- .../svelte/tests/css/samples/global-local/_config.js | 5 +++++ .../tests/css/samples/global-local/expected.css | 8 ++++++++ .../tests/css/samples/global-local/input.svelte | 11 +++++++++++ .../tests/css/samples/global-with-nesting/_config.js | 2 +- 4 files changed, 25 insertions(+), 1 deletion(-) create mode 100644 packages/svelte/tests/css/samples/global-local/_config.js create mode 100644 packages/svelte/tests/css/samples/global-local/expected.css create mode 100644 packages/svelte/tests/css/samples/global-local/input.svelte diff --git a/packages/svelte/tests/css/samples/global-local/_config.js b/packages/svelte/tests/css/samples/global-local/_config.js new file mode 100644 index 000000000000..5a7796ebac7e --- /dev/null +++ b/packages/svelte/tests/css/samples/global-local/_config.js @@ -0,0 +1,5 @@ +import { test } from '../../test'; + +export default test({ + hasGlobal: false +}); diff --git a/packages/svelte/tests/css/samples/global-local/expected.css b/packages/svelte/tests/css/samples/global-local/expected.css new file mode 100644 index 000000000000..c4fc74fb1aaf --- /dev/null +++ b/packages/svelte/tests/css/samples/global-local/expected.css @@ -0,0 +1,8 @@ + + div.svelte-xyz .whatever { + color: green; + } + + .whatever div.svelte-xyz { + color: green; + } diff --git a/packages/svelte/tests/css/samples/global-local/input.svelte b/packages/svelte/tests/css/samples/global-local/input.svelte new file mode 100644 index 000000000000..bff97ab485a2 --- /dev/null +++ b/packages/svelte/tests/css/samples/global-local/input.svelte @@ -0,0 +1,11 @@ +
{@html whatever}
+ + diff --git a/packages/svelte/tests/css/samples/global-with-nesting/_config.js b/packages/svelte/tests/css/samples/global-with-nesting/_config.js index 6cec7c236045..e8080368ccc5 100644 --- a/packages/svelte/tests/css/samples/global-with-nesting/_config.js +++ b/packages/svelte/tests/css/samples/global-with-nesting/_config.js @@ -3,5 +3,5 @@ import { test } from '../../test'; export default test({ warnings: [], - hasGlobal: false + hasGlobal: true }); From 5e0b8ced33bc0adaf4b0084e70670a70b553393d Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Thu, 10 Apr 2025 21:25:19 -0400 Subject: [PATCH 12/24] fix --- .../phases/2-analyze/css/css-analyze.js | 44 +++++++++---------- .../src/compiler/phases/2-analyze/index.js | 2 +- .../compiler/phases/3-transform/css/index.js | 2 +- .../svelte/src/compiler/phases/types.d.ts | 2 +- 4 files changed, 24 insertions(+), 26 deletions(-) diff --git a/packages/svelte/src/compiler/phases/2-analyze/css/css-analyze.js b/packages/svelte/src/compiler/phases/2-analyze/css/css-analyze.js index 639d7bfc57e0..7b1f064f91c6 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/css/css-analyze.js +++ b/packages/svelte/src/compiler/phases/2-analyze/css/css-analyze.js @@ -6,16 +6,17 @@ import * as e from '../../../errors.js'; import { is_keyframes_node } from '../../css.js'; import { is_global, is_unscoped_pseudo_class } from './utils.js'; +/** + * @typedef {{ + * keyframes: string[]; + * rule: AST.CSS.Rule | null; + * has_global: { value: boolean }; + * }} CssState + */ + /** * We need to use an object for `has_global_unscoped` since state is spread - * @typedef {Visitors< - * AST.CSS.Node, - * { - * keyframes: string[]; - * rule: AST.CSS.Rule | null; - * has_unscoped_global: { value: boolean }; - * } - * >} CssVisitors + * @typedef {Visitors} CssVisitors */ /** @@ -70,7 +71,7 @@ const css_visitors = { context.state.keyframes.push(node.prelude); } else if (node.prelude.startsWith('-global-')) { // we don't check if the block.children.length because the keyframe is still added even if empty - context.state.has_unscoped_global.value ||= is_unscoped_global(context.path); + context.state.has_global.value ||= is_unscoped_global(context.path); } } @@ -93,13 +94,6 @@ const css_visitors = { } } } - - if (idx === 0) { - context.state.has_unscoped_global.value ||= - !!context.state.rule && - context.state.rule.block.children.length > 0 && - is_unscoped_global(context.path, context.state.rule); - } } } @@ -213,6 +207,13 @@ const css_visitors = { Rule(node, context) { node.metadata.parent_rule = context.state.rule; + // if this rule has a ComplexSelector whose RelativeSelector children + // are all `:global(...)` + context.state.has_global.value ||= + node.block.children.length > 0 && + node.prelude.children.some((selector) => selector.children.every(is_global)) && + is_unscoped_global(context.path); + node.metadata.is_global_block = node.prelude.children.some((selector) => { let is_global_block = false; @@ -228,11 +229,6 @@ const css_visitors = { if (idx !== -1) { is_global_block = true; - if (i === 0) { - context.state.has_unscoped_global.value ||= - node.block.children.length > 0 && is_unscoped_global(context.path); - } - for (let i = idx + 1; i < child.selectors.length; i++) { walk(/** @type {AST.CSS.Node} */ (child.selectors[i]), null, { ComplexSelector(node) { @@ -332,12 +328,14 @@ const css_visitors = { * @param {ComponentAnalysis} analysis */ export function analyze_css(stylesheet, analysis) { + /** @type {CssState} */ const css_state = { keyframes: analysis.css.keyframes, rule: null, // we need to use an object since state is spread - has_unscoped_global: { value: false } + has_global: { value: false } }; + walk(stylesheet, css_state, css_visitors); - analysis.css.has_unscoped_global = css_state.has_unscoped_global.value; + analysis.css.has_global = css_state.has_global.value; } diff --git a/packages/svelte/src/compiler/phases/2-analyze/index.js b/packages/svelte/src/compiler/phases/2-analyze/index.js index 0dad63e5cbbe..a6eb9565cb3b 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/index.js +++ b/packages/svelte/src/compiler/phases/2-analyze/index.js @@ -457,7 +457,7 @@ export function analyze_component(root, source, options) { }) : '', keyframes: [], - has_unscoped_global: false + has_global: false }, source, undefined_exports: new Map(), diff --git a/packages/svelte/src/compiler/phases/3-transform/css/index.js b/packages/svelte/src/compiler/phases/3-transform/css/index.js index 478c22e716ea..dff034f8aad6 100644 --- a/packages/svelte/src/compiler/phases/3-transform/css/index.js +++ b/packages/svelte/src/compiler/phases/3-transform/css/index.js @@ -60,7 +60,7 @@ export function render_stylesheet(source, analysis, options) { source: options.filename, file: options.cssOutputFilename || options.filename }), - hasGlobal: analysis.css.has_unscoped_global + hasGlobal: analysis.css.has_global }; merge_with_preprocessor_map(css, options, css.map.sources[0]); diff --git a/packages/svelte/src/compiler/phases/types.d.ts b/packages/svelte/src/compiler/phases/types.d.ts index a92bd480d33f..f98cbe141567 100644 --- a/packages/svelte/src/compiler/phases/types.d.ts +++ b/packages/svelte/src/compiler/phases/types.d.ts @@ -74,7 +74,7 @@ export interface ComponentAnalysis extends Analysis { ast: AST.CSS.StyleSheet | null; hash: string; keyframes: string[]; - has_unscoped_global: boolean; + has_global: boolean; }; source: string; undefined_exports: Map; From e8b5f686e6e2c54bc502e42f9479b00eee4f552b Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Thu, 10 Apr 2025 21:29:13 -0400 Subject: [PATCH 13/24] fix --- .../compiler/phases/2-analyze/css/css-analyze.js | 7 ++++--- .../css/samples/global-local-nested/_config.js | 5 +++++ .../css/samples/global-local-nested/expected.css | 12 ++++++++++++ .../css/samples/global-local-nested/input.svelte | 15 +++++++++++++++ .../css/samples/global-with-nesting/_config.js | 2 +- 5 files changed, 37 insertions(+), 4 deletions(-) create mode 100644 packages/svelte/tests/css/samples/global-local-nested/_config.js create mode 100644 packages/svelte/tests/css/samples/global-local-nested/expected.css create mode 100644 packages/svelte/tests/css/samples/global-local-nested/input.svelte diff --git a/packages/svelte/src/compiler/phases/2-analyze/css/css-analyze.js b/packages/svelte/src/compiler/phases/2-analyze/css/css-analyze.js index 7b1f064f91c6..79cbbb733bd0 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/css/css-analyze.js +++ b/packages/svelte/src/compiler/phases/2-analyze/css/css-analyze.js @@ -207,10 +207,11 @@ const css_visitors = { Rule(node, context) { node.metadata.parent_rule = context.state.rule; - // if this rule has a ComplexSelector whose RelativeSelector children - // are all `:global(...)` + // if this rule has a ComplexSelector whose RelativeSelector children are all + // `:global(...)`, and the rule contains declarations (rather than just + // nested rules) then the component as a whole includes global CSS context.state.has_global.value ||= - node.block.children.length > 0 && + node.block.children.filter((child) => child.type === 'Declaration').length > 0 && node.prelude.children.some((selector) => selector.children.every(is_global)) && is_unscoped_global(context.path); diff --git a/packages/svelte/tests/css/samples/global-local-nested/_config.js b/packages/svelte/tests/css/samples/global-local-nested/_config.js new file mode 100644 index 000000000000..5a7796ebac7e --- /dev/null +++ b/packages/svelte/tests/css/samples/global-local-nested/_config.js @@ -0,0 +1,5 @@ +import { test } from '../../test'; + +export default test({ + hasGlobal: false +}); diff --git a/packages/svelte/tests/css/samples/global-local-nested/expected.css b/packages/svelte/tests/css/samples/global-local-nested/expected.css new file mode 100644 index 000000000000..8eadf2b948c4 --- /dev/null +++ b/packages/svelte/tests/css/samples/global-local-nested/expected.css @@ -0,0 +1,12 @@ + + div.svelte-xyz { + .whatever { + color: green; + } + } + + .whatever { + div.svelte-xyz { + color: green; + } + } diff --git a/packages/svelte/tests/css/samples/global-local-nested/input.svelte b/packages/svelte/tests/css/samples/global-local-nested/input.svelte new file mode 100644 index 000000000000..60210be75363 --- /dev/null +++ b/packages/svelte/tests/css/samples/global-local-nested/input.svelte @@ -0,0 +1,15 @@ +
{@html whatever}
+ + diff --git a/packages/svelte/tests/css/samples/global-with-nesting/_config.js b/packages/svelte/tests/css/samples/global-with-nesting/_config.js index e8080368ccc5..6cec7c236045 100644 --- a/packages/svelte/tests/css/samples/global-with-nesting/_config.js +++ b/packages/svelte/tests/css/samples/global-with-nesting/_config.js @@ -3,5 +3,5 @@ import { test } from '../../test'; export default test({ warnings: [], - hasGlobal: true + hasGlobal: false }); From c8cb2f9a09ca775fec0a8c8c0476f4b391240538 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Thu, 10 Apr 2025 21:30:26 -0400 Subject: [PATCH 14/24] fix jsdoc --- .../svelte/src/compiler/phases/2-analyze/css/css-analyze.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/svelte/src/compiler/phases/2-analyze/css/css-analyze.js b/packages/svelte/src/compiler/phases/2-analyze/css/css-analyze.js index 79cbbb733bd0..4aebd5e2ae23 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/css/css-analyze.js +++ b/packages/svelte/src/compiler/phases/2-analyze/css/css-analyze.js @@ -10,12 +10,11 @@ import { is_global, is_unscoped_pseudo_class } from './utils.js'; * @typedef {{ * keyframes: string[]; * rule: AST.CSS.Rule | null; - * has_global: { value: boolean }; + * has_global: { value: boolean }; // need an object since state is spread * }} CssState */ /** - * We need to use an object for `has_global_unscoped` since state is spread * @typedef {Visitors} CssVisitors */ From d62a90465df098a47425c7e113b88e48ecff70cd Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Thu, 10 Apr 2025 21:32:11 -0400 Subject: [PATCH 15/24] unused --- .../src/compiler/phases/2-analyze/css/css-analyze.js | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/packages/svelte/src/compiler/phases/2-analyze/css/css-analyze.js b/packages/svelte/src/compiler/phases/2-analyze/css/css-analyze.js index 4aebd5e2ae23..52531ede9e7a 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/css/css-analyze.js +++ b/packages/svelte/src/compiler/phases/2-analyze/css/css-analyze.js @@ -32,14 +32,11 @@ function is_global_block_selector(simple_selector) { /** * @param {import('../types.js').Context["path"]} path - * @param {AST.CSS.Rule | null} [rule] * @returns */ -function is_unscoped_global(path, rule) { +function is_unscoped_global(path) { // remove every at rule or stylesheet and the current rule in case is passed in from `ComplexSelector` - const parents = path.filter( - (parent) => parent.type !== 'Atrule' && parent.type !== 'StyleSheet' && parent !== rule - ); + const parents = path.filter((parent) => parent.type !== 'Atrule' && parent.type !== 'StyleSheet'); let unscoped_global = true; From 5e2bf13407aeba19e4d03535b9953e3f81e0a484 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Thu, 10 Apr 2025 21:51:09 -0400 Subject: [PATCH 16/24] fix --- .../phases/2-analyze/css/css-analyze.js | 53 ++++++++----------- packages/svelte/src/compiler/types/css.d.ts | 4 ++ 2 files changed, 27 insertions(+), 30 deletions(-) diff --git a/packages/svelte/src/compiler/phases/2-analyze/css/css-analyze.js b/packages/svelte/src/compiler/phases/2-analyze/css/css-analyze.js index 52531ede9e7a..71a0976dbcdd 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/css/css-analyze.js +++ b/packages/svelte/src/compiler/phases/2-analyze/css/css-analyze.js @@ -36,19 +36,7 @@ function is_global_block_selector(simple_selector) { */ function is_unscoped_global(path) { // remove every at rule or stylesheet and the current rule in case is passed in from `ComplexSelector` - const parents = path.filter((parent) => parent.type !== 'Atrule' && parent.type !== 'StyleSheet'); - - let unscoped_global = true; - - // no parents means we are on top - if (parents.length > 0) { - // let's check that everything in the path is not a global block - unscoped_global = parents.every((parent) => { - return parent.type !== 'Rule' || parent.metadata.is_global_block; - }); - } - - return unscoped_global; + return path.filter((node) => node.type === 'Rule').every((node) => node.metadata.is_global); } /** @@ -203,14 +191,6 @@ const css_visitors = { Rule(node, context) { node.metadata.parent_rule = context.state.rule; - // if this rule has a ComplexSelector whose RelativeSelector children are all - // `:global(...)`, and the rule contains declarations (rather than just - // nested rules) then the component as a whole includes global CSS - context.state.has_global.value ||= - node.block.children.filter((child) => child.type === 'Declaration').length > 0 && - node.prelude.children.some((selector) => selector.children.every(is_global)) && - is_unscoped_global(context.path); - node.metadata.is_global_block = node.prelude.children.some((selector) => { let is_global_block = false; @@ -278,16 +258,29 @@ const css_visitors = { } } - context.next({ - ...context.state, - rule: node - }); + const state = { ...context.state, rule: node }; - node.metadata.has_local_selectors = node.prelude.children.some((selector) => { - return selector.children.some( - ({ metadata }) => !metadata.is_global && !metadata.is_global_like - ); - }); + // visit selector list first, to populate child selector metadata + context.visit(node.prelude, state); + + node.metadata.is_global = node.prelude.children.some((selector) => + selector.children.every(({ metadata }) => metadata.is_global || metadata.is_global_like) + ); + + node.metadata.has_local_selectors = node.prelude.children.some((selector) => + selector.children.some(({ metadata }) => !metadata.is_global && !metadata.is_global_like) + ); + + // if this rule has a ComplexSelector whose RelativeSelector children are all + // `:global(...)`, and the rule contains declarations (rather than just + // nested rules) then the component as a whole includes global CSS + context.state.has_global.value ||= + node.metadata.is_global && + node.block.children.filter((child) => child.type === 'Declaration').length > 0 && + is_unscoped_global(context.path); + + // visit block list, so parent rule metadata is populated + context.visit(node.block, state); }, NestingSelector(node, context) { const rule = /** @type {AST.CSS.Rule} */ (context.state.rule); diff --git a/packages/svelte/src/compiler/types/css.d.ts b/packages/svelte/src/compiler/types/css.d.ts index 7b2e6ae5f710..82020ea29851 100644 --- a/packages/svelte/src/compiler/types/css.d.ts +++ b/packages/svelte/src/compiler/types/css.d.ts @@ -34,6 +34,10 @@ export namespace _CSS { metadata: { parent_rule: null | Rule; has_local_selectors: boolean; + /** + * `true` if the rule contains a ComplexSelector whose RelativeSelectors are all global or global-like + */ + is_global: boolean; /** * `true` if the rule contains a `:global` selector, and therefore everything inside should be unscoped */ From be3dad10345253aaa3d2496bcca29a96de5f2984 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Thu, 10 Apr 2025 22:06:50 -0400 Subject: [PATCH 17/24] lint --- packages/svelte/src/compiler/phases/1-parse/read/style.js | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/svelte/src/compiler/phases/1-parse/read/style.js b/packages/svelte/src/compiler/phases/1-parse/read/style.js index f8c39f1b1dd1..ec430e5ea214 100644 --- a/packages/svelte/src/compiler/phases/1-parse/read/style.js +++ b/packages/svelte/src/compiler/phases/1-parse/read/style.js @@ -118,6 +118,7 @@ function read_rule(parser) { metadata: { parent_rule: null, has_local_selectors: false, + is_global: false, is_global_block: false } }; From 501783f6a817ea18f73a1860bcf415770c28b1cd Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Thu, 10 Apr 2025 22:07:57 -0400 Subject: [PATCH 18/24] rename --- packages/svelte/src/compiler/phases/1-parse/read/style.js | 2 +- .../src/compiler/phases/2-analyze/css/css-analyze.js | 8 +++++--- packages/svelte/src/compiler/types/css.d.ts | 2 +- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/packages/svelte/src/compiler/phases/1-parse/read/style.js b/packages/svelte/src/compiler/phases/1-parse/read/style.js index ec430e5ea214..a0e1483ce1ef 100644 --- a/packages/svelte/src/compiler/phases/1-parse/read/style.js +++ b/packages/svelte/src/compiler/phases/1-parse/read/style.js @@ -118,7 +118,7 @@ function read_rule(parser) { metadata: { parent_rule: null, has_local_selectors: false, - is_global: false, + has_global_selectors: false, is_global_block: false } }; diff --git a/packages/svelte/src/compiler/phases/2-analyze/css/css-analyze.js b/packages/svelte/src/compiler/phases/2-analyze/css/css-analyze.js index 71a0976dbcdd..9f8960fac7a6 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/css/css-analyze.js +++ b/packages/svelte/src/compiler/phases/2-analyze/css/css-analyze.js @@ -36,7 +36,9 @@ function is_global_block_selector(simple_selector) { */ function is_unscoped_global(path) { // remove every at rule or stylesheet and the current rule in case is passed in from `ComplexSelector` - return path.filter((node) => node.type === 'Rule').every((node) => node.metadata.is_global); + return path + .filter((node) => node.type === 'Rule') + .every((node) => node.metadata.has_global_selectors); } /** @@ -263,7 +265,7 @@ const css_visitors = { // visit selector list first, to populate child selector metadata context.visit(node.prelude, state); - node.metadata.is_global = node.prelude.children.some((selector) => + node.metadata.has_global_selectors = node.prelude.children.some((selector) => selector.children.every(({ metadata }) => metadata.is_global || metadata.is_global_like) ); @@ -275,7 +277,7 @@ const css_visitors = { // `:global(...)`, and the rule contains declarations (rather than just // nested rules) then the component as a whole includes global CSS context.state.has_global.value ||= - node.metadata.is_global && + node.metadata.has_global_selectors && node.block.children.filter((child) => child.type === 'Declaration').length > 0 && is_unscoped_global(context.path); diff --git a/packages/svelte/src/compiler/types/css.d.ts b/packages/svelte/src/compiler/types/css.d.ts index 82020ea29851..4e0e9c30c931 100644 --- a/packages/svelte/src/compiler/types/css.d.ts +++ b/packages/svelte/src/compiler/types/css.d.ts @@ -37,7 +37,7 @@ export namespace _CSS { /** * `true` if the rule contains a ComplexSelector whose RelativeSelectors are all global or global-like */ - is_global: boolean; + has_global_selectors: boolean; /** * `true` if the rule contains a `:global` selector, and therefore everything inside should be unscoped */ From dc4330c791a741caf6b2de899886752f7220a145 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Thu, 10 Apr 2025 22:08:51 -0400 Subject: [PATCH 19/24] rename --- .../svelte/src/compiler/phases/2-analyze/css/css-analyze.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/svelte/src/compiler/phases/2-analyze/css/css-analyze.js b/packages/svelte/src/compiler/phases/2-analyze/css/css-analyze.js index 9f8960fac7a6..97e2d4458c7d 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/css/css-analyze.js +++ b/packages/svelte/src/compiler/phases/2-analyze/css/css-analyze.js @@ -34,7 +34,7 @@ function is_global_block_selector(simple_selector) { * @param {import('../types.js').Context["path"]} path * @returns */ -function is_unscoped_global(path) { +function is_unscoped(path) { // remove every at rule or stylesheet and the current rule in case is passed in from `ComplexSelector` return path .filter((node) => node.type === 'Rule') @@ -57,7 +57,7 @@ const css_visitors = { context.state.keyframes.push(node.prelude); } else if (node.prelude.startsWith('-global-')) { // we don't check if the block.children.length because the keyframe is still added even if empty - context.state.has_global.value ||= is_unscoped_global(context.path); + context.state.has_global.value ||= is_unscoped(context.path); } } @@ -279,7 +279,7 @@ const css_visitors = { context.state.has_global.value ||= node.metadata.has_global_selectors && node.block.children.filter((child) => child.type === 'Declaration').length > 0 && - is_unscoped_global(context.path); + is_unscoped(context.path); // visit block list, so parent rule metadata is populated context.visit(node.block, state); From 76371f7e1110cc03c4c50335e56f8be8bc89f2ff Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Thu, 10 Apr 2025 22:18:23 -0400 Subject: [PATCH 20/24] reduce indirection --- .../src/compiler/phases/2-analyze/css/css-analyze.js | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/packages/svelte/src/compiler/phases/2-analyze/css/css-analyze.js b/packages/svelte/src/compiler/phases/2-analyze/css/css-analyze.js index 97e2d4458c7d..000eef98e07d 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/css/css-analyze.js +++ b/packages/svelte/src/compiler/phases/2-analyze/css/css-analyze.js @@ -10,7 +10,7 @@ import { is_global, is_unscoped_pseudo_class } from './utils.js'; * @typedef {{ * keyframes: string[]; * rule: AST.CSS.Rule | null; - * has_global: { value: boolean }; // need an object since state is spread + * analysis: ComponentAnalysis; * }} CssState */ @@ -57,7 +57,7 @@ const css_visitors = { context.state.keyframes.push(node.prelude); } else if (node.prelude.startsWith('-global-')) { // we don't check if the block.children.length because the keyframe is still added even if empty - context.state.has_global.value ||= is_unscoped(context.path); + context.state.analysis.css.has_global ||= is_unscoped(context.path); } } @@ -276,7 +276,7 @@ const css_visitors = { // if this rule has a ComplexSelector whose RelativeSelector children are all // `:global(...)`, and the rule contains declarations (rather than just // nested rules) then the component as a whole includes global CSS - context.state.has_global.value ||= + context.state.analysis.css.has_global ||= node.metadata.has_global_selectors && node.block.children.filter((child) => child.type === 'Declaration').length > 0 && is_unscoped(context.path); @@ -324,10 +324,8 @@ export function analyze_css(stylesheet, analysis) { const css_state = { keyframes: analysis.css.keyframes, rule: null, - // we need to use an object since state is spread - has_global: { value: false } + analysis }; walk(stylesheet, css_state, css_visitors); - analysis.css.has_global = css_state.has_global.value; } From aad25090766bd5d9544e21a7799da2678caa1695 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Thu, 10 Apr 2025 22:19:25 -0400 Subject: [PATCH 21/24] tidy up --- .../svelte/src/compiler/phases/2-analyze/css/css-analyze.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/svelte/src/compiler/phases/2-analyze/css/css-analyze.js b/packages/svelte/src/compiler/phases/2-analyze/css/css-analyze.js index 000eef98e07d..ae862114e715 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/css/css-analyze.js +++ b/packages/svelte/src/compiler/phases/2-analyze/css/css-analyze.js @@ -31,11 +31,9 @@ function is_global_block_selector(simple_selector) { } /** - * @param {import('../types.js').Context["path"]} path - * @returns + * @param {AST.SvelteNode[]} path */ function is_unscoped(path) { - // remove every at rule or stylesheet and the current rule in case is passed in from `ComplexSelector` return path .filter((node) => node.type === 'Rule') .every((node) => node.metadata.has_global_selectors); From 77befcfb7580d0d410e36a65af800ff4f3289cbd Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Thu, 10 Apr 2025 22:21:34 -0400 Subject: [PATCH 22/24] revert --- .../svelte/src/compiler/phases/2-analyze/css/css-analyze.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/svelte/src/compiler/phases/2-analyze/css/css-analyze.js b/packages/svelte/src/compiler/phases/2-analyze/css/css-analyze.js index ae862114e715..aeeaa6d64c50 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/css/css-analyze.js +++ b/packages/svelte/src/compiler/phases/2-analyze/css/css-analyze.js @@ -194,8 +194,7 @@ const css_visitors = { node.metadata.is_global_block = node.prelude.children.some((selector) => { let is_global_block = false; - for (let i = 0; i < selector.children.length; i++) { - const child = selector.children[i]; + for (const child of selector.children) { const idx = child.selectors.findIndex(is_global_block_selector); if (is_global_block) { From 227729e01621f02618704a4c584d665deb987c9a Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Thu, 10 Apr 2025 22:31:32 -0400 Subject: [PATCH 23/24] tweak --- .../compiler/phases/2-analyze/css/css-analyze.js | 15 +++++++-------- packages/svelte/src/compiler/types/css.d.ts | 1 + 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/svelte/src/compiler/phases/2-analyze/css/css-analyze.js b/packages/svelte/src/compiler/phases/2-analyze/css/css-analyze.js index aeeaa6d64c50..76cb2f56e995 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/css/css-analyze.js +++ b/packages/svelte/src/compiler/phases/2-analyze/css/css-analyze.js @@ -113,10 +113,12 @@ const css_visitors = { node.metadata.rule = context.state.rule; - node.metadata.used ||= node.children.every( + node.metadata.is_global = node.children.every( ({ metadata }) => metadata.is_global || metadata.is_global_like ); + node.metadata.used ||= node.metadata.is_global; + if ( node.metadata.rule?.metadata.parent_rule && node.children[0]?.selectors[0]?.type === 'NestingSelector' @@ -262,13 +264,10 @@ const css_visitors = { // visit selector list first, to populate child selector metadata context.visit(node.prelude, state); - node.metadata.has_global_selectors = node.prelude.children.some((selector) => - selector.children.every(({ metadata }) => metadata.is_global || metadata.is_global_like) - ); - - node.metadata.has_local_selectors = node.prelude.children.some((selector) => - selector.children.some(({ metadata }) => !metadata.is_global && !metadata.is_global_like) - ); + for (const selector of node.prelude.children) { + node.metadata.has_global_selectors ||= selector.metadata.is_global; + node.metadata.has_local_selectors ||= !selector.metadata.is_global; + } // if this rule has a ComplexSelector whose RelativeSelector children are all // `:global(...)`, and the rule contains declarations (rather than just diff --git a/packages/svelte/src/compiler/types/css.d.ts b/packages/svelte/src/compiler/types/css.d.ts index 4e0e9c30c931..154a06ffb11c 100644 --- a/packages/svelte/src/compiler/types/css.d.ts +++ b/packages/svelte/src/compiler/types/css.d.ts @@ -68,6 +68,7 @@ export namespace _CSS { /** @internal */ metadata: { rule: null | Rule; + is_global: boolean; /** True if this selector applies to an element. For global selectors, this is defined in css-analyze, for others in css-prune while scoping */ used: boolean; }; From 67ddc8c799a62374eba8d53f6af2deb559541da3 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Thu, 10 Apr 2025 22:34:25 -0400 Subject: [PATCH 24/24] lint --- packages/svelte/src/compiler/phases/1-parse/read/style.js | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/svelte/src/compiler/phases/1-parse/read/style.js b/packages/svelte/src/compiler/phases/1-parse/read/style.js index a0e1483ce1ef..56dbe124b7bf 100644 --- a/packages/svelte/src/compiler/phases/1-parse/read/style.js +++ b/packages/svelte/src/compiler/phases/1-parse/read/style.js @@ -343,6 +343,7 @@ function read_selector(parser, inside_pseudo_class = false) { children, metadata: { rule: null, + is_global: false, used: false } };