Skip to content

Commit 1b693c8

Browse files
committed
proper solution
1 parent ebf3c4a commit 1b693c8

File tree

6 files changed

+53
-34
lines changed

6 files changed

+53
-34
lines changed

packages/svelte/src/compiler/phases/2-analyze/css/css-prune.js

Lines changed: 44 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { get_attribute_chunks, is_text_attribute } from '../../../utils/ast.js';
99
* @typedef {{
1010
* stylesheet: Compiler.Css.StyleSheet;
1111
* element: Compiler.AST.RegularElement | Compiler.AST.SvelteElement;
12+
* from_render_tag: boolean;
1213
* }} State
1314
*/
1415
/** @typedef {NODE_PROBABLY_EXISTS | NODE_DEFINITELY_EXISTS} NodeExistsValue */
@@ -53,10 +54,17 @@ const nesting_selector = {
5354
/**
5455
*
5556
* @param {Compiler.Css.StyleSheet} stylesheet
56-
* @param {Compiler.AST.RegularElement | Compiler.AST.SvelteElement} element
57+
* @param {Compiler.AST.RegularElement | Compiler.AST.SvelteElement | Compiler.AST.RenderTag} element
5758
*/
5859
export function prune(stylesheet, element) {
59-
walk(stylesheet, { stylesheet, element }, visitors);
60+
if (element.type === 'RenderTag') {
61+
const parent = get_element_parent(element);
62+
if (!parent) return;
63+
64+
walk(stylesheet, { stylesheet, element: parent, from_render_tag: true }, visitors);
65+
} else {
66+
walk(stylesheet, { stylesheet, element, from_render_tag: false }, visitors);
67+
}
6068
}
6169

6270
/** @type {Visitors<Compiler.Css.Node, State>} */
@@ -101,7 +109,37 @@ const visitors = {
101109
}
102110
}
103111

