Skip to content

Commit 1eb484e

Browse files
committed
more
1 parent 05e25e7 commit 1eb484e

File tree

8 files changed

+360
-283
lines changed

8 files changed

+360
-283
lines changed

packages/svelte/src/compiler/phases/3-transform/server/transform-server.js

Lines changed: 14 additions & 283 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/** @import { AssignmentExpression, BinaryOperator, BlockStatement, CallExpression, Expression, ExpressionStatement, Identifier, MethodDefinition, Node, Pattern, Program, Property, PropertyDefinition, Statement, VariableDeclarator } from 'estree' */
2-
/** @import { Attribute, Binding, Component, Namespace, SvelteComponent, SvelteNode, SvelteSelf, TemplateNode, Text, ValidatedCompileOptions, ValidatedModuleCompileOptions } from '#compiler' */
3-
/** @import { ComponentContext, ComponentServerTransformState, ComponentVisitors, ServerTransformState, Visitors } from './types.js' */
2+
/** @import { Binding, Namespace, SvelteNode, ValidatedCompileOptions, ValidatedModuleCompileOptions } from '#compiler' */
3+
/** @import { ComponentServerTransformState, ComponentVisitors, ServerTransformState, Visitors } from './types.js' */
44
/** @import { Analysis, ComponentAnalysis } from '../../types.js' */
55
/** @import { Scope } from '../../scope.js' */
66
/** @import { StateField } from '../../3-transform/client/types.js' */ // TODO move this type
@@ -10,18 +10,23 @@ import { extract_identifiers, extract_paths, is_expression_async } from '../../.
1010
import * as b from '../../../utils/builders.js';
1111
import is_reference from 'is-reference';
1212
import { transform_inspect_rune } from '../utils.js';
13-
import { is_element_node } from '../../nodes.js';
1413
import { filename } from '../../../state.js';
1514
import { render_stylesheet } from '../css/index.js';
15+
import { AwaitBlock } from './visitors/template/AwaitBlock.js';
16+
import { Component } from './visitors/template/Component.js';
1617
import { ConstTag } from './visitors/template/ConstTag.js';
1718
import { DebugTag } from './visitors/template/DebugTag.js';
1819
import { EachBlock } from './visitors/template/EachBlock.js';
1920
import { Fragment } from './visitors/template/Fragment.js';
2021
import { HtmlTag } from './visitors/template/HtmlTag.js';
2122
import { IfBlock } from './visitors/template/IfBlock.js';
23+
import { KeyBlock } from './visitors/template/KeyBlock.js';
2224
import { RegularElement } from './visitors/template/RegularElement.js';
2325
import { RenderTag } from './visitors/template/RenderTag.js';
26+
import { SnippetBlock } from './visitors/template/SnippetBlock.js';
27+
import { SvelteComponent } from './visitors/template/SvelteComponent.js';
2428
import { SvelteElement } from './visitors/template/SvelteElement.js';
29+
import { SvelteSelf } from './visitors/template/SvelteSelf.js';
2530
import {
2631
empty_comment,
2732
process_children,
@@ -502,235 +507,6 @@ const javascript_visitors_runes = {
502507
}
503508
};
504509

