render tags

aaa
Rich Harris 8 months ago
parent e8e0d7338d
commit baba2638c9

@ -715,7 +715,7 @@ function special(parser) {
expression: /** @type {AST.RenderTag['expression']} */ (expression), expression: /** @type {AST.RenderTag['expression']} */ (expression),
metadata: { metadata: {
dynamic: false, dynamic: false,
args_with_call_expression: new Set(), arguments: [],
path: [], path: [],
snippets: new Set() snippets: new Set()
} }

@ -618,7 +618,6 @@ export function analyze_component(root, source, options) {
has_props_rune: false, has_props_rune: false,
component_slots: new Set(), component_slots: new Set(),
expression: null, expression: null,
render_tag: null,
private_derived_state: [], private_derived_state: [],
function_depth: scope.function_depth, function_depth: scope.function_depth,
instance_scope: instance.scope, instance_scope: instance.scope,
@ -690,7 +689,6 @@ export function analyze_component(root, source, options) {
reactive_statements: analysis.reactive_statements, reactive_statements: analysis.reactive_statements,
component_slots: new Set(), component_slots: new Set(),
expression: null, expression: null,
render_tag: null,
private_derived_state: [], private_derived_state: [],
function_depth: scope.function_depth function_depth: scope.function_depth
}; };

@ -19,8 +19,6 @@ export interface AnalysisState {
component_slots: Set<string>; component_slots: Set<string>;
/** Information about the current expression/directive/block value */ /** Information about the current expression/directive/block value */
expression: ExpressionMetadata | null; expression: ExpressionMetadata | null;
/** The current {@render ...} tag, if any */
render_tag: null | AST.RenderTag;
private_derived_state: string[]; private_derived_state: string[];
function_depth: number; function_depth: number;

@ -20,8 +20,12 @@ export function AwaitExpression(node, context) {
while (i--) { while (i--) {
const parent = context.path[i]; const parent = context.path[i];
if (
// @ts-expect-error we could probably use a neater/more robust mechanism // @ts-expect-error we could probably use a neater/more robust mechanism
if (parent.metadata?.expression === context.state.expression) { parent.metadata?.expression === context.state.expression ||
// @ts-expect-error
parent.metadata?.arguments?.includes(context.state.expression)
) {
break; break;
} }

@ -3,7 +3,7 @@
/** @import { Context } from '../types' */ /** @import { Context } from '../types' */
import { get_rune } from '../../scope.js'; import { get_rune } from '../../scope.js';
import * as e from '../../../errors.js'; import * as e from '../../../errors.js';
import { get_parent, unwrap_optional } from '../../../utils/ast.js'; import { get_parent } from '../../../utils/ast.js';
import { is_pure, is_safe_identifier } from './shared/utils.js'; import { is_pure, is_safe_identifier } from './shared/utils.js';
import { dev, locate_node, source } from '../../../state.js'; import { dev, locate_node, source } from '../../../state.js';
import * as b from '../../../utils/builders.js'; import * as b from '../../../utils/builders.js';
@ -187,18 +187,6 @@ export function CallExpression(node, context) {
break; break;
} }
if (context.state.render_tag) {
// Find out which of the render tag arguments contains this call expression
const arg_idx = unwrap_optional(context.state.render_tag.expression).arguments.findIndex(
(arg) => arg === node || context.path.includes(arg)
);
// -1 if this is the call expression of the render tag itself
if (arg_idx !== -1) {
context.state.render_tag.metadata.args_with_call_expression.add(arg_idx);
}
}
if (node.callee.type === 'Identifier') { if (node.callee.type === 'Identifier') {
const binding = context.state.scope.get(node.callee.name); const binding = context.state.scope.get(node.callee.name);

@ -5,6 +5,7 @@ import * as e from '../../../errors.js';
import { validate_opening_tag } from './shared/utils.js'; import { validate_opening_tag } from './shared/utils.js';
import { mark_subtree_dynamic } from './shared/fragment.js'; import { mark_subtree_dynamic } from './shared/fragment.js';
import { is_resolved_snippet } from './shared/snippets.js'; import { is_resolved_snippet } from './shared/snippets.js';
import { create_expression_metadata } from '../../nodes.js';
/** /**
* @param {AST.RenderTag} node * @param {AST.RenderTag} node
@ -15,7 +16,8 @@ export function RenderTag(node, context) {
node.metadata.path = [...context.path]; node.metadata.path = [...context.path];
const callee = unwrap_optional(node.expression).callee; const expression = unwrap_optional(node.expression);
const callee = expression.callee;
const binding = callee.type === 'Identifier' ? context.state.scope.get(callee.name) : null; const binding = callee.type === 'Identifier' ? context.state.scope.get(callee.name) : null;
@ -52,5 +54,15 @@ export function RenderTag(node, context) {
mark_subtree_dynamic(context.path); mark_subtree_dynamic(context.path);
context.next({ ...context.state, render_tag: node }); context.visit(callee);
for (const arg of expression.arguments) {
const metadata = create_expression_metadata();
node.metadata.arguments.push(metadata);
context.visit(arg, {
...context.state,
expression: metadata
});
}
} }

@ -1,8 +1,10 @@
/** @import { Expression } from 'estree' */ /** @import { Expression, Statement } from 'estree' */
/** @import { AST } from '#compiler' */ /** @import { AST } from '#compiler' */
/** @import { ComponentContext } from '../types' */ /** @import { ComponentContext, MemoizedExpression } from '../types' */
import { unwrap_optional } from '../../../../utils/ast.js'; import { unwrap_optional } from '../../../../utils/ast.js';
import * as b from '../../../../utils/builders.js'; import * as b from '../../../../utils/builders.js';
import { create_derived } from '../utils.js';
import { get_expression_id } from './shared/utils.js';
/** /**
* @param {AST.RenderTag} node * @param {AST.RenderTag} node
@ -10,23 +12,44 @@ import * as b from '../../../../utils/builders.js';
*/ */
export function RenderTag(node, context) { export function RenderTag(node, context) {
context.state.template.push('<!>'); context.state.template.push('<!>');
const callee = unwrap_optional(node.expression).callee;
const raw_args = unwrap_optional(node.expression).arguments; const expression = unwrap_optional(node.expression);
const callee = expression.callee;
const raw_args = expression.arguments;
/** @type {Expression[]} */ /** @type {Expression[]} */
let args = []; let args = [];
/** @type {MemoizedExpression[]} */
const expressions = [];
/** @type {MemoizedExpression[]} */
const async_expressions = [];
for (let i = 0; i < raw_args.length; i++) { for (let i = 0; i < raw_args.length; i++) {
const raw = raw_args[i]; let expression = /** @type {Expression} */ (context.visit(raw_args[i]));
const arg = /** @type {Expression} */ (context.visit(raw)); const { has_call, is_async } = node.metadata.arguments[i];
if (node.metadata.args_with_call_expression.has(i)) {
const id = b.id(context.state.scope.generate('render_arg')); if (is_async || has_call) {
context.state.init.push(b.var(id, b.call('$.derived_safe_equal', b.thunk(arg)))); expression = b.call(
args.push(b.thunk(b.call('$.get', id))); '$.get',
} else { get_expression_id(is_async ? async_expressions : expressions, expression)
args.push(b.thunk(arg)); );
} }
args.push(b.thunk(expression));
} }
[...async_expressions, ...expressions].forEach((memo, i) => {
memo.id.name = `$${i}`;
});
/** @type {Statement[]} */
const statements = expressions.map((memo, i) =>
b.var(memo.id, create_derived(context.state, b.thunk(memo.expression)))
);
let snippet_function = /** @type {Expression} */ (context.visit(callee)); let snippet_function = /** @type {Expression} */ (context.visit(callee));
if (node.metadata.dynamic) { if (node.metadata.dynamic) {
@ -35,11 +58,11 @@ export function RenderTag(node, context) {
snippet_function = b.logical('??', snippet_function, b.id('$.noop')); snippet_function = b.logical('??', snippet_function, b.id('$.noop'));
} }
context.state.init.push( statements.push(
b.stmt(b.call('$.snippet', context.state.node, b.thunk(snippet_function), ...args)) b.stmt(b.call('$.snippet', context.state.node, b.thunk(snippet_function), ...args))
); );
} else { } else {
context.state.init.push( statements.push(
b.stmt( b.stmt(
(node.expression.type === 'CallExpression' ? b.call : b.maybe_call)( (node.expression.type === 'CallExpression' ? b.call : b.maybe_call)(
snippet_function, snippet_function,
@ -49,4 +72,22 @@ export function RenderTag(node, context) {
) )
); );
} }
if (async_expressions.length > 0) {
context.state.init.push(
b.stmt(
b.call(
'$.async',
context.state.node,
b.array(async_expressions.map((memo) => b.thunk(memo.expression, true))),
b.arrow(
[context.state.node, ...async_expressions.map((memo) => memo.id)],
b.block(statements)
)
)
)
);
} else {
context.state.init.push(statements.length === 1 ? statements[0] : b.block(statements));
}
} }

@ -166,7 +166,7 @@ export namespace AST {
/** @internal */ /** @internal */
metadata: { metadata: {
dynamic: boolean; dynamic: boolean;
args_with_call_expression: Set<number>; arguments: ExpressionMetadata[];
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 */

@ -6,8 +6,6 @@ import { test } from '../../test';
let d; let d;
export default test({ export default test({
skip: true,
html: `<p>pending</p>`, html: `<p>pending</p>`,
get props() { get props() {

Loading…
Cancel
Save