104-
if (
112+
if (context.state.from_render_tag) {
113+
// We're searching for a match that crosses a render tag boundary. That means we have to both traverse up
114+
// the element tree (to see if we find an entry point) but also remove selectors from the end (assuming
115+
// they are part of the render tag we don't see). We do all possible combinations of both until we find a match.
116+
/** @type {Compiler.AST.RegularElement | Compiler.AST.SvelteElement | null} */
117+
let element = context.state.element;
118+
119+
while (element) {
120+
const selectors_to_check = selectors.slice();
121+
122+
while (selectors_to_check.length > 0) {
123+
selectors_to_check.pop();
124+
125+
if (
126+
apply_selector(
127+
selectors_to_check,
128+
/** @type {Compiler.Css.Rule} */ (node.metadata.rule),
129+
element,
130+
context.state.stylesheet,
131+
true
132+
)
133+
) {
134+
mark(inner, element);
135+
node.metadata.used = true;
136+
return;
137+
}
138+
}
139+
140+
element = get_element_parent(element);
141+
}
142+
} else if (
105143
apply_selector(
106144
selectors,
107145
/** @type {Compiler.Css.Rule} */ (node.metadata.rule),
@@ -144,17 +182,9 @@ function truncate(node) {
144182
* @param {Compiler.AST.RegularElement | Compiler.AST.SvelteElement} element
145183
* @param {Compiler.Css.StyleSheet} stylesheet
146184
* @param {boolean} check_has Whether or not to check the `:has(...)` selectors
147-
* @param {boolean} [contains_render_tag]
148185
* @returns {boolean}
149186
*/
150-
function apply_selector(
151-
relative_selectors,
152-
rule,
153-
element,
154-
stylesheet,
155-
check_has,
156-
contains_render_tag
157-
) {
187+
function apply_selector(relative_selectors, rule, element, stylesheet, check_has) {
158188
const parent_selectors = relative_selectors.slice();
159189
const relative_selector = parent_selectors.pop();
160190

@@ -169,19 +199,7 @@ function apply_selector(
169199
);
170200

171201
if (!possible_match) {
172-
contains_render_tag ??= element.fragment.nodes.some((node) => node.type === 'RenderTag');
173-
if (contains_render_tag) {
174-
// If the element contains a render tag then we assume the selector might match something inside the rendered snippet
175-
// and traverse the blocks upwards to see if the present blocks match our node further upwards.
176-
// (We could do more static analysis and check the render tag reference to see if this snippet block continues
177-
// with elements that actually match the selector, but that would be a lot of work for little gain)
178-
const possible = apply_selector(parent_selectors, rule, element, stylesheet, true);
179-
if (possible) return true; // e.g `div span` matched for element `<div>{@render tag()}</div>`
180-
// Continue checking if a parent element might match the selector.
181-
// Example: Selector is `p span`, which matches `<p><strong>{@render tag()}</strong></p>` and we're currently at `strong`
182-
} else {
183-
return false;
184-
}
202+
return false;
185203
}
186204

187205
if (relative_selector.combinator) {
@@ -196,10 +214,6 @@ function apply_selector(
196214
);
197215
}
198216

199-
// We got to the end of this under the assumption higher up might start matching,
200-
// but turns out it didn't - therefore the selector doesn't apply after all.
201-
if (contains_render_tag) return false;
202-
203217
// if this is the left-most non-global selector, mark it — we want
204218
// `x y z {...}` to become `x.blah y z.blah {...}`
205219
const parent = parent_selectors[parent_selectors.length - 1];
@@ -705,7 +719,7 @@ function unquote(str) {
705719
}
706720

707721
/**
708-
* @param {Compiler.AST.RegularElement | Compiler.AST.SvelteElement} node
722+
* @param {Compiler.AST.RegularElement | Compiler.AST.SvelteElement | Compiler.AST.RenderTag} node
709723
* @returns {Compiler.AST.RegularElement | Compiler.AST.SvelteElement | null}
710724
*/
711725
function get_element_parent(node) {

packages/svelte/src/compiler/phases/2-analyze/index.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -691,6 +691,8 @@ export function analyze_component(root, source, options) {
691691
}
692692

693693
outer: for (const element of analysis.elements) {
694+
if (element.type === 'RenderTag') continue;
695+
694696
if (element.metadata.scoped) {
695697
// Dynamic elements in dom mode always use spread for attributes and therefore shouldn't have a class attribute added to them
696698
// TODO this happens during the analysis phase, which shouldn't know anything about client vs server

packages/svelte/src/compiler/phases/2-analyze/visitors/RenderTag.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ import { mark_subtree_dynamic } from './shared/fragment.js';
1212
export function RenderTag(node, context) {
1313
validate_opening_tag(node, context.state, '@');
1414

15+
context.state.analysis.elements.push(node);
16+
1517
const callee = unwrap_optional(node.expression).callee;
1618

1719
node.metadata.dynamic =

packages/svelte/src/compiler/phases/types.d.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,8 @@ export interface ComponentAnalysis extends Analysis {
3636
root: ScopeRoot;
3737
instance: Js;
3838
template: Template;
39-
elements: Array<AST.RegularElement | AST.SvelteElement>;
39+
/** Used for CSS pruning and scoping */
40+
elements: Array<AST.RegularElement | AST.SvelteElement | AST.RenderTag>;
4041
runes: boolean;
4142
exports: Array<{ name: string; alias: string | null }>;
4243
/** Whether the component uses `$$props` */

packages/svelte/tests/css/samples/snippets/expected.css

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11

2-
div > span.svelte-xyz {
2+
div.svelte-xyz > span:where(.svelte-xyz) {
33
color: green;
44
}
5-
div span.svelte-xyz {
5+
div.svelte-xyz span:where(.svelte-xyz) {
66
color: green;
77
}
88
div.svelte-xyz span {
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
11
<div class="svelte-xyz"><span class="svelte-xyz">Hello world</span></div>
2-
<p class="svelte-xyz"><strong class="svelte-xyz"><span class="svelte-xyz">Hello world</span></strong></p>
2+
<p class="svelte-xyz"><strong><span class="svelte-xyz">Hello world</span></strong></p>

0 commit comments

Comments
 (0)