505-
/**
506-
* @param {Component | SvelteComponent | SvelteSelf} node
507-
* @param {Expression} expression
508-
* @param {ComponentContext} context
509-
*/
510-
function serialize_inline_component(node, expression, context) {
511-
/** @type {Array<Property[] | Expression>} */
512-
const props_and_spreads = [];
513-
514-
/** @type {Property[]} */
515-
const custom_css_props = [];
516-
517-
/** @type {ExpressionStatement[]} */
518-
const lets = [];
519-
520-
/** @type {Record<string, TemplateNode[]>} */
521-
const children = {};
522-
523-
/**
524-
* If this component has a slot property, it is a named slot within another component. In this case
525-
* the slot scope applies to the component itself, too, and not just its children.
526-
*/
527-
let slot_scope_applies_to_itself = false;
528-
529-
/**
530-
* Components may have a children prop and also have child nodes. In this case, we assume
531-
* that the child component isn't using render tags yet and pass the slot as $$slots.default.
532-
* We're not doing it for spread attributes, as this would result in too many false positives.
533-
*/
534-
let has_children_prop = false;
535-
536-
/**
537-
* @param {Property} prop
538-
*/
539-
function push_prop(prop) {
540-
const current = props_and_spreads.at(-1);
541-
const current_is_props = Array.isArray(current);
542-
const props = current_is_props ? current : [];
543-
props.push(prop);
544-
if (!current_is_props) {
545-
props_and_spreads.push(props);
546-
}
547-
}
548-
for (const attribute of node.attributes) {
549-
if (attribute.type === 'LetDirective') {
550-
lets.push(/** @type {ExpressionStatement} */ (context.visit(attribute)));
551-
} else if (attribute.type === 'SpreadAttribute') {
552-
props_and_spreads.push(/** @type {Expression} */ (context.visit(attribute)));
553-
} else if (attribute.type === 'Attribute') {
554-
if (attribute.name.startsWith('--')) {
555-
const value = serialize_attribute_value(attribute.value, context, false, true);
556-
custom_css_props.push(b.init(attribute.name, value));
557-
continue;
558-
}
559-
560-
if (attribute.name === 'slot') {
561-
slot_scope_applies_to_itself = true;
562-
}
563-
564-
if (attribute.name === 'children') {
565-
has_children_prop = true;
566-
}
567-
568-
const value = serialize_attribute_value(attribute.value, context, false, true);
569-
push_prop(b.prop('init', b.key(attribute.name), value));
570-
} else if (attribute.type === 'BindDirective' && attribute.name !== 'this') {
571-
// TODO this needs to turn the whole thing into a while loop because the binding could be mutated eagerly in the child
572-
push_prop(
573-
b.get(attribute.name, [
574-
b.return(/** @type {Expression} */ (context.visit(attribute.expression)))
575-
])
576-
);
577-
push_prop(
578-
b.set(attribute.name, [
579-
b.stmt(
580-
/** @type {Expression} */ (
581-
context.visit(b.assignment('=', attribute.expression, b.id('$$value')))
582-
)
583-
),
584-
b.stmt(b.assignment('=', b.id('$$settled'), b.false))
585-
])
586-
);
587-
}
588-
}
589-
590-
if (slot_scope_applies_to_itself) {
591-
context.state.init.push(...lets);
592-
}
593-
594-
/** @type {Statement[]} */
595-
const snippet_declarations = [];
596-
597-
// Group children by slot
598-
for (const child of node.fragment.nodes) {
599-
if (child.type === 'SnippetBlock') {
600-
// the SnippetBlock visitor adds a declaration to `init`, but if it's directly
601-
// inside a component then we want to hoist them into a block so that they
602-
// can be used as props without creating conflicts
603-
context.visit(child, {
604-
...context.state,
605-
init: snippet_declarations
606-
});
607-
608-
push_prop(b.prop('init', child.expression, child.expression));
609-
610-
continue;
611-
}
612-
613-
let slot_name = 'default';
614-
if (is_element_node(child)) {
615-
const attribute = /** @type {Attribute | undefined} */ (
616-
child.attributes.find(
617-
(attribute) => attribute.type === 'Attribute' && attribute.name === 'slot'
618-
)
619-
);
620-
if (attribute !== undefined) {
621-
slot_name = /** @type {Text[]} */ (attribute.value)[0].data;
622-
}
623-
}
624-
625-
children[slot_name] = children[slot_name] || [];
626-
children[slot_name].push(child);
627-
}
628-
629-
// Serialize each slot
630-
/** @type {Property[]} */
631-
const serialized_slots = [];
632-
633-
for (const slot_name of Object.keys(children)) {
634-
const block = /** @type {BlockStatement} */ (
635-
context.visit(
636-
{
637-
...node.fragment,
638-
// @ts-expect-error
639-
nodes: children[slot_name]
640-
},
641-
{
642-
...context.state,
643-
scope:
644-
context.state.scopes.get(slot_name === 'default' ? children[slot_name][0] : node) ??
645-
context.state.scope
646-
}
647-
)
648-
);
649-
650-
if (block.body.length === 0) continue;
651-
652-
const slot_fn = b.arrow(
653-
[b.id('$$payload'), b.id('$$slotProps')],
654-
b.block([
655-
...(slot_name === 'default' && !slot_scope_applies_to_itself ? lets : []),
656-
...block.body
657-
])
658-
);
659-
660-
if (slot_name === 'default' && !has_children_prop) {
661-
if (lets.length === 0 && children.default.every((node) => node.type !== 'SvelteFragment')) {
662-
// create `children` prop...
663-
push_prop(b.prop('init', b.id('children'), slot_fn));
664-
665-
// and `$$slots.default: true` so that `<slot>` on the child works
666-
serialized_slots.push(b.init(slot_name, b.true));
667-
} else {
668-
// create `$$slots.default`...
669-
serialized_slots.push(b.init(slot_name, slot_fn));
670-
671-
// and a `children` prop that errors
672-
push_prop(b.init('children', b.id('$.invalid_default_snippet')));
673-
}
674-
} else {
675-
serialized_slots.push(b.init(slot_name, slot_fn));
676-
}
677-
}
678-
679-
if (serialized_slots.length > 0) {
680-
push_prop(b.prop('init', b.id('$$slots'), b.object(serialized_slots)));
681-
}
682-
683-
const props_expression =
684-
props_and_spreads.length === 0 ||
685-
(props_and_spreads.length === 1 && Array.isArray(props_and_spreads[0]))
686-
? b.object(/** @type {Property[]} */ (props_and_spreads[0] || []))
687-
: b.call(
688-
'$.spread_props',
689-
b.array(props_and_spreads.map((p) => (Array.isArray(p) ? b.object(p) : p)))
690-
);
691-
692-
/** @type {Statement} */
693-
let statement = b.stmt(
694-
(node.type === 'SvelteComponent' ? b.maybe_call : b.call)(
695-
expression,
696-
b.id('$$payload'),
697-
props_expression
698-
)
699-
);
700-
701-
if (snippet_declarations.length > 0) {
702-
statement = b.block([...snippet_declarations, statement]);
703-
}
704-
705-
const dynamic =
706-
node.type === 'SvelteComponent' || (node.type === 'Component' && node.metadata.dynamic);
707-
708-
if (custom_css_props.length > 0) {
709-
context.state.template.push(
710-
b.stmt(
711-
b.call(
712-
'$.css_props',
713-
b.id('$$payload'),
714-
b.literal(context.state.namespace === 'svg' ? false : true),
715-
b.object(custom_css_props),
716-
b.thunk(b.block([statement])),
717-
dynamic && b.true
718-
)
719-
)
720-
);
721-
} else {
722-
if (dynamic) {
723-
context.state.template.push(empty_comment);
724-
}
725-
726-
context.state.template.push(statement);
727-
728-
if (!context.state.skip_hydration_boundaries) {
729-
context.state.template.push(empty_comment);
730-
}
731-
}
732-
}
733-
734510
/** @type {Visitors} */
735511
const javascript_visitors_legacy = {
736512
VariableDeclaration(node, { state, visit }) {
@@ -834,57 +610,12 @@ const template_visitors = {
834610
SvelteElement,
835611
EachBlock,
836612
IfBlock,
837-
AwaitBlock(node, context) {
838-
context.state.template.push(
839-
empty_comment,
840-
b.stmt(
841-
b.call(
842-
'$.await',
843-
/** @type {Expression} */ (context.visit(node.expression)),
844-
b.thunk(
845-
node.pending ? /** @type {BlockStatement} */ (context.visit(node.pending)) : b.block([])
846-
),
847-
b.arrow(
848-
node.value ? [/** @type {Pattern} */ (context.visit(node.value))] : [],
849-
node.then ? /** @type {BlockStatement} */ (context.visit(node.then)) : b.block([])
850-
),
851-
b.arrow(
852-
node.error ? [/** @type {Pattern} */ (context.visit(node.error))] : [],
853-
node.catch ? /** @type {BlockStatement} */ (context.visit(node.catch)) : b.block([])
854-
)
855-
)
856-
),
857-
empty_comment
858-
);
859-
},
860-
KeyBlock(node, context) {
861-
const block = /** @type {BlockStatement} */ (context.visit(node.fragment));
862-
context.state.template.push(empty_comment, block, empty_comment);
863-
},
864-
SnippetBlock(node, context) {
865-
const fn = b.function_declaration(
866-
node.expression,
867-
[b.id('$$payload'), ...node.parameters],
868-
/** @type {BlockStatement} */ (context.visit(node.body))
869-
);
870-
// @ts-expect-error - TODO remove this hack once $$render_inner for legacy bindings is gone
871-
fn.___snippet = true;
872-
// TODO hoist where possible
873-
context.state.init.push(fn);
874-
},
875-
Component(node, context) {
876-
serialize_inline_component(node, b.id(node.name), context);
877-
},
878-
SvelteSelf(node, context) {
879-
serialize_inline_component(node, b.id(context.state.analysis.name), context);
880-
},
881-
SvelteComponent(node, context) {
882-
serialize_inline_component(
883-
node,
884-
/** @type {Expression} */ (context.visit(node.expression)),
885-
context
886-
);
887-
},
613+
AwaitBlock,
614+
KeyBlock,
615+
SnippetBlock,
616+
Component,
617+
SvelteSelf,
618+
SvelteComponent,
888619
LetDirective(node, { state }) {
889620
if (node.expression === null || node.expression.type === 'Identifier') {
890621
const name = node.expression === null ? node.name : node.expression.name;
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
/** @import { BlockStatement, Expression, Pattern } from 'estree' */
2+
/** @import { AwaitBlock } from '#compiler' */
3+
/** @import { ComponentContext } from '../../types' */
4+
import * as b from '../../../../../utils/builders.js';
5+
import { empty_comment } from './shared/utils.js';
6+
7+
/**
8+
* @param {AwaitBlock} node
9+
* @param {ComponentContext} context
10+
*/
11+
export function AwaitBlock(node, context) {
12+
context.state.template.push(
13+
empty_comment,
14+
b.stmt(
15+
b.call(
16+
'$.await',
17+
/** @type {Expression} */ (context.visit(node.expression)),
18+
b.thunk(
19+
node.pending ? /** @type {BlockStatement} */ (context.visit(node.pending)) : b.block([])
20+
),
21+
b.arrow(
22+
node.value ? [/** @type {Pattern} */ (context.visit(node.value))] : [],
23+
node.then ? /** @type {BlockStatement} */ (context.visit(node.then)) : b.block([])
24+
),
25+
b.arrow(
26+
node.error ? [/** @type {Pattern} */ (context.visit(node.error))] : [],
27+
node.catch ? /** @type {BlockStatement} */ (context.visit(node.catch)) : b.block([])
28+
)
29+
)
30+
),
31+
empty_comment
32+
);
33+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
/** @import { Component } from '#compiler' */
2+
/** @import { ComponentContext } from '../../types' */
3+
import * as b from '../../../../../utils/builders.js';
4+
import { serialize_inline_component } from './shared/component.js';
5+
6+
/**
7+
* @param {Component} node
8+
* @param {ComponentContext} context
9+
*/
10+
export function Component(node, context) {
11+
serialize_inline_component(node, b.id(node.name), context);
12+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
/** @import { BlockStatement } from 'estree' */
2+
/** @import { KeyBlock } from '#compiler' */
3+
/** @import { ComponentContext } from '../../types' */
4+
import { empty_comment } from './shared/utils.js';
5+
6+
/**
7+
* @param {KeyBlock} node
8+
* @param {ComponentContext} context
9+
*/
10+
export function KeyBlock(node, context) {
11+
context.state.template.push(
12+
empty_comment,
13+
/** @type {BlockStatement} */ (context.visit(node.fragment)),
14+
empty_comment
15+
);
16+
}

0 commit comments

Comments
 (0)