Skip to content

Commit 038754b

Browse files
authored
chore: client transform visitors refactor (#12683)
* start refactoring client transform visitor code * more * more * more * more * more * more * more * more * more * more * more * more * more * more * more * tweak * painful * more * simplify * more * more * more * more * more * tidy up * changeset
1 parent 9eca3d0 commit 038754b

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

71 files changed

+5050
-4635
lines changed

.changeset/twelve-scissors-kneel.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'svelte': patch
3+
---
4+
5+
chore: internal refactoring of client transform visitors

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

Lines changed: 114 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -5,51 +5,122 @@
55
import { walk } from 'zimmerframe';
66
import * as b from '../../../utils/builders.js';
77
import { set_scope } from '../../scope.js';
8-
import { template_visitors } from './visitors/template.js';
9-
import { global_visitors } from './visitors/global.js';
10-
import { javascript_visitors } from './visitors/javascript.js';
11-
import { javascript_visitors_runes } from './visitors/javascript-runes.js';
12-
import { javascript_visitors_legacy } from './visitors/javascript-legacy.js';
138
import { serialize_get_binding } from './utils.js';
149
import { render_stylesheet } from '../css/index.js';
1510
import { dev, filename } from '../../../state.js';
11+
import { AnimateDirective } from './visitors/AnimateDirective.js';
12+
import { ArrowFunctionExpression } from './visitors/ArrowFunctionExpression.js';
13+
import { AssignmentExpression } from './visitors/AssignmentExpression.js';
14+
import { Attribute } from './visitors/Attribute.js';
15+
import { AwaitBlock } from './visitors/AwaitBlock.js';
16+
import { BinaryExpression } from './visitors/BinaryExpression.js';
17+
import { BindDirective } from './visitors/BindDirective.js';
18+
import { BreakStatement } from './visitors/BreakStatement.js';
19+
import { CallExpression } from './visitors/CallExpression.js';
20+
import { ClassBody } from './visitors/ClassBody.js';
21+
import { Comment } from './visitors/Comment.js';
22+
import { Component } from './visitors/Component.js';
23+
import { ConstTag } from './visitors/ConstTag.js';
24+
import { DebugTag } from './visitors/DebugTag.js';
25+
import { EachBlock } from './visitors/EachBlock.js';
26+
import { ExportNamedDeclaration } from './visitors/ExportNamedDeclaration.js';
27+
import { ExpressionStatement } from './visitors/ExpressionStatement.js';
28+
import { Fragment } from './visitors/Fragment.js';
29+
import { FunctionDeclaration } from './visitors/FunctionDeclaration.js';
30+
import { FunctionExpression } from './visitors/FunctionExpression.js';
31+
import { HtmlTag } from './visitors/HtmlTag.js';
32+
import { Identifier } from './visitors/Identifier.js';
33+
import { IfBlock } from './visitors/IfBlock.js';
34+
import { ImportDeclaration } from './visitors/ImportDeclaration.js';
35+
import { KeyBlock } from './visitors/KeyBlock.js';
36+
import { LabeledStatement } from './visitors/LabeledStatement.js';
37+
import { LetDirective } from './visitors/LetDirective.js';
38+
import { MemberExpression } from './visitors/MemberExpression.js';
39+
import { OnDirective } from './visitors/OnDirective.js';
40+
import { RegularElement } from './visitors/RegularElement.js';
41+
import { RenderTag } from './visitors/RenderTag.js';
42+
import { SlotElement } from './visitors/SlotElement.js';
43+
import { SnippetBlock } from './visitors/SnippetBlock.js';
44+
import { SpreadAttribute } from './visitors/SpreadAttribute.js';
45+
import { SvelteBody } from './visitors/SvelteBody.js';
46+
import { SvelteComponent } from './visitors/SvelteComponent.js';
47+
import { SvelteDocument } from './visitors/SvelteDocument.js';
48+
import { SvelteElement } from './visitors/SvelteElement.js';
49+
import { SvelteFragment } from './visitors/SvelteFragment.js';
50+
import { SvelteHead } from './visitors/SvelteHead.js';
51+
import { SvelteSelf } from './visitors/SvelteSelf.js';
52+
import { SvelteWindow } from './visitors/SvelteWindow.js';
53+
import { TitleElement } from './visitors/TitleElement.js';
54+
import { TransitionDirective } from './visitors/TransitionDirective.js';
55+
import { UpdateExpression } from './visitors/UpdateExpression.js';
56+
import { UseDirective } from './visitors/UseDirective.js';
57+
import { VariableDeclaration } from './visitors/VariableDeclaration.js';
58+
59+
/** @type {Visitors} */
60+
const visitors = {
61+
_: set_scope,
62+
AnimateDirective,
63+
ArrowFunctionExpression,
64+
AssignmentExpression,
65+
Attribute,
66+
AwaitBlock,
67+
BinaryExpression,
68+
BindDirective,
69+
BreakStatement,
70+
CallExpression,
71+
ClassBody,
72+
Comment,
73+
Component,
74+
ConstTag,
75+
DebugTag,
76+
EachBlock,
77+
ExportNamedDeclaration,
78+
ExpressionStatement,
79+
Fragment,
80+
FunctionDeclaration,
81+
FunctionExpression,
82+
HtmlTag,
83+
Identifier,
84+
IfBlock,
85+
ImportDeclaration,
86+
KeyBlock,
87+
LabeledStatement,
88+
LetDirective,
89+
MemberExpression,
90+
OnDirective,
91+
RegularElement,
92+
RenderTag,
93+
SlotElement,
94+
SnippetBlock,
95+
SpreadAttribute,
96+
SvelteBody,
97+
SvelteComponent,
98+
SvelteDocument,
99+
SvelteElement,
100+
SvelteFragment,
101+
SvelteHead,
102+
SvelteSelf,
103+
SvelteWindow,
104+
TitleElement,
105+
TransitionDirective,
106+
UpdateExpression,
107+
UseDirective,
108+
VariableDeclaration
109+
};
16110

17111
/**
18-
* This function ensures visitor sets don't accidentally clobber each other
19-
* @param {...Visitors} array
20-
* @returns {Visitors}
21-
*/
22-
function combine_visitors(...array) {
23-
/** @type {Record<string, any>} */
24-
const visitors = {};
25-
26-
for (const member of array) {
27-
for (const key in member) {
28-
if (visitors[key]) {
29-
throw new Error(`Duplicate visitor: ${key}`);
30-
}
31-
32-
// @ts-ignore
33-
visitors[key] = member[key];
34-
}
35-
}
36-
37-
return visitors;
38-
}
39-
40-
/**
41-
* @param {string} source
42112
* @param {ComponentAnalysis} analysis
43113
* @param {ValidatedCompileOptions} options
44114
* @returns {ESTree.Program}
45115
*/
46-
export function client_component(source, analysis, options) {
116+
export function client_component(analysis, options) {
47117
/** @type {ComponentClientTransformState} */
48118
const state = {
49119
analysis,
50120
options,
51121
scope: analysis.module.scope,
52-
scopes: analysis.template.scopes,
122+
scopes: analysis.module.scopes,
123+
is_instance: false,
53124
hoisted: [b.import_all('$', 'svelte/internal/client')],
54125
node: /** @type {any} */ (null), // populated by the root node
55126
legacy_reactive_statements: new Map(),
@@ -78,57 +149,25 @@ export function client_component(source, analysis, options) {
78149
};
79150

80151
const module = /** @type {ESTree.Program} */ (
81-
walk(
82-
/** @type {SvelteNode} */ (analysis.module.ast),
83-
state,
84-
combine_visitors(
85-
set_scope(analysis.module.scopes),
86-
global_visitors,
87-
// @ts-expect-error TODO
88-
javascript_visitors,
89-
analysis.runes ? javascript_visitors_runes : javascript_visitors_legacy
90-
)
91-
)
152+
walk(/** @type {SvelteNode} */ (analysis.module.ast), state, visitors)
92153
);
93154

94-
const instance_state = { ...state, scope: analysis.instance.scope };
155+
const instance_state = {
156+
...state,
157+
scope: analysis.instance.scope,
158+
scopes: analysis.instance.scopes,
159+
is_instance: true
160+
};
161+
95162
const instance = /** @type {ESTree.Program} */ (
96-
walk(
97-
/** @type {SvelteNode} */ (analysis.instance.ast),
98-
instance_state,
99-
combine_visitors(
100-
set_scope(analysis.instance.scopes),
101-
global_visitors,
102-
// @ts-expect-error TODO
103-
javascript_visitors,
104-
analysis.runes ? javascript_visitors_runes : javascript_visitors_legacy,
105-
{
106-
ImportDeclaration(node) {
107-
state.hoisted.push(node);
108-
return b.empty;
109-
},
110-
ExportNamedDeclaration(node, context) {
111-
if (node.declaration) {
112-
return context.visit(node.declaration);
113-
}
114-
115-
return b.empty;
116-
}
117-
}
118-
)
119-
)
163+
walk(/** @type {SvelteNode} */ (analysis.instance.ast), instance_state, visitors)
120164
);
121165

122166
const template = /** @type {ESTree.Program} */ (
123167
walk(
124168
/** @type {SvelteNode} */ (analysis.template.ast),
125-
{ ...state, scope: analysis.instance.scope },
126-
combine_visitors(
127-
set_scope(analysis.template.scopes),
128-
global_visitors,
129-
// @ts-expect-error TODO
130-
template_visitors
131-
)
169+
{ ...state, scope: analysis.instance.scope, scopes: analysis.template.scopes },
170+
visitors
132171
)
133172
);
134173

@@ -589,17 +628,7 @@ export function client_module(analysis, options) {
589628
};
590629

591630
const module = /** @type {ESTree.Program} */ (
592-
walk(
593-
/** @type {SvelteNode} */ (analysis.module.ast),
594-
state,
595-
combine_visitors(
596-
set_scope(analysis.module.scopes),
597-
global_visitors,
598-
// @ts-expect-error
599-
javascript_visitors,
600-
javascript_visitors_runes
601-
)
602-
)
631+
walk(/** @type {SvelteNode} */ (analysis.module.ast), state, visitors)
603632
);
604633

605634
return {

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
@@ -35,6 +35,7 @@ export interface ComponentClientTransformState extends ClientTransformState {
3535
readonly options: ValidatedCompileOptions;
3636
readonly hoisted: Array<Statement | ModuleDeclaration>;
3737
readonly events: Set<string>;
38+
readonly is_instance: boolean;
3839

3940
/** Stuff that happens before the render effect(s) */
4041
readonly before_init: Statement[];
@@ -78,7 +79,7 @@ export interface StateField {
7879
}
7980

8081
export type Context = import('zimmerframe').Context<SvelteNode, ClientTransformState>;
81-
export type Visitors = import('zimmerframe').Visitors<SvelteNode, ClientTransformState>;
82+
export type Visitors = import('zimmerframe').Visitors<SvelteNode, any>;
8283

8384
export type ComponentContext = import('zimmerframe').Context<
8485
SvelteNode,

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

Lines changed: 0 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -476,37 +476,6 @@ export function serialize_proxy_reassignment(value, proxy_reference) {
476476
: b.call('$.proxy', value);
477477
}
478478

479-
/**
480-
* @param {ArrowFunctionExpression | FunctionExpression} node
481-
* @param {ComponentContext} context
482-
*/
483-
export const function_visitor = (node, context) => {
484-
const metadata = node.metadata;
485-
486-
let state = context.state;
487-
488-
if (node.type === 'FunctionExpression') {
489-
const parent = /** @type {Node} */ (context.path.at(-1));
490-
const in_constructor = parent.type === 'MethodDefinition' && parent.kind === 'constructor';
491-
492-
state = { ...context.state, in_constructor };
493-
} else {
494-
state = { ...context.state, in_constructor: false };
495-
}
496-
497-
if (metadata?.hoistable === true) {
498-
const params = serialize_hoistable_params(node, context);
499-
500-
return /** @type {FunctionExpression} */ ({
501-
...node,
502-
params,
503-
body: context.visit(node.body, state)
504-
});
505-
}
506-
507-
context.next(state);
508-
};
509-
510479
/**
511480
* @param {FunctionDeclaration | FunctionExpression | ArrowFunctionExpression} node
512481
* @param {ComponentContext} context
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
/** @import { Expression } from 'estree' */
2+
/** @import { AnimateDirective } from '#compiler' */
3+
/** @import { ComponentContext } from '../types' */
4+
import * as b from '../../../../utils/builders.js';
5+
import { parse_directive_name } from './shared/utils.js';
6+
7+
/**
8+
* @param {AnimateDirective} node
9+
* @param {ComponentContext} context
10+
*/
11+
export function AnimateDirective(node, context) {
12+
const expression =
13+
node.expression === null
14+
? b.literal(null)
15+
: b.thunk(/** @type {Expression} */ (context.visit(node.expression)));
16+
17+
// in after_update to ensure it always happens after bind:this
18+
context.state.after_update.push(
19+
b.stmt(
20+
b.call(
21+
'$.animation',
22+
context.state.node,
23+
b.thunk(/** @type {Expression} */ (context.visit(parse_directive_name(node.name)))),
24+
expression
25+
)
26+
)
27+
);
28+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
/** @import { ArrowFunctionExpression } from 'estree' */
2+
/** @import { ComponentContext } from '../types' */
3+
import { visit_function } from './shared/function.js';
4+
5+
/**
6+
* @param {ArrowFunctionExpression} node
7+
* @param {ComponentContext} context
8+
*/
9+
export function ArrowFunctionExpression(node, context) {
10+
return visit_function(node, context);
11+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
/** @import { AssignmentExpression } from 'estree' */
2+
/** @import { Context } from '../types' */
3+
import { serialize_set_binding } from '../utils.js';
4+
5+
/**
6+
* @param {AssignmentExpression} node
7+
* @param {Context} context
8+
*/
9+
export function AssignmentExpression(node, context) {
10+
return serialize_set_binding(node, context, context.next);
11+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
/** @import { Attribute } from '#compiler' */
2+
/** @import { ComponentContext } from '../types' */
3+
import { is_event_attribute } from '../../../../utils/ast.js';
4+
import { serialize_event_attribute } from './shared/element.js';
5+
6+
/**
7+
* @param {Attribute} node
8+
* @param {ComponentContext} context
9+
*/
10+
export function Attribute(node, context) {
11+
if (is_event_attribute(node)) {
12+
serialize_event_attribute(node, context);
13+
}
14+
}

0 commit comments

Comments
 (0)