Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: implement :global {...} CSS blocks #11276

Merged
merged 4 commits into from
Apr 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
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/small-apples-eat.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'svelte': patch
---

feat: implement `:global {...}` CSS blocks
9 changes: 9 additions & 0 deletions packages/svelte/src/compiler/errors.js
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,15 @@ const css = {
/** @param {string} message */
'css-parse-error': (message) => message,
'invalid-css-empty-declaration': () => `Declaration cannot be empty`,
'invalid-css-global-block-list': () =>
`A :global {...} block cannot be part of a selector list with more than one item`,
'invalid-css-global-block-modifier': () =>
`A :global {...} block cannot modify an existing selector`,
/** @param {string} name */
'invalid-css-global-block-combinator': (name) =>
`A :global {...} block cannot follow a ${name} combinator`,
'invalid-css-global-block-declaration': () =>
`A :global {...} block can only contain rules, not declarations`,
'invalid-css-global-placement': () =>
`:global(...) can be at the start or end of a selector sequence, but not in the middle`,
'invalid-css-global-selector': () => `:global(...) must contain exactly one selector`,
Expand Down
6 changes: 2 additions & 4 deletions packages/svelte/src/compiler/phases/1-parse/read/style.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import { error } from '../../../errors.js';
const REGEX_MATCHER = /^[~^$*|]?=/;
const REGEX_CLOSING_BRACKET = /[\s\]]/;
const REGEX_ATTRIBUTE_FLAGS = /^[a-zA-Z]+/; // only `i` and `s` are valid today, but make it future-proof
const REGEX_COMBINATOR_WHITESPACE = /^\s*(\+|~|>|\|\|)\s*/;
const REGEX_COMBINATOR = /^(\+|~|>|\|\|)/;
const REGEX_PERCENTAGE = /^\d+(\.\d+)?%/;
const REGEX_NTH_OF =
Expand Down Expand Up @@ -116,7 +115,8 @@ function read_rule(parser) {
end: parser.index,
metadata: {
parent_rule: null,
has_local_selectors: false
has_local_selectors: false,
is_global_block: false
}
};
}
Expand Down Expand Up @@ -252,8 +252,6 @@ function read_selector(parser, inside_pseudo_class = false) {
if (parser.eat('(')) {
args = read_selector_list(parser, true);
parser.eat(')', true);
} else if (name === 'global') {
error(parser.index, 'invalid-css-global-selector');
}

relative_selector.selectors.push({
Expand Down
44 changes: 44 additions & 0 deletions packages/svelte/src/compiler/phases/2-analyze/css/css-analyze.js
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,17 @@ 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];

const s = last.selectors[last.selectors.length - 1];

if (s.type === 'PseudoClassSelector' && s.name === 'global' && s.args === null) {
return true;
}
});

