pull/12596/head
Rich Harris 2 years ago
parent 1eb484eb25
commit 82be92c780

@ -21,18 +21,18 @@ import { Fragment } from './visitors/template/Fragment.js';
import { HtmlTag } from './visitors/template/HtmlTag.js';
import { IfBlock } from './visitors/template/IfBlock.js';
import { KeyBlock } from './visitors/template/KeyBlock.js';
import { LetDirective } from './visitors/template/LetDirective.js';
import { RegularElement } from './visitors/template/RegularElement.js';
import { RenderTag } from './visitors/template/RenderTag.js';
import { SlotElement } from './visitors/template/SlotElement.js';
import { SnippetBlock } from './visitors/template/SnippetBlock.js';
import { SpreadAttribute } from './visitors/template/SpreadAttribute.js';
import { SvelteComponent } from './visitors/template/SvelteComponent.js';
import { SvelteElement } from './visitors/template/SvelteElement.js';
import { SvelteFragment } from './visitors/template/SvelteFragment.js';
import { SvelteHead } from './visitors/template/SvelteHead.js';
import { SvelteSelf } from './visitors/template/SvelteSelf.js';
import {
empty_comment,
process_children,
serialize_attribute_value,
serialize_template
} from './visitors/template/shared/utils.js';
import { TitleElement } from './visitors/template/TitleElement.js';
/**
* @param {VariableDeclarator} declarator
@ -616,124 +616,12 @@ const template_visitors = {
Component,
SvelteSelf,
SvelteComponent,
LetDirective(node, { state }) {
if (node.expression === null || node.expression.type === 'Identifier') {
const name = node.expression === null ? node.name : node.expression.name;
return b.const(name, b.member(b.id('$$slotProps'), b.id(node.name)));
}
const name = state.scope.generate(node.name);
const bindings = state.scope.get_bindings(node);
for (const binding of bindings) {
state.getters[binding.node.name] = b.member(b.id(name), b.id(binding.node.name));
}
return b.const(
name,
b.call(
b.thunk(
b.block([
b.let(
node.expression.type === 'ObjectExpression'
? // @ts-expect-error types don't match, but it can't contain spread elements and the structure is otherwise fine
b.object_pattern(node.expression.properties)
: // @ts-expect-error types don't match, but it can't contain spread elements and the structure is otherwise fine
b.array_pattern(node.expression.elements),
b.member(b.id('$$slotProps'), b.id(node.name))
),
b.return(b.object(bindings.map((binding) => b.init(binding.node.name, binding.node))))
])
)
)
);
},
SpreadAttribute(node, { visit }) {
return visit(node.expression);
},
SvelteFragment(node, context) {
const child_state = {
...context.state,
getters: { ...context.state.getters }
};
for (const attribute of node.attributes) {
if (attribute.type === 'LetDirective') {
context.state.template.push(
/** @type {ExpressionStatement} */ (context.visit(attribute, child_state))
);
}
}
const block = /** @type {BlockStatement} */ (context.visit(node.fragment, child_state));
context.state.template.push(block);
},
TitleElement(node, context) {
// title is guaranteed to contain only text/expression tag children
const template = [b.literal('<title>')];
process_children(node.fragment.nodes, { ...context, state: { ...context.state, template } });
template.push(b.literal('</title>'));
context.state.init.push(...serialize_template(template, b.id('$$payload.title'), '='));
},
SlotElement(node, context) {
/** @type {Property[]} */
const props = [];
/** @type {Expression[]} */
const spreads = [];
/** @type {ExpressionStatement[]} */
const lets = [];
/** @type {Expression} */
let expression = b.call('$.default_slot', b.id('$$props'));
for (const attribute of node.attributes) {
if (attribute.type === 'SpreadAttribute') {
spreads.push(/** @type {Expression} */ (context.visit(attribute)));
} else if (attribute.type === 'Attribute') {
const value = serialize_attribute_value(attribute.value, context, false, true);
if (attribute.name === 'name') {
expression = b.member(b.member_id('$$props.$$slots'), value, true, true);
} else if (attribute.name !== 'slot') {
if (attribute.metadata.dynamic) {
props.push(b.get(attribute.name, [b.return(value)]));
} else {
props.push(b.init(attribute.name, value));
}
}
} else if (attribute.type === 'LetDirective') {
lets.push(/** @type {ExpressionStatement} */ (context.visit(attribute)));
}
}
// Let bindings first, they can be used on attributes
context.state.init.push(...lets);
const props_expression =
spreads.length === 0
? b.object(props)
: b.call('$.spread_props', b.array([b.object(props), ...spreads]));
const fallback =
node.fragment.nodes.length === 0
? b.literal(null)
: b.thunk(/** @type {BlockStatement} */ (context.visit(node.fragment)));
const slot = b.call('$.slot', b.id('$$payload'), expression, props_expression, fallback);
context.state.template.push(empty_comment, b.stmt(slot), empty_comment);
},
SvelteHead(node, context) {
const block = /** @type {BlockStatement} */ (context.visit(node.fragment));
context.state.template.push(
b.stmt(b.call('$.head', b.id('$$payload'), b.arrow([b.id('$$payload')], block)))
);
}
LetDirective,
SpreadAttribute,
SvelteFragment,
TitleElement,
SlotElement,
SvelteHead
};
/**

@ -0,0 +1,40 @@
/** @import { LetDirective } from '#compiler' */
/** @import { ComponentContext } from '../../types' */
import * as b from '../../../../../utils/builders.js';
/**
* @param {LetDirective} node
* @param {ComponentContext} context
*/
export function LetDirective(node, context) {
if (node.expression === null || node.expression.type === 'Identifier') {
const name = node.expression === null ? node.name : node.expression.name;
return b.const(name, b.member(b.id('$$slotProps'), b.id(node.name)));
}
const name = context.state.scope.generate(node.name);
const bindings = context.state.scope.get_bindings(node);
for (const binding of bindings) {
context.state.getters[binding.node.name] = b.member(b.id(name), b.id(binding.node.name));
}
return b.const(
name,
b.call(
b.thunk(
b.block([
b.let(
node.expression.type === 'ObjectExpression'
? // @ts-expect-error types don't match, but it can't contain spread elements and the structure is otherwise fine
b.object_pattern(node.expression.properties)
: // @ts-expect-error types don't match, but it can't contain spread elements and the structure is otherwise fine
b.array_pattern(node.expression.elements),
b.member(b.id('$$slotProps'), b.id(node.name))
),
b.return(b.object(bindings.map((binding) => b.init(binding.node.name, binding.node))))
])
)
)
);
}

