Skip to content

Commit 17f1b6a

Browse files
committed
WIP
1 parent d9237e2 commit 17f1b6a

File tree

8 files changed

+170
-153
lines changed

8 files changed

+170
-153
lines changed

packages/svelte/src/compiler/phases/3-transform/client/transform-template/index.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -76,8 +76,8 @@ export function transform_template(state, context, namespace, template_name, fla
7676
/** @type {Expression[]} */
7777
const args = [
7878
state.options.templatingMode === 'functional'
79-
? template_to_functions(state.template)
80-
: b.template([b.quasi(template_to_string(state.template), true)], [])
79+
? template_to_functions(state.template.nodes)
80+
: b.template([b.quasi(template_to_string(state.template.nodes), true)], [])
8181
];
8282

8383
if (flags) {
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
/** @import { AST } from '#compiler' */
2+
/** @import { TemplateOperation } from '../types.js' */
3+
/** @import { Node, Element } from './types'; */
4+
5+
export class Template {
6+
/** @type {Node[]} */
7+
nodes = [];
8+
9+
/** @type {Node[][]} */
10+
#stack = [this.nodes];
11+
12+
/** @type {Element | undefined} */
13+
#element;
14+
15+
#fragment = this.nodes;
16+
17+
/**
18+
* @param {...TemplateOperation} nodes
19+
* @deprecated
20+
*/
21+
push(...nodes) {
22+
for (const node of nodes) {
23+
switch (node.kind) {
24+
case 'create_element':
25+
this.create_element(node.name);
26+
break;
27+
28+
case 'create_anchor':
29+
this.create_anchor(node.data);
30+
break;
31+
32+
case 'create_text':
33+
this.create_text(node.nodes);
34+
break;
35+
36+
case 'push_element': {
37+
this.push_element();
38+
break;
39+
}
40+
41+
case 'pop_element': {
42+
this.pop_element();
43+
break;
44+
}
45+
46+
case 'set_prop': {
47+
this.set_prop(node.key, node.value);
48+
break;
49+
}
50+
}
51+
}
52+
}
53+
54+
/** @param {string} name */
55+
create_element(name) {
56+
this.#element = {
57+
type: 'element',
58+
name,
59+
attributes: {},
60+
children: []
61+
};
62+
63+
this.#fragment.push(this.#element);
64+
}
65+
66+
/** @param {string | undefined} data */
67+
create_anchor(data) {
68+
this.#fragment.push({ type: 'anchor', data });
69+
}
70+
71+
/** @param {AST.Text[]} nodes */
72+
create_text(nodes) {
73+
this.#fragment.push({ type: 'text', nodes });
74+
}
75+
76+
push_element() {
77+
const element = /** @type {Element} */ (this.#element);
78+
this.#fragment = element.children;
79+
this.#stack.push(this.#fragment);
80+
}
81+
82+
pop_element() {
83+
this.#stack.pop();
84+
this.#fragment = /** @type {Node[]} */ (this.#stack.at(-1));
85+
}
86+
87+
/**
88+
* @param {string} key
89+
* @param {string | undefined} value
90+
*/
91+
set_prop(key, value) {
92+
const element = /** @type {Element} */ (this.#element);
93+
element.attributes[key] = value;
94+
}
95+
}

packages/svelte/src/compiler/phases/3-transform/client/transform-template/to-functions.js

Lines changed: 33 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1,70 +1,52 @@
1-
/** @import { TemplateOperation } from '../types.js' */
1+
/** @import { Node } from './types.js' */
22
/** @import { ObjectExpression, Identifier, ArrayExpression, Property, Expression, Literal } from 'estree' */
33
import * as b from '../../../../utils/builders.js';
44
import { regex_is_valid_identifier, regex_starts_with_newline } from '../../../patterns.js';
55
import fix_attribute_casing from './fix-attribute-casing.js';
66

77
/**
8-
* @param {TemplateOperation[]} items
8+
* @param {Node[]} items
99
*/
1010
export function template_to_functions(items) {
11-
let elements = b.array([]);
11+
return b.array(items.map(build));
12+
}
1213

13-
/**
14-
* @type {Array<Element>}
15-
*/
16-
let elements_stack = [];
14+
/** @param {Node} item */
15+
function build(item) {
16+
switch (item.type) {
17+
case 'element': {
18+
const element = b.object([b.prop('init', b.id('e'), b.literal(item.name))]);
19+
20+
const entries = Object.entries(item.attributes);
21+
if (entries.length > 0) {
22+
element.properties.push(
23+
b.prop(
24+
'init',
25+
b.id('p'),
26+
b.object(
27+
entries.map(([key, value]) => {
28+
return b.prop('init', b.key(key), value === undefined ? b.void0 : b.literal(value));
29+
})
30+
)
31+
)
32+
);
33+
}
1734

18-
/**
19-
* @type {Element | undefined}
20-
*/
21-
let last_current_element;
35+
if (item.children.length > 0) {
36+
element.properties.push(b.prop('init', b.id('c'), b.array(item.children.map(build))));
37+
}
2238

23-
// if the first item is a comment we need to add another comment for effect.start
24-
if (items[0].kind === 'create_anchor') {
25-
items.unshift({ kind: 'create_anchor' });
26-
}
39+
return element;
40+
}
2741

28-
for (let instruction of items) {
29-
const last_element_stack = /** @type {Element} */ (elements_stack.at(-1));
30-
/**
31-
* @param {Expression | null | void} value
32-
* @returns
33-
*/
34-
function push(value) {
35-
if (value === undefined) return;
36-
if (last_element_stack) {
37-
insert(last_element_stack, value);
38-
} else {
39-
elements.elements.push(value);
40-
}
42+
case 'anchor': {
43+
return item.data ? b.array([b.literal(item.data)]) : null;
4144
}
4245

43-
switch (instruction.kind) {
44-
case 'push_element':
45-
elements_stack.push(/** @type {Element} */ (last_current_element));
46-
break;
47-
case 'pop_element':
48-
elements_stack.pop();
49-
last_current_element = elements_stack.at(-1);
50-
break;
51-
case 'create_element':
52-
last_current_element = create_element(instruction.name);
53-
push(last_current_element);
54-
break;
55-
case 'create_text':
56-
push(create_text(last_element_stack, instruction.nodes.map((node) => node.data).join('')));
57-
break;
58-
case 'create_anchor':
59-
push(create_anchor(last_element_stack, instruction.data));
60-
break;
61-
case 'set_prop':
62-
set_prop(/** @type {Element} */ (last_current_element), instruction.key, instruction.value);
63-
break;
46+
case 'text': {
47+
return b.literal(item.nodes.map((node) => node.data).join(','));
6448
}
6549
}
66-
67-
return elements;
6850
}
6951

7052
/**

packages/svelte/src/compiler/phases/3-transform/client/transform-template/to-string.js

Lines changed: 12 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -1,111 +1,30 @@
1-
/** @import { TemplateOperation } from '../types.js' */
1+
/** @import { Node } from './types.js' */
22
import { escape_html } from '../../../../../escaping.js';
33
import { is_void } from '../../../../../utils.js';
44

55
/**
6-
* @param {TemplateOperation[]} items
6+
* @param {Node[]} items
77
*/
88
export function template_to_string(items) {
9-
/**
10-
* @type {Array<Element>}
11-
*/
12-
let elements = [];
13-
14-
/**
15-
* @type {Array<Element>}
16-
*/
17-
let elements_stack = [];
18-
19-
/**
20-
* @type {Element | undefined}
21-
*/
22-
let last_current_element;
23-
24-
/**
25-
* @template {Node} T
26-
* @param {T} child
27-
*/
28-
function insert(child) {
29-
if (last_current_element) {
30-
last_current_element.children ??= [];
31-
last_current_element.children.push(child);
32-
} else {
33-
elements.push(/** @type {Element} */ (child));
34-
}
35-
return child;
36-
}
37-
38-
for (let instruction of items) {
39-
switch (instruction.kind) {
40-
case 'push_element':
41-
elements_stack.push(/** @type {Element} */ (last_current_element));
42-
break;
43-
case 'pop_element':
44-
elements_stack.pop();
45-
last_current_element = elements_stack.at(-1);
46-
break;
47-
case 'create_element':
48-
last_current_element = insert({
49-
kind: 'element',
50-
element: instruction.name
51-
});
52-
break;
53-
case 'create_text':
54-
insert({
55-
kind: 'text',
56-
value: instruction.nodes.map((node) => node.raw).join('')
57-
});
58-
break;
59-
case 'create_anchor':
60-
insert({
61-
kind: 'anchor',
62-
data: instruction.data
63-
});
64-
break;
65-
case 'set_prop': {
66-
const el = /** @type {Element} */ (last_current_element);
67-
el.props ??= {};
68-
el.props[instruction.key] = escape_html(instruction.value, true);
69-
break;
70-
}
71-
}
72-
}
73-
74-
return elements.map((el) => stringify(el)).join('');
9+
return items.map((el) => stringify(el)).join('');
7510
}
7611

77-
/**
78-
* @typedef {{ kind: "element", element: string, props?: Record<string, string>, children?: Array<Node> }} Element
79-
*/
80-
81-
/**
82-
* @typedef {{ kind: "anchor", data?: string }} Anchor
83-
*/
84-
85-
/**
86-
* @typedef {{ kind: "text", value?: string }} Text
87-
*/
88-
89-
/**
90-
* @typedef { Element | Anchor| Text } Node
91-
*/
92-
9312
/**
9413
*
9514
* @param {Node} el
9615
* @returns
9716
*/
9817
function stringify(el) {
9918
let str = ``;
100-
if (el.kind === 'element') {
19+
if (el.type === 'element') {
10120
// we create the <tagname part
102-
str += `<${el.element}`;
21+
str += `<${el.name}`;
10322
// we concatenate all the prop to it
104-
for (let [prop, value] of Object.entries(el.props ?? {})) {
23+
for (let [prop, value] of Object.entries(el.attributes ?? {})) {
10524
if (value == null) {
10625
str += ` ${prop}`;
10726
} else {
108-
str += ` ${prop}="${value}"`;
27+
str += ` ${prop}="${escape_html(value, true)}"`;
10928
}
11029
}
11130
// then we close the opening tag
@@ -115,12 +34,12 @@ function stringify(el) {
11534
str += stringify(child);
11635
}
11736
// if it's not void we also add the closing tag
118-
if (!is_void(el.element)) {
119-
str += `</${el.element}>`;
37+
if (!is_void(el.name)) {
38+
str += `</${el.name}>`;
12039
}
121-
} else if (el.kind === 'text') {
122-
str += el.value;
123-
} else if (el.kind === 'anchor') {
40+
} else if (el.type === 'text') {
41+
str += el.nodes.map((node) => node.raw).join('');
42+
} else if (el.type === 'anchor') {
12443
if (el.data) {
12544
str += `<!--${el.data}-->`;
12645
} else {
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import type { AST } from '#compiler';
2+
3+
export interface Element {
4+
type: 'element';
5+
name: string;
6+
attributes: Record<string, string | undefined>;
7+
children: Node[];
8+
}
9+
10+
export interface Text {
11+
type: 'text';
12+
nodes: AST.Text[];
13+
}
14+
15+
export interface Anchor {
16+
type: 'anchor';
17+
data: string | undefined;
18+
}
19+
20+
export type Node = Element | Text | Anchor;

packages/svelte/src/compiler/phases/3-transform/client/types.d.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import type { AST, Namespace, StateField, ValidatedCompileOptions } from '#compi
1313
import type { TransformState } from '../types.js';
1414
import type { ComponentAnalysis } from '../../types.js';
1515
import type { SourceLocation } from '#shared';
16+
import type { Template } from './transform-template/template.js';
1617

1718
export interface ClientTransformState extends TransformState {
1819
/**
@@ -78,7 +79,7 @@ export interface ComponentClientTransformState extends ClientTransformState {
7879
/** Expressions used inside the render effect */
7980
readonly expressions: Expression[];
8081
/** The HTML template string */
81-
readonly template: TemplateOperation[];
82+
readonly template: Template;
8283
readonly locations: SourceLocation[];
8384
readonly metadata: {
8485
namespace: Namespace;

0 commit comments

Comments
 (0)