Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/plenty-hotels-mix.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'svelte': minor
---

feat: add `hasUnscopedGlobalCss` to `compile` metadata
53 changes: 51 additions & 2 deletions packages/svelte/src/compiler/phases/2-analyze/css/css-analyze.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
*/
Expand All @@ -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<AST.CSS.Node>} path
Expand Down Expand Up @@ -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;
}
}
}
}

Expand Down Expand Up @@ -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) {
Expand All @@ -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) {
Expand Down Expand Up @@ -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;
}
3 changes: 2 additions & 1 deletion packages/svelte/src/compiler/phases/2-analyze/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -455,7 +455,8 @@ export function analyze_component(root, source, options) {
hash
})
: '',
keyframes: []
keyframes: [],
has_global_unscoped: false
},
source,
undefined_exports: new Map(),
Expand Down
12 changes: 8 additions & 4 deletions packages/svelte/src/compiler/phases/3-transform/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
};
Expand Down Expand Up @@ -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
};
Expand All @@ -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
};
Expand Down Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions packages/svelte/src/compiler/phases/types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<string, Node>;
Expand Down
5 changes: 5 additions & 0 deletions packages/svelte/src/compiler/types/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
5 changes: 5 additions & 0 deletions packages/svelte/types/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down