context.next({
...context.state,
rule: node
Expand All @@ -84,6 +95,39 @@ const analysis_visitors = {

/** @type {Visitors} */
const validation_visitors = {
Rule(node, context) {
if (node.metadata.is_global_block) {
if (node.prelude.children.length > 1) {
error(node.prelude, 'invalid-css-global-block-list');
}

const complex_selector = node.prelude.children[0];
const relative_selector = complex_selector.children[complex_selector.children.length - 1];

if (relative_selector.selectors.length > 1) {
error(
relative_selector.selectors[relative_selector.selectors.length - 1],
'invalid-css-global-block-modifier'
);
}

if (relative_selector.combinator && relative_selector.combinator.name !== ' ') {
error(
relative_selector,
'invalid-css-global-block-combinator',
relative_selector.combinator.name
);
}

const declaration = node.block.children.find((child) => child.type === 'Declaration');

if (declaration) {
error(declaration, 'invalid-css-global-block-declaration');
}
}

context.next();
},
ComplexSelector(node, context) {
// ensure `:global(...)` is not used in the middle of a selector
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { walk } from 'zimmerframe';
import { get_possible_values } from './utils.js';
import { regex_ends_with_whitespace, regex_starts_with_whitespace } from '../../patterns.js';
import { error } from '../../../errors.js';

/**
* @typedef {{
Expand Down Expand Up @@ -60,6 +59,13 @@ export function prune(stylesheet, element) {

/** @type {import('zimmerframe').Visitors<import('#compiler').Css.Node, State>} */
const visitors = {
Rule(node, context) {
if (node.metadata.is_global_block) {
context.visit(node.prelude);
} else {
context.next();
}
},
ComplexSelector(node, context) {
const selectors = truncate(node);
const inner = selectors[selectors.length - 1];
Expand Down
7 changes: 7 additions & 0 deletions packages/svelte/src/compiler/phases/2-analyze/css/css-warn.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,5 +30,12 @@ const visitors = {
}

context.next();
},
Rule(node, context) {
if (node.metadata.is_global_block) {
context.visit(node.prelude);
} else {
context.next();
}
}
};
26 changes: 25 additions & 1 deletion packages/svelte/src/compiler/phases/3-transform/css/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ const visitors = {
}
}
},
Rule(node, { state, next }) {
Rule(node, { state, next, visit }) {
// keep empty rules in dev, because it's convenient to
// see them in devtools
if (!state.dev && is_empty(node)) {
Expand All @@ -134,6 +134,26 @@ const visitors = {
return;
}

if (node.metadata.is_global_block) {
const selector = node.prelude.children[0];

if (selector.children.length === 1) {
// `:global {...}`
state.code.prependRight(node.start, '/* ');
state.code.appendLeft(node.block.start + 1, '*/');

state.code.prependRight(node.block.end - 1, '/*');
state.code.appendLeft(node.block.end, '*/');

// don't recurse into selector or body
return;
}

// don't recurse into body
visit(node.prelude);
return;
}

next();
},
SelectorList(node, { state, next, path }) {
Expand Down Expand Up @@ -275,6 +295,10 @@ const visitors = {

/** @param {import('#compiler').Css.Rule} rule */
function is_empty(rule) {
if (rule.metadata.is_global_block) {
return rule.block.children.length === 0;
}

for (const child of rule.block.children) {
if (child.type === 'Declaration') {
return false;
Expand Down
1 change: 1 addition & 0 deletions packages/svelte/src/compiler/types/css.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ export namespace Css {
metadata: {
parent_rule: null | Rule;
has_local_selectors: boolean;
is_global_block: boolean;
};
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { test } from '../../test';

export default test({
error: {
code: 'invalid-css-global-block-combinator',
message: 'A :global {...} block cannot follow a > combinator',
position: [12, 21]
}
});
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
<style>
:global {}
.x > :global {}
</style>
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { test } from '../../test';

export default test({
error: {
code: 'invalid-css-global-block-declaration',
message: 'A :global {...} block can only contain rules, not declarations',
position: [24, 34]
}
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<style>
.x :global {
color: red;
}
</style>
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { test } from '../../test';

export default test({
error: {
code: 'invalid-css-global-block-modifier',
message: 'A :global {...} block cannot modify an existing selector',
position: [14, 21]
}
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<style>
.x .y:global {}
</style>
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { test } from '../../test';

export default test({
error: {
code: 'invalid-css-global-block-list',
message: 'A :global {...} block cannot be part of a selector list with more than one item',
position: [9, 31]
}
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<style>
.x :global, .y :global {}
</style>

This file was deleted.

21 changes: 21 additions & 0 deletions packages/svelte/tests/css/samples/global-block/_config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { test } from '../../test';

export default test({
warnings: [
{
filename: 'SvelteComponent.svelte',
code: 'css-unused-selector',
message: 'Unused CSS selector ".unused :global"',
start: {
line: 16,
column: 1,
character: 128
},
end: {
line: 16,
column: 16,
character: 143
}
}
]
});
17 changes: 17 additions & 0 deletions packages/svelte/tests/css/samples/global-block/expected.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
/* :global {*/
.x {
color: green;
}
/*}*/

div.svelte-xyz {
.y {
color: green;
}
}

/* (unused) .unused :global {
.z {
color: red;
}
}*/
21 changes: 21 additions & 0 deletions packages/svelte/tests/css/samples/global-block/input.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<div>{@html whatever}</div>

<style>
:global {
.x {
color: green;
}
}

div :global {
.y {
color: green;
}
}

.unused :global {
.z {
color: red;
}
}
</style>
1 change: 1 addition & 0 deletions packages/svelte/types/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1122,6 +1122,7 @@ declare module 'svelte/compiler' {
metadata: {
parent_rule: null | Rule;
has_local_selectors: boolean;
is_global_block: boolean;
};
}

Expand Down
Loading