link snippets to render tags and vice versa

pull/14456/head
Rich Harris 2 years ago
parent b84ed1e5d8
commit 33026a70e1

@ -613,7 +613,7 @@ function special(parser) {
metadata: { metadata: {
dynamic: false, dynamic: false,
args_with_call_expression: new Set(), args_with_call_expression: new Set(),
snippets: [] snippets: new Set()
} }
}); });
} }

@ -438,7 +438,9 @@ export function analyze_component(root, source, options) {
: '', : '',
keyframes: [] keyframes: []
}, },
source source,
snippet_renderers: new Map(),
snippets: new Set()
}; };
if (!runes) { if (!runes) {
@ -698,6 +700,16 @@ export function analyze_component(root, source, options) {
); );
} }
for (const [node, resolved] of analysis.snippet_renderers) {
if (!resolved) {
node.metadata.snippets = analysis.snippets;
}
for (const snippet of node.metadata.snippets) {
snippet.metadata.sites.add(node);
}
}
if ( if (
analysis.uses_render_tags && analysis.uses_render_tags &&
(analysis.uses_slots || (!analysis.custom_element && analysis.slot_names.size > 0)) (analysis.uses_slots || (!analysis.custom_element && analysis.slot_names.size > 0))

@ -17,12 +17,28 @@ export function RenderTag(node, context) {
const callee = unwrap_optional(node.expression).callee; const callee = unwrap_optional(node.expression).callee;
node.metadata.dynamic = const binding = callee.type === 'Identifier' ? context.state.scope.get(callee.name) : undefined;
callee.type !== 'Identifier' || context.state.scope.get(callee.name)?.kind !== 'normal';
node.metadata.dynamic = binding?.kind !== 'normal';
// TODO populate node.metadata.snippets with all the locally-defined snippets that this
// could refer to, and create bidirectional links /** If we can't unambiguously resolve this to a declaration, we
* must assume the worst and link the render tag to every snippet
*/
let resolved =
callee.type === 'Identifier' &&
(!binding ||
binding.declaration_kind === 'import' ||
binding.kind === 'prop' ||
binding.kind === 'rest_prop' ||
binding.kind === 'bindable_prop' ||
binding?.initial?.type === 'SnippetBlock');
if (binding?.initial?.type === 'SnippetBlock') {
// if this render tag unambiguously references a local snippet, our job is easy
node.metadata.snippets.add(binding.initial);
}
context.state.analysis.snippet_renderers.set(node, resolved);
context.state.analysis.uses_render_tags = true; context.state.analysis.uses_render_tags = true;
const raw_args = unwrap_optional(node.expression).arguments; const raw_args = unwrap_optional(node.expression).arguments;

@ -8,6 +8,8 @@ import * as e from '../../../errors.js';
* @param {Context} context * @param {Context} context
*/ */
export function SnippetBlock(node, context) { export function SnippetBlock(node, context) {
context.state.analysis.snippets.add(node);
validate_block_not_empty(node.body, context); validate_block_not_empty(node.body, context);
if (context.state.analysis.runes) { if (context.state.analysis.runes) {

@ -1,4 +1,5 @@
/** @import { AST } from '#compiler' */ /** @import { AST } from '#compiler' */
/** @import { Expression } from 'estree' */
/** @import { AnalysisState, Context } from '../../types' */ /** @import { AnalysisState, Context } from '../../types' */
import * as e from '../../../../errors.js'; import * as e from '../../../../errors.js';
import { get_attribute_expression, is_expression_attribute } from '../../../../utils/ast.js'; import { get_attribute_expression, is_expression_attribute } from '../../../../utils/ast.js';
@ -15,10 +16,67 @@ import { mark_subtree_dynamic } from './fragment.js';
* @param {Context} context * @param {Context} context
*/ */
export function visit_component(node, context) { export function visit_component(node, context) {
node.metadata.snippets = []; // link this node to all the snippets that it could render, so that we can prune CSS correctly
node.metadata.snippets = new Set();
// TODO populate node.metadata.snippets with all the locally-defined snippets that this let resolved = true;
// could refer to, and create bidirectional links
for (const attribute of node.attributes) {
/** @type {Expression | undefined} */
let expression;
if (attribute.type === 'SpreadAttribute' || attribute.type === 'BindDirective') {
resolved = false;
continue;
}
if (attribute.type !== 'Attribute' || attribute.value === true) {
continue;
}
if (Array.isArray(attribute.value)) {
if (attribute.value.length === 1 && attribute.value[0].type === 'ExpressionTag') {
expression = attribute.value[0].expression;
}
} else {
expression = attribute.value.expression;
}
if (!expression) continue;
if (expression.type === 'Identifier') {
const binding = context.state.scope.get(expression.name);
if (
binding &&
binding.declaration_kind !== 'import' &&
binding.kind !== 'prop' &&
binding.kind !== 'rest_prop' &&
binding.kind !== 'bindable_prop' &&
binding.initial?.type !== 'SnippetBlock'
) {
resolved = false;
}
if (binding?.initial?.type === 'SnippetBlock') {
node.metadata.snippets.add(binding.initial);
}
} else {
// we can't safely know which snippets this component could render,
// so we deopt. this _could_ result in unused CSS not being discarded
resolved = false;
}
}
if (resolved) {
for (const child of node.fragment.nodes) {
if (child.type === 'SnippetBlock') {
node.metadata.snippets.add(child);
}
}
}
context.state.analysis.snippet_renderers.set(node, resolved);
mark_subtree_dynamic(context.path); mark_subtree_dynamic(context.path);

@ -72,6 +72,17 @@ export interface ComponentAnalysis extends Analysis {
keyframes: string[]; keyframes: string[];
}; };
source: string; source: string;
/**
* Every render tag/component, and whether it could be definitively resolved or not
*/
snippet_renderers: Map<
AST.RenderTag | AST.Component | AST.SvelteComponent | AST.SvelteSelf,
boolean
>;
/**
* Every snippet that is declared locally
*/
snippets: Set<AST.SnippetBlock>;
} }
declare module 'estree' { declare module 'estree' {

@ -170,7 +170,7 @@ export namespace AST {
path: SvelteNode[]; path: SvelteNode[];
/** The set of locally-defined snippets that this render tag could correspond to, /** The set of locally-defined snippets that this render tag could correspond to,
* used for CSS pruning purposes */ * used for CSS pruning purposes */
snippets: SnippetBlock[]; snippets: Set<SnippetBlock>;
}; };
} }
@ -285,7 +285,7 @@ export namespace AST {
dynamic: boolean; dynamic: boolean;
/** The set of locally-defined snippets that this component tag could render, /** The set of locally-defined snippets that this component tag could render,
* used for CSS pruning purposes */ * used for CSS pruning purposes */
snippets: SnippetBlock[]; snippets: Set<SnippetBlock>;
}; };
} }
@ -328,7 +328,7 @@ export namespace AST {
scopes: Record<string, Scope>; scopes: Record<string, Scope>;
/** The set of locally-defined snippets that this component tag could render, /** The set of locally-defined snippets that this component tag could render,
* used for CSS pruning purposes */ * used for CSS pruning purposes */
snippets: SnippetBlock[]; snippets: Set<SnippetBlock>;
}; };
} }
@ -382,7 +382,7 @@ export namespace AST {
scopes: Record<string, Scope>; scopes: Record<string, Scope>;
/** The set of locally-defined snippets that this component tag could render, /** The set of locally-defined snippets that this component tag could render,
* used for CSS pruning purposes */ * used for CSS pruning purposes */
snippets: SnippetBlock[]; snippets: Set<SnippetBlock>;
}; };
} }
@ -456,7 +456,7 @@ export namespace AST {
metadata: { metadata: {
/** The set of components/render tags that could render this snippet, /** The set of components/render tags that could render this snippet,
* used for CSS pruning */ * used for CSS pruning */
sites: Set<Component | SvelteComponent | RenderTag>; sites: Set<Component | SvelteComponent | SvelteSelf | RenderTag>;
}; };
} }

Loading…
Cancel
Save