Skip to content

Commit 33026a7

Browse files
committed
link snippets to render tags and vice versa
1 parent b84ed1e commit 33026a7

File tree

7 files changed

+114
-15
lines changed

7 files changed

+114
-15
lines changed

packages/svelte/src/compiler/phases/1-parse/state/tag.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -613,7 +613,7 @@ function special(parser) {
613613
metadata: {
614614
dynamic: false,
615615
args_with_call_expression: new Set(),
616-
snippets: []
616+
snippets: new Set()
617617
}
618618
});
619619
}

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

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -438,7 +438,9 @@ export function analyze_component(root, source, options) {
438438
: '',
439439
keyframes: []
440440
},
441-
source
441+
source,
442+
snippet_renderers: new Map(),
443+
snippets: new Set()
442444
};
443445

444446
if (!runes) {
@@ -698,6 +700,16 @@ export function analyze_component(root, source, options) {
698700
);
699701
}
700702

703+
for (const [node, resolved] of analysis.snippet_renderers) {
704+
if (!resolved) {
705+
node.metadata.snippets = analysis.snippets;
706+
}
707+
708+
for (const snippet of node.metadata.snippets) {
709+
snippet.metadata.sites.add(node);
710+
}
711+
}
712+
701713
if (
702714
analysis.uses_render_tags &&
703715
(analysis.uses_slots || (!analysis.custom_element && analysis.slot_names.size > 0))

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

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,28 @@ export function RenderTag(node, context) {
1717

1818
const callee = unwrap_optional(node.expression).callee;
1919

20-
node.metadata.dynamic =
21-
callee.type !== 'Identifier' || context.state.scope.get(callee.name)?.kind !== 'normal';
22-
23-
// TODO populate node.metadata.snippets with all the locally-defined snippets that this
24-
// could refer to, and create bidirectional links
20+
const binding = callee.type === 'Identifier' ? context.state.scope.get(callee.name) : undefined;
21+
22+
node.metadata.dynamic = binding?.kind !== 'normal';
23+
24+
/** If we can't unambiguously resolve this to a declaration, we
25+
* must assume the worst and link the render tag to every snippet
26+
*/
27+
let resolved =
28+
callee.type === 'Identifier' &&
29+
(!binding ||
30+
binding.declaration_kind === 'import' ||
31+
binding.kind === 'prop' ||
32+
binding.kind === 'rest_prop' ||
33+
binding.kind === 'bindable_prop' ||
34+
binding?.initial?.type === 'SnippetBlock');
35+
36+
if (binding?.initial?.type === 'SnippetBlock') {
37+
// if this render tag unambiguously references a local snippet, our job is easy
38+
node.metadata.snippets.add(binding.initial);
39+
}
2540

41+
context.state.analysis.snippet_renderers.set(node, resolved);
2642
context.state.analysis.uses_render_tags = true;
2743

2844
const raw_args = unwrap_optional(node.expression).arguments;

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ import * as e from '../../../errors.js';
88
* @param {Context} context
99
*/
1010
export function SnippetBlock(node, context) {
11+
context.state.analysis.snippets.add(node);
12+
1113
validate_block_not_empty(node.body, context);
1214

1315
if (context.state.analysis.runes) {

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

Lines changed: 61 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
/** @import { AST } from '#compiler' */
2+
/** @import { Expression } from 'estree' */
23
/** @import { AnalysisState, Context } from '../../types' */
34
import * as e from '../../../../errors.js';
45
import { get_attribute_expression, is_expression_attribute } from '../../../../utils/ast.js';
@@ -15,10 +16,67 @@ import { mark_subtree_dynamic } from './fragment.js';
1516
* @param {Context} context
1617
*/
1718
export function visit_component(node, context) {
18-
node.metadata.snippets = [];
19+
// link this node to all the snippets that it could render, so that we can prune CSS correctly
20+
node.metadata.snippets = new Set();
1921

20-
// TODO populate node.metadata.snippets with all the locally-defined snippets that this
21-
// could refer to, and create bidirectional links
22+
let resolved = true;
23+
24+
for (const attribute of node.attributes) {
25+
/** @type {Expression | undefined} */
26+
let expression;
27+
28+
if (attribute.type === 'SpreadAttribute' || attribute.type === 'BindDirective') {
29+
resolved = false;
30+
continue;
31+
}
32+
33+
if (attribute.type !== 'Attribute' || attribute.value === true) {
34+
continue;
35+
}
36+
37+
if (Array.isArray(attribute.value)) {
38+
if (attribute.value.length === 1 && attribute.value[0].type === 'ExpressionTag') {
39+
expression = attribute.value[0].expression;
40+
}
41+
} else {
42+
expression = attribute.value.expression;
43+
}
44+
45+
if (!expression) continue;
46+
47+
if (expression.type === 'Identifier') {
48+
const binding = context.state.scope.get(expression.name);
49+
50+
if (
51+
binding &&
52+
binding.declaration_kind !== 'import' &&
53+
binding.kind !== 'prop' &&
54+
binding.kind !== 'rest_prop' &&
55+
binding.kind !== 'bindable_prop' &&
56+
binding.initial?.type !== 'SnippetBlock'
57+
) {
58+
resolved = false;
59+
}
60+
61+
if (binding?.initial?.type === 'SnippetBlock') {
62+
node.metadata.snippets.add(binding.initial);
63+
}
64+
} else {
65+
// we can't safely know which snippets this component could render,
66+
// so we deopt. this _could_ result in unused CSS not being discarded
67+
resolved = false;
68+
}
69+
}
70+
71+
if (resolved) {
72+
for (const child of node.fragment.nodes) {
73+
if (child.type === 'SnippetBlock') {
74+
node.metadata.snippets.add(child);
75+
}
76+
}
77+
}
78+
79+
context.state.analysis.snippet_renderers.set(node, resolved);
2280

2381
mark_subtree_dynamic(context.path);
2482

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

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,17 @@ export interface ComponentAnalysis extends Analysis {
7272
keyframes: string[];
7373
};
7474
source: string;
75+
/**
76+
* Every render tag/component, and whether it could be definitively resolved or not
77+
*/
78+
snippet_renderers: Map<
79+
AST.RenderTag | AST.Component | AST.SvelteComponent | AST.SvelteSelf,
80+
boolean
81+
>;
82+
/**
83+
* Every snippet that is declared locally
84+
*/
85+
snippets: Set<AST.SnippetBlock>;
7586
}
7687

7788
declare module 'estree' {

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

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -170,7 +170,7 @@ export namespace AST {
170170
path: SvelteNode[];
171171
/** The set of locally-defined snippets that this render tag could correspond to,
172172
* used for CSS pruning purposes */
173-
snippets: SnippetBlock[];
173+
snippets: Set<SnippetBlock>;
174174
};
175175
}
176176

@@ -285,7 +285,7 @@ export namespace AST {
285285
dynamic: boolean;
286286
/** The set of locally-defined snippets that this component tag could render,
287287
* used for CSS pruning purposes */
288-
snippets: SnippetBlock[];
288+
snippets: Set<SnippetBlock>;
289289
};
290290
}
291291

@@ -328,7 +328,7 @@ export namespace AST {
328328
scopes: Record<string, Scope>;
329329
/** The set of locally-defined snippets that this component tag could render,
330330
* used for CSS pruning purposes */
331-
snippets: SnippetBlock[];
331+
snippets: Set<SnippetBlock>;
332332
};
333333
}
334334

@@ -382,7 +382,7 @@ export namespace AST {
382382
scopes: Record<string, Scope>;
383383
/** The set of locally-defined snippets that this component tag could render,
384384
* used for CSS pruning purposes */
385-
snippets: SnippetBlock[];
385+
snippets: Set<SnippetBlock>;
386386
};
387387
}
388388

@@ -456,7 +456,7 @@ export namespace AST {
456456
metadata: {
457457
/** The set of components/render tags that could render this snippet,
458458
* used for CSS pruning */
459-
sites: Set<Component | SvelteComponent | RenderTag>;
459+
sites: Set<Component | SvelteComponent | SvelteSelf | RenderTag>;
460460
};
461461
}
462462

0 commit comments

Comments
 (0)