feat: allow `:global` in more places (alternative) (#12560)

* `div { :global { &.x { ... } } }` is equivalent to `div:global.x { ... }`, so the latter should be allowed, too

* finalize

* replace obsolete breaking change (which turned out to be a wrong change and was since reverted), add new breaking change note

* changeset

* regenerate types

* Update sites/svelte-5-preview/src/routes/docs/content/03-appendix/02-breaking-changes.md

Co-authored-by: Rich Harris <rich.harris@vercel.com>

* always remove descendant selector before global

* error on lone `:global` with nested `&`, revert "remove spaces" rule

* regenerate types

* documentation

* oops

* switch to removing descendant combinator

* fix

* revert combinator validation relaxation

* error on first global being modified

* tweak docs

* tweak error messages

* Update documentation/docs/02-template-syntax/05-styles-and-classes.md

Co-authored-by: Rich Harris <rich.harris@vercel.com>

* clarify

* tweak messages

* update tests

* tweak docs

* tweak `:global(...)` docs

* tweak docs

---------

Co-authored-by: Rich Harris <rich.harris@vercel.com>
pull/12609/head
Simon H 2 months ago committed by GitHub
parent e2f17c8a7b
commit 13d86e9019
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -0,0 +1,5 @@
---
'svelte': patch
---
feat: allow `:global` in more places

@ -25,31 +25,28 @@ This works by adding a class to affected elements, which is based on a hash of t
</style>
```
## :global
## :global(...)
To apply styles to a selector globally, use the `:global(...)` modifier.
To apply styles to a single selector globally, use the `:global(...)` modifier:
```svelte
<style>
:global(body) {
/* this will apply to <body> */
/* applies to <body> */
margin: 0;
}
div :global(strong) {
/* this will apply to all <strong> elements, in any
component, that are inside <div> elements belonging
/* applies to all <strong> elements, in any component,
that are inside <div> elements belonging
to this component */
color: goldenrod;
}
p:global(.red) {
/* this will apply to all <p> elements belonging to this
component with a class of red, even if class="red" does
not initially appear in the markup, and is instead
added at runtime. This is useful when the class
of the element is dynamically applied, for instance
when updating the element's classList property directly. */
p:global(.big.red) {
/* applies to all <p> elements belonging to this component
with `class="big red"`, even if it is applied
programmatically (for example by a library) */
}
</style>
```
@ -66,6 +63,30 @@ The `-global-` part will be removed when compiled, and the keyframe will then be
</style>
```
## :global
To apply styles to a group of selectors globally, create a `:global {...}` block:
```svelte
<style>
:global {
/* applies to every <div> in your application */
div { ... }
/* applies to every <p> in your application */
p { ... }
}
.a :global {
/* applies to every `.b .c .d` element, in any component,
that is inside an `.a` element in this component */
.b .c .d {...}
}
</style>
```
> The second example above could also be written as an equivalent `.a :global .b .c .d` selector, where everything after the `:global` is unscoped, though the nested form is preferred.
## Nested style tags
There should only be 1 top-level `<style>` tag per component.

@ -8,35 +8,35 @@
## css_global_block_invalid_combinator
> A :global {...} block cannot follow a %name% combinator
> A `:global` selector cannot follow a `%name%` combinator
## css_global_block_invalid_declaration
> A :global {...} block can only contain rules, not declarations
> A top-level `:global {...}` block can only contain rules, not declarations
## css_global_block_invalid_list
> A :global {...} block cannot be part of a selector list with more than one item
> A `:global` selector cannot be part of a selector list with more than one item
## css_global_block_invalid_modifier
> A :global {...} block cannot modify an existing selector
> A `:global` selector cannot modify an existing selector
## css_global_block_invalid_placement
## css_global_block_invalid_modifier_start
> A :global {...} block can only appear at the end of a selector sequence (did you mean to use :global(...) instead?)
> A `:global` selector can only be modified if it is a descendant of other selectors
## css_global_invalid_placement
> :global(...) can be at the start or end of a selector sequence, but not in the middle
> `:global(...)` can be at the start or end of a selector sequence, but not in the middle
## css_global_invalid_selector
> :global(...) must contain exactly one selector
> `:global(...)` must contain exactly one selector
## css_global_invalid_selector_list
> :global(...) must not contain type or universal selectors when used in a compound selector
> `:global(...)` must not contain type or universal selectors when used in a compound selector
## css_nesting_selector_invalid_placement
@ -48,4 +48,4 @@
## css_type_selector_invalid_placement
> :global(...) must not be followed with a type selector
> `:global(...)` must not be followed by a type selector

@ -434,76 +434,76 @@ export function css_expected_identifier(node) {
}
/**
* A :global {...} block cannot follow a %name% combinator
* A `:global` selector cannot follow a `%name%` combinator
* @param {null | number | NodeLike} node
* @param {string} name
* @returns {never}
*/
export function css_global_block_invalid_combinator(node, name) {
e(node, "css_global_block_invalid_combinator", `A :global {...} block cannot follow a ${name} combinator`);
e(node, "css_global_block_invalid_combinator", `A \`:global\` selector cannot follow a \`${name}\` combinator`);
}
/**
* A :global {...} block can only contain rules, not declarations
* A top-level `:global {...}` block can only contain rules, not declarations
* @param {null | number | NodeLike} node
* @returns {never}
*/
export function css_global_block_invalid_declaration(node) {
e(node, "css_global_block_invalid_declaration", "A :global {...} block can only contain rules, not declarations");
e(node, "css_global_block_invalid_declaration", "A top-level `:global {...}` block can only contain rules, not declarations");
}
/**
* A :global {...} block cannot be part of a selector list with more than one item
* A `:global` selector cannot be part of a selector list with more than one item
* @param {null | number | NodeLike} node
* @returns {never}
*/
export function css_global_block_invalid_list(node) {
e(node, "css_global_block_invalid_list", "A :global {...} block cannot be part of a selector list with more than one item");
e(node, "css_global_block_invalid_list", "A `:global` selector cannot be part of a selector list with more than one item");
}
/**
* A :global {...} block cannot modify an existing selector
* A `:global` selector cannot modify an existing selector
* @param {null | number | NodeLike} node
* @returns {never}
*/
export function css_global_block_invalid_modifier(node) {
e(node, "css_global_block_invalid_modifier", "A :global {...} block cannot modify an existing selector");
e(node, "css_global_block_invalid_modifier", "A `:global` selector cannot modify an existing selector");
}
/**
* A :global {...} block can only appear at the end of a selector sequence (did you mean to use :global(...) instead?)
* A `:global` selector can only be modified if it is a descendant of other selectors
* @param {null | number | NodeLike} node
* @returns {never}
*/
export function css_global_block_invalid_placement(node) {
e(node, "css_global_block_invalid_placement", "A :global {...} block can only appear at the end of a selector sequence (did you mean to use :global(...) instead?)");
export function css_global_block_invalid_modifier_start(node) {
e(node, "css_global_block_invalid_modifier_start", "A `:global` selector can only be modified if it is a descendant of other selectors");
}
/**
* :global(...) can be at the start or end of a selector sequence, but not in the middle
* `:global(...)` can be at the start or end of a selector sequence, but not in the middle
* @param {null | number | NodeLike} node
* @returns {never}
*/
export function css_global_invalid_placement(node) {
e(node, "css_global_invalid_placement", ":global(...) can be at the start or end of a selector sequence, but not in the middle");
e(node, "css_global_invalid_placement", "`:global(...)` can be at the start or end of a selector sequence, but not in the middle");
}
/**
* :global(...) must contain exactly one selector
* `:global(...)` must contain exactly one selector
* @param {null | number | NodeLike} node
* @returns {never}
*/
export function css_global_invalid_selector(node) {
e(node, "css_global_invalid_selector", ":global(...) must contain exactly one selector");
e(node, "css_global_invalid_selector", "`:global(...)` must contain exactly one selector");
}
/**
* :global(...) must not contain type or universal selectors when used in a compound selector
* `:global(...)` must not contain type or universal selectors when used in a compound selector
* @param {null | number | NodeLike} node
* @returns {never}
*/
export function css_global_invalid_selector_list(node) {
e(node, "css_global_invalid_selector_list", ":global(...) must not contain type or universal selectors when used in a compound selector");
e(node, "css_global_invalid_selector_list", "`:global(...)` must not contain type or universal selectors when used in a compound selector");
}
/**
@ -525,12 +525,12 @@ export function css_selector_invalid(node) {
}
/**
* :global(...) must not be followed with a type selector
* `:global(...)` must not be followed by a type selector
* @param {null | number | NodeLike} node
* @returns {never}
*/
export function css_type_selector_invalid_placement(node) {
e(node, "css_type_selector_invalid_placement", ":global(...) must not be followed with a type selector");
e(node, "css_type_selector_invalid_placement", "`:global(...)` must not be followed by a type selector");
}
/**

@ -27,10 +27,25 @@ function is_global(relative_selector) {
return (
first.type === 'PseudoClassSelector' &&
first.name === 'global' &&
(first.args === null ||
// Only these two selector types keep the whole selector global, because e.g.
// :global(button).x means that the selector is still scoped because of the .x
relative_selector.selectors.every(
(selector) =>
selector.type === 'PseudoClassSelector' || selector.type === 'PseudoElementSelector'
)
))
);
}
/**
* True if is `:global`
* @param {Css.SimpleSelector} simple_selector
*/
function is_global_block_selector(simple_selector) {
return (
simple_selector.type === 'PseudoClassSelector' &&
simple_selector.name === 'global' &&
simple_selector.args === null
);
}
@ -48,19 +63,12 @@ const analysis_visitors = {
node.metadata.rule = context.state.rule;
node.metadata.used = node.children.every(
node.metadata.used ||= node.children.every(
({ metadata }) => metadata.is_global || metadata.is_global_like
);
},
RelativeSelector(node, context) {
node.metadata.is_global =
node.selectors.length >= 1 &&
node.selectors[0].type === 'PseudoClassSelector' &&
node.selectors[0].name === 'global' &&
node.selectors.every(
(selector) =>
selector.type === 'PseudoClassSelector' || selector.type === 'PseudoElementSelector'
);
node.metadata.is_global = node.selectors.length >= 1 && is_global(node);
if (node.selectors.length === 1) {
const first = node.selectors[0];
@ -85,16 +93,31 @@ const analysis_visitors = {
Rule(node, context) {
node.metadata.parent_rule = context.state.rule;
// `:global {...}` or `div :global {...}`
node.metadata.is_global_block = node.prelude.children.some((selector) => {
const last = selector.children[selector.children.length - 1];
let is_global_block = false;
const s = last.selectors[last.selectors.length - 1];
for (const child of selector.children) {
const idx = child.selectors.findIndex(is_global_block_selector);
if (s.type === 'PseudoClassSelector' && s.name === 'global' && s.args === null) {
return true;
if (is_global_block) {
// All selectors after :global are unscoped
child.metadata.is_global_like = true;
}
if (idx !== -1) {
is_global_block = true;
for (let i = idx + 1; i < child.selectors.length; i++) {
walk(/** @type {Css.Node} */ (child.selectors[i]), null, {
ComplexSelector(node) {
node.metadata.used = true;
}
});
}
}
}
return is_global_block;
});
context.next({
...context.state,
@ -118,21 +141,35 @@ const validation_visitors = {
}
const complex_selector = node.prelude.children[0];
const relative_selector = complex_selector.children[complex_selector.children.length - 1];
const global_selector = complex_selector.children.find((r, selector_idx) => {
const idx = r.selectors.findIndex(is_global_block_selector);
if (idx === 0) {
if (r.selectors.length > 1 && selector_idx === 0 && node.metadata.parent_rule === null) {
e.css_global_block_invalid_modifier_start(r.selectors[1]);
}
return true;
} else if (idx !== -1) {
e.css_global_block_invalid_modifier(r.selectors[idx]);
}
});
if (relative_selector.selectors.length > 1) {
e.css_global_block_invalid_modifier(
relative_selector.selectors[relative_selector.selectors.length - 1]
);
if (!global_selector) {
throw new Error('Internal error: global block without :global selector');
}
if (relative_selector.combinator && relative_selector.combinator.name !== ' ') {
e.css_global_block_invalid_combinator(relative_selector, relative_selector.combinator.name);
if (global_selector.combinator && global_selector.combinator.name !== ' ') {
e.css_global_block_invalid_combinator(global_selector, global_selector.combinator.name);
}
const declaration = node.block.children.find((child) => child.type === 'Declaration');
if (declaration) {
if (
declaration &&
// :global { color: red; } is invalid, but foo :global { color: red; } is valid
node.prelude.children.length === 1 &&
node.prelude.children[0].children.length === 1 &&
node.prelude.children[0].children[0].selectors.length === 1
) {
e.css_global_block_invalid_declaration(declaration);
}
}
@ -146,14 +183,7 @@ const validation_visitors = {
if (global) {
const idx = node.children.indexOf(global);
if (global.selectors[0].args === null && idx !== node.children.length - 1) {
// ensure `:global` is only at the end of a selector
e.css_global_block_invalid_placement(global.selectors[0]);
} else if (
global.selectors[0].args !== null &&
idx !== 0 &&
idx !== node.children.length - 1
) {
if (global.selectors[0].args !== null && idx !== 0 && idx !== node.children.length - 1) {
// ensure `:global(...)` is not used in the middle of a selector (but multiple `global(...)` in sequence are ok)
for (let i = idx + 1; i < node.children.length; i++) {
if (!is_global(node.children[i])) {
@ -196,8 +226,17 @@ const validation_visitors = {
},
NestingSelector(node, context) {
const rule = /** @type {Css.Rule} */ (context.state.rule);
if (!rule.metadata.parent_rule) {
const parent_rule = rule.metadata.parent_rule;
if (!parent_rule) {
e.css_nesting_selector_invalid_placement(node);
} else if (
// :global { &.foo { ... } } is invalid
parent_rule.metadata.is_global_block &&
!parent_rule.metadata.parent_rule &&
parent_rule.prelude.children[0].children.length === 1 &&
parent_rule.prelude.children[0].children[0].selectors.length === 1
) {
e.css_global_block_invalid_modifier_start(node);
}
}
};

@ -295,12 +295,19 @@ function relative_selector_might_apply_to_node(relative_selector, rule, element,
return false;
}
if (name === 'global' && relative_selector.selectors.length === 1) {
const args = /** @type {Compiler.Css.SelectorList} */ (selector.args);
if (
name === 'global' &&
selector.args !== null &&
relative_selector.selectors.length === 1
) {
const args = selector.args;
const complex_selector = args.children[0];
return apply_selector(complex_selector.children, rule, element, stylesheet);
}
// We came across a :global, everything beyond it is global and therefore a potential match
if (name === 'global' && selector.args === null) return true;
if ((name === 'is' || name === 'where') && selector.args) {
let matched = false;

@ -92,7 +92,7 @@ const visitors = {
next();
},
Declaration(node, { state, next }) {
Declaration(node, { state }) {
const property = node.property && remove_css_prefix(node.property.toLowerCase());
if (property === 'animation' || property === 'animation-name') {
let index = node.start + node.property.length + 1;
@ -140,7 +140,7 @@ const visitors = {
if (node.metadata.is_global_block) {
const selector = node.prelude.children[0];
if (selector.children.length === 1) {
if (selector.children.length === 1 && selector.children[0].selectors.length === 1) {
// `:global {...}`
state.code.prependRight(node.start, '/* ');
state.code.appendLeft(node.block.start + 1, '*/');
@ -216,16 +216,38 @@ const visitors = {
ComplexSelector(node, context) {
const before_bumped = context.state.specificity.bumped;
/** @param {Css.SimpleSelector} selector */
function remove_global_pseudo_class(selector) {
/**
* @param {Css.PseudoClassSelector} selector
* @param {Css.Combinator | null} combinator
*/
function remove_global_pseudo_class(selector, combinator) {
if (selector.args === null) {
let start = selector.start;
if (combinator?.name === ' ') {
// div :global.x becomes div.x
while (/\s/.test(context.state.code.original[start - 1])) start--;
}
context.state.code.remove(start, selector.start + ':global'.length);
} else {
context.state.code
.remove(selector.start, selector.start + ':global('.length)
.remove(selector.end - 1, selector.end);
}
}
for (const relative_selector of node.children) {
if (relative_selector.metadata.is_global) {
remove_global_pseudo_class(relative_selector.selectors[0]);
const global = /** @type {Css.PseudoClassSelector} */ (relative_selector.selectors[0]);
remove_global_pseudo_class(global, relative_selector.combinator);
if (
node.metadata.rule?.metadata.parent_rule &&
global.args === null &&
relative_selector.combinator === null
) {
// div { :global.x { ... } } becomes div { &.x { ... } }
context.state.code.prependRight(global.start, '&');
}
continue;
}
@ -241,10 +263,10 @@ const visitors = {
}
}
// for any :global() at the middle of compound selector
// for any :global() or :global at the middle of compound selector
for (const selector of relative_selector.selectors) {
if (selector.type === 'PseudoClassSelector' && selector.name === 'global') {
remove_global_pseudo_class(selector);
remove_global_pseudo_class(selector, null);
}
}

@ -33,17 +33,32 @@ export namespace Css {
metadata: {
parent_rule: null | Rule;
has_local_selectors: boolean;
/**
* `true` if the rule contains a `:global` selector, and therefore everything inside should be unscoped
*/
is_global_block: boolean;
};
}
/**
* A list of selectors, e.g. `a, b, c {}`
*/
export interface SelectorList extends BaseNode {
type: 'SelectorList';
/**
* The `a`, `b` and `c` in `a, b, c {}`
*/
children: ComplexSelector[];
}
/**
* A complex selector, e.g. `a b c {}`
*/
export interface ComplexSelector extends BaseNode {
type: 'ComplexSelector';
/**
* The `a`, `b` and `c` in `a b c {}`
*/
children: RelativeSelector[];
metadata: {
rule: null | Rule;
@ -51,14 +66,26 @@ export namespace Css {
};
}
/**
* A relative selector, e.g the `a` and `> b` in `a > b {}`
*/
export interface RelativeSelector extends BaseNode {
type: 'RelativeSelector';
/**
* In `a > b`, `> b` forms one relative selector, and `>` is the combinator. `null` for the first selector.
*/
combinator: null | Combinator;
/**
* The `b:is(...)` in `> b:is(...)`
*/
selectors: SimpleSelector[];
metadata: {
/** :global(..) */
/**
* `true` if the whole selector is unscoped, e.g. `:global(...)` or `:global` or `:global.x`.
* Selectors like `:global(...).x` are not considered global, because they still need scoping.
*/
is_global: boolean;
/** :root, :host, ::view-transition */
/** `:root`, `:host`, `::view-transition`, or selectors after a `:global` */
is_global_like: boolean;
scoped: boolean;
};

@ -0,0 +1,9 @@
import { test } from '../../test';
export default test({
error: {
code: 'css_global_block_invalid_combinator',
message: 'A `:global` selector cannot follow a `>` combinator',
position: [79, 88]
}
});

@ -0,0 +1,11 @@
<style>
/* ok */
.x :global.x p {
}
.x :global > p {
}
/* not ok */
.x > :global p {
}
</style>

@ -3,7 +3,7 @@ import { test } from '../../test';
export default test({
error: {
code: 'css_global_block_invalid_combinator',
message: 'A :global {...} block cannot follow a > combinator',
position: [12, 21]
message: 'A `:global` selector cannot follow a `>` combinator',
position: [54, 63]
}
});

@ -1,3 +1,9 @@
<style>
.x > :global {}
/* ok */
.x :global {
}
/* not ok */
.x > :global {
}
</style>

@ -3,7 +3,7 @@ import { test } from '../../test';
export default test({
error: {
code: 'css_global_block_invalid_declaration',
message: 'A :global {...} block can only contain rules, not declarations',
position: [24, 34]
message: 'A top-level `:global {...}` block can only contain rules, not declarations',
position: [109, 119]
}
});

@ -1,5 +1,15 @@
<style>
/* ok */
.x :global {
color: red;
}
:global .y {
color: red;
}
/* not ok */
:global {
color: red;
}
</style>

@ -1,10 +0,0 @@
import { test } from '../../test';
export default test({
error: {
code: 'css_global_block_invalid_placement',
message:
'A :global {...} block can only appear at the end of a selector sequence (did you mean to use :global(...) instead?)',
position: [50, 57]
}
});

@ -1,8 +0,0 @@
<style>
/* ok */
.x :global {
}
/* not ok */
:global .x {
}
</style>

@ -3,7 +3,7 @@ import { test } from '../../test';
export default test({
error: {
code: 'css_global_block_invalid_list',
message: 'A :global {...} block cannot be part of a selector list with more than one item',
message: 'A `:global` selector cannot be part of a selector list with more than one item',
position: [9, 31]
}
});

@ -0,0 +1,9 @@
import { test } from '../../test';
export default test({
error: {
code: 'css_global_block_invalid_modifier_start',
message: 'A `:global` selector can only be modified if it is a descendant of other selectors',
position: [75, 77]
}
});

@ -0,0 +1,11 @@
<style>
/* ok */
div :global.x {
color: red;
}
/* not ok */
:global.x {
color: red;
}
</style>

@ -0,0 +1,9 @@
import { test } from '../../test';
export default test({
error: {
code: 'css_global_block_invalid_modifier_start',
message: 'A `:global` selector can only be modified if it is a descendant of other selectors',
position: [147, 148]
}
});

@ -0,0 +1,22 @@
<style>
/* ok */
div :global {
&.x {
color: red;
}
}
div {
:global {
&.x {
color: red;
}
}
}
/* not ok */
:global {
&.x {
color: red;
}
}
</style>

@ -3,7 +3,7 @@ import { test } from '../../test';
export default test({
error: {
code: 'css_global_block_invalid_modifier',
message: 'A :global {...} block cannot modify an existing selector',
position: [14, 21]
message: 'A `:global` selector cannot modify an existing selector',
position: [70, 77]
}
});

@ -0,0 +1,11 @@
<style>
/* ok */
div :global.x {
color: red;
}
/* not ok */
.x:global {
color: red;
}
</style>

@ -7,14 +7,14 @@ export default test({
code: 'css_unused_selector',
message: 'Unused CSS selector ".unused :global"',
start: {
line: 16,
line: 69,
column: 1,
character: 128
character: 917
},
end: {
line: 16,
line: 69,
column: 16,
character: 143
character: 932
}
}
]

@ -1,3 +1,4 @@
/* :global {*/
.x {
color: green;
@ -10,6 +11,59 @@
}
}
/* some css preprocessors de-nest :global blocks with a single child
(e.g turn `:global { div { ... } }` into `:global div { ... }`),
so we need to support it, too */
div {
.y {
color: green;
}
}
div.svelte-xyz p {
.y {
color: green;
}
}
/* `div { :global { &.x { ...} } }` is allowed ... */
div.svelte-xyz {
/* :global {*/
&.x {
color: green;
}
/*}*/
}
/* ...wich is equivalent to `div :global { &.x { ...} }` ... */
div.svelte-xyz {
&.x {
color: green;
}
}
/* ...so `div :global.x` must be, too ... */
div.svelte-xyz.x {
color: green;
}
/* ...and therefore `div { :global.x { ... }` aswell */
div.svelte-xyz {
&.x {
color: green;
}
}
div.svelte-xyz {
&.x {
color: green;
}
}
div.svelte-xyz:is(html.dark-mode *) {
color: green;
}
/* (unused) .unused :global {
.z {
color: red;

@ -13,6 +13,59 @@
}
}
/* some css preprocessors de-nest :global blocks with a single child
(e.g turn `:global { div { ... } }` into `:global div { ... }`),
so we need to support it, too */
:global div {
.y {
color: green;
}
}
div :global p {
.y {
color: green;
}
}
/* `div { :global { &.x { ...} } }` is allowed ... */
div {
:global {
&.x {
color: green;
}
}
}
/* ...wich is equivalent to `div :global { &.x { ...} }` ... */
div :global {
&.x {
color: green;
}
}
/* ...so `div :global.x` must be, too ... */
div :global.x {
color: green;
}
/* ...and therefore `div { :global.x { ... }` aswell */
div {
:global.x {
color: green;
}
}
div {
& :global.x {
color: green;
}
}
div :global:is(html.dark-mode *) {
color: green;
}
.unused :global {
.z {
color: red;

@ -1,7 +1,7 @@
[
{
"code": "css_global_invalid_placement",
"message": ":global(...) can be at the start or end of a selector sequence, but not in the middle",
"message": "`:global(...)` can be at the start or end of a selector sequence, but not in the middle",
"start": {
"line": 8,
"column": 6

@ -1,7 +1,7 @@
[
{
"code": "css_global_invalid_placement",
"message": ":global(...) can be at the start or end of a selector sequence, but not in the middle",
"message": "`:global(...)` can be at the start or end of a selector sequence, but not in the middle",
"start": {
"line": 5,
"column": 6

@ -1,7 +1,7 @@
[
{
"code": "css_global_invalid_selector_list",
"message": ":global(...) must not contain type or universal selectors when used in a compound selector",
"message": "`:global(...)` must not contain type or universal selectors when used in a compound selector",
"start": {
"line": 2,
"column": 5

@ -1,7 +1,7 @@
[
{
"code": "css_global_invalid_selector_list",
"message": ":global(...) must not contain type or universal selectors when used in a compound selector",
"message": "`:global(...)` must not contain type or universal selectors when used in a compound selector",
"start": {
"line": 2,
"column": 5

@ -1,7 +1,7 @@
[
{
"code": "css_global_invalid_selector_list",
"message": ":global(...) must not contain type or universal selectors when used in a compound selector",
"message": "`:global(...)` must not contain type or universal selectors when used in a compound selector",
"start": {
"line": 5,
"column": 5

@ -1,7 +1,7 @@
[
{
"code": "css_global_invalid_placement",
"message": ":global(...) can be at the start or end of a selector sequence, but not in the middle",
"message": "`:global(...)` can be at the start or end of a selector sequence, but not in the middle",
"start": {
"line": 2,
"column": 6

@ -1,7 +1,7 @@
[
{
"code": "css_global_invalid_selector",
"message": ":global(...) must contain exactly one selector",
"message": "`:global(...)` must contain exactly one selector",
"start": {
"line": 11,
"column": 5

@ -1,7 +1,7 @@
[
{
"code": "css_global_invalid_selector",
"message": ":global(...) must contain exactly one selector",
"message": "`:global(...)` must contain exactly one selector",
"start": {
"line": 5,
"column": 5

@ -1,7 +1,7 @@
[
{
"code": "css_global_invalid_selector",
"message": ":global(...) must contain exactly one selector",
"message": "`:global(...)` must contain exactly one selector",
"start": {
"line": 2,
"column": 5

@ -1,7 +1,7 @@
[
{
"code": "css_global_invalid_selector",
"message": ":global(...) must contain exactly one selector",
"message": "`:global(...)` must contain exactly one selector",
"start": {
"line": 5,
"column": 1

@ -1,7 +1,7 @@
[
{
"code": "css_global_invalid_selector",
"message": ":global(...) must contain exactly one selector",
"message": "`:global(...)` must contain exactly one selector",
"start": {
"line": 5,
"column": 1

@ -1,7 +1,7 @@
[
{
"code": "css_global_invalid_selector_list",
"message": ":global(...) must not contain type or universal selectors when used in a compound selector",
"message": "`:global(...)` must not contain type or universal selectors when used in a compound selector",
"start": {
"line": 20,
"column": 6

@ -1,7 +1,7 @@
[
{
"code": "css_global_invalid_selector",
"message": ":global(...) must contain exactly one selector",
"message": "`:global(...)` must contain exactly one selector",
"start": {
"line": 11,
"column": 5

@ -1,7 +1,7 @@
[
{
"code": "css_type_selector_invalid_placement",
"message": ":global(...) must not be followed with a type selector",
"message": "`:global(...)` must not be followed by a type selector",
"start": {
"line": 17,
"column": 14

@ -1322,17 +1322,32 @@ declare module 'svelte/compiler' {
metadata: {
parent_rule: null | Rule;
has_local_selectors: boolean;
/**
* `true` if the rule contains a `:global` selector, and therefore everything inside should be unscoped
*/
is_global_block: boolean;
};
}
/**
* A list of selectors, e.g. `a, b, c {}`
*/
export interface SelectorList extends BaseNode {
type: 'SelectorList';
/**
* The `a`, `b` and `c` in `a, b, c {}`
*/
children: ComplexSelector[];
}
/**
* A complex selector, e.g. `a b c {}`
*/
export interface ComplexSelector extends BaseNode {
type: 'ComplexSelector';
/**
* The `a`, `b` and `c` in `a b c {}`
*/
children: RelativeSelector[];
metadata: {
rule: null | Rule;
@ -1340,14 +1355,26 @@ declare module 'svelte/compiler' {
};
}
/**
* A relative selector, e.g the `a` and `> b` in `a > b {}`
*/
export interface RelativeSelector extends BaseNode {
type: 'RelativeSelector';
/**
* In `a > b`, `> b` forms one relative selector, and `>` is the combinator. `null` for the first selector.
*/
combinator: null | Combinator;
/**
* The `b:is(...)` in `> b:is(...)`
*/
selectors: SimpleSelector[];
metadata: {
/** :global(..) */
/**
* `true` if the whole selector is unscoped, e.g. `:global(...)` or `:global` or `:global.x`.
* Selectors like `:global(...).x` are not considered global, because they still need scoping.
*/
is_global: boolean;
/** :root, :host, ::view-transition */
/** `:root`, `:host`, `::view-transition`, or selectors after a `:global` */
is_global_like: boolean;
scoped: boolean;
};

@ -202,9 +202,18 @@ This is because the Svelte compiler treated the assignment to `foo.value` as an
Assignments to destructured parts of a `@const` declaration are no longer allowed. It was an oversight that this was ever allowed.
### Stricter CSS `:global` selector validation
### :is(...) and :where(...) are scoped
Previously, a compound selector starting with a global modifier which has universal or type selectors (like `:global(span).foo`) was valid. In Svelte 5, this is a validation error instead. The reason is that in this selector the resulting CSS would be equivalent to one without `:global` - in other words, `:global` is ignored in this case.
Previously, Svelte did not analyse selectors inside `:is(...)` and `:where(...)`, effectively treating them as global. Svelte 5 analyses them in the context of the current component. As such, some selectors may now be treated as unused if they were relying on this treatment. To fix this, use `:global(...)` inside the `:is(...)/:where(...)` selectors.
When using Tailwind's `@apply` directive, add a `:global` selector to preserve rules that use Tailwind-generated `:is(...)` selectors:
```diff
- main {
+ main :global {
@apply bg-blue-100 dark:bg-blue-900
}
```
### CSS hash position no longer deterministic

Loading…
Cancel
Save