@ -0,0 +1,60 @@
/** @import { BlockStatement, Expression, ExpressionStatement, Property } from 'estree' */
/** @import { SlotElement } from '#compiler' */
/** @import { ComponentContext } from '../../types' */
import * as b from '../../../../../utils/builders.js';
import { empty_comment, serialize_attribute_value } from './shared/utils.js';
/**
* @param {SlotElement} node
* @param {ComponentContext} context
*/
export function SlotElement(node, context) {
/** @type {Property[]} */
const props = [];
/** @type {Expression[]} */
const spreads = [];
/** @type {ExpressionStatement[]} */
const lets = [];
/** @type {Expression} */
let expression = b.call('$.default_slot', b.id('$$props'));
for (const attribute of node.attributes) {
if (attribute.type === 'SpreadAttribute') {
spreads.push(/** @type {Expression} */ (context.visit(attribute)));
} else if (attribute.type === 'Attribute') {
const value = serialize_attribute_value(attribute.value, context, false, true);
if (attribute.name === 'name') {
expression = b.member(b.member_id('$$props.$$slots'), value, true, true);
} else if (attribute.name !== 'slot') {
if (attribute.metadata.dynamic) {
props.push(b.get(attribute.name, [b.return(value)]));
} else {
props.push(b.init(attribute.name, value));
}
}
} else if (attribute.type === 'LetDirective') {
lets.push(/** @type {ExpressionStatement} */ (context.visit(attribute)));
}
}
// Let bindings first, they can be used on attributes
context.state.init.push(...lets);
const props_expression =
spreads.length === 0
? b.object(props)
: b.call('$.spread_props', b.array([b.object(props), ...spreads]));
const fallback =
node.fragment.nodes.length === 0
? b.literal(null)
: b.thunk(/** @type {BlockStatement} */ (context.visit(node.fragment)));
const slot = b.call('$.slot', b.id('$$payload'), expression, props_expression, fallback);
context.state.template.push(empty_comment, b.stmt(slot), empty_comment);
}

@ -0,0 +1,10 @@
/** @import { SpreadAttribute } from '#compiler' */
/** @import { ComponentContext } from '../../types' */
/**
* @param {SpreadAttribute} node
* @param {ComponentContext} context
*/
export function SpreadAttribute(node, context) {
return context.visit(node.expression);
}

@ -0,0 +1,26 @@
/** @import { BlockStatement, ExpressionStatement } from 'estree' */
/** @import { SvelteFragment } from '#compiler' */
/** @import { ComponentContext } from '../../types' */
/**
* @param {SvelteFragment} node
* @param {ComponentContext} context
*/
export function SvelteFragment(node, context) {
const child_state = {
...context.state,
getters: { ...context.state.getters }
};
for (const attribute of node.attributes) {
if (attribute.type === 'LetDirective') {
context.state.template.push(
/** @type {ExpressionStatement} */ (context.visit(attribute, child_state))
);
}
}
const block = /** @type {BlockStatement} */ (context.visit(node.fragment, child_state));
context.state.template.push(block);
}

@ -0,0 +1,16 @@
/** @import { BlockStatement } from 'estree' */
/** @import { SvelteHead } from '#compiler' */
/** @import { ComponentContext } from '../../types' */
import * as b from '../../../../../utils/builders.js';
/**
* @param {SvelteHead} node
* @param {ComponentContext} context
*/
export function SvelteHead(node, context) {
const block = /** @type {BlockStatement} */ (context.visit(node.fragment));
context.state.template.push(
b.stmt(b.call('$.head', b.id('$$payload'), b.arrow([b.id('$$payload')], block)))
);
}

@ -0,0 +1,17 @@
/** @import { TitleElement } from '#compiler' */
/** @import { ComponentContext } from '../../types' */
import * as b from '../../../../../utils/builders.js';
import { process_children, serialize_template } from './shared/utils.js';
/**
* @param {TitleElement} node
* @param {ComponentContext} context
*/
export function TitleElement(node, context) {
// title is guaranteed to contain only text/expression tag children
const template = [b.literal('<title>')];
process_children(node.fragment.nodes, { ...context, state: { ...context.state, template } });
template.push(b.literal('</title>'));
context.state.init.push(...serialize_template(template, b.id('$$payload.title'), '='));
}
Loading…
Cancel
Save