Skip to content

Commit 431d17b

Browse files
committed
more
1 parent 6814926 commit 431d17b

File tree

5 files changed

+222
-188
lines changed

5 files changed

+222
-188
lines changed

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

Lines changed: 7 additions & 187 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
/** @import { AssignmentExpression, BinaryOperator, CallExpression, Expression, ExpressionStatement, MethodDefinition, Pattern, Program, Property, PropertyDefinition, Statement, VariableDeclarator } from 'estree' */
1+
/** @import { Expression, ExpressionStatement, MethodDefinition, Pattern, Program, Property, PropertyDefinition, Statement, VariableDeclarator } from 'estree' */
22
/** @import { Binding, Namespace, SvelteNode, ValidatedCompileOptions, ValidatedModuleCompileOptions } from '#compiler' */
33
/** @import { ComponentServerTransformState, ComponentVisitors, ServerTransformState, Visitors } from './types.js' */
44
/** @import { Analysis, ComponentAnalysis } from '../../types.js' */
@@ -8,10 +8,12 @@ import { walk } from 'zimmerframe';
88
import { set_scope, get_rune } from '../../scope.js';
99
import { extract_identifiers, extract_paths, is_expression_async } from '../../../utils/ast.js';
1010
import * as b from '../../../utils/builders.js';
11-
import { transform_inspect_rune } from '../utils.js';
1211
import { filename } from '../../../state.js';
1312
import { render_stylesheet } from '../css/index.js';
13+
import { AssignmentExpression } from './visitors/javascript/AssignmentExpression.js';
14+
import { CallExpression } from './visitors/javascript/CallExpression.js';
1415
import { Identifier } from './visitors/javascript/Identifier.js';
16+
import { UpdateExpression } from './visitors/javascript/UpdateExpression.js';
1517
import { AwaitBlock } from './visitors/template/AwaitBlock.js';
1618
import { Component } from './visitors/template/Component.js';
1719
import { ConstTag } from './visitors/template/ConstTag.js';
@@ -33,7 +35,6 @@ import { SvelteFragment } from './visitors/template/SvelteFragment.js';
3335
import { SvelteHead } from './visitors/template/SvelteHead.js';
3436
import { SvelteSelf } from './visitors/template/SvelteSelf.js';
3537
import { TitleElement } from './visitors/template/TitleElement.js';
36-
import { serialize_get_binding } from './visitors/javascript/shared/utils.js';
3738

3839
/**
3940
* @param {VariableDeclarator} declarator
@@ -57,193 +58,12 @@ function create_state_declarators(declarator, scope, value) {
5758
];
5859
}
5960

60-
/**
61-
* @param {AssignmentExpression} node
62-
* @param {Pick<import('zimmerframe').Context<SvelteNode, ServerTransformState>, 'visit' | 'state'>} context
63-
*/
64-
function get_assignment_value(node, { state, visit }) {
65-
if (node.left.type === 'Identifier') {
66-
const operator = node.operator;
67-
return operator === '='
68-
? /** @type {Expression} */ (visit(node.right))
69-
: // turn something like x += 1 into x = x + 1
70-
b.binary(
71-
/** @type {BinaryOperator} */ (operator.slice(0, -1)),
72-
serialize_get_binding(node.left, state),
73-
/** @type {Expression} */ (visit(node.right))
74-
);
75-
}
76-
77-
return /** @type {Expression} */ (visit(node.right));
78-
}
79-
80-
/**
81-
* @param {string} name
82-
*/
83-
function is_store_name(name) {
84-
return name[0] === '$' && /[A-Za-z_]/.test(name[1]);
85-
}
86-
87-
/**
88-
* @param {AssignmentExpression} node
89-
* @param {import('zimmerframe').Context<SvelteNode, ServerTransformState>} context
90-
* @param {() => any} fallback
91-
* @returns {Expression}
92-
*/
93-
function serialize_set_binding(node, context, fallback) {
94-
const { state, visit } = context;
95-
96-
if (
97-
node.left.type === 'ArrayPattern' ||
98-
node.left.type === 'ObjectPattern' ||
99-
node.left.type === 'RestElement'
100-
) {
101-
// Turn assignment into an IIFE, so that `$.set` calls etc don't produce invalid code
102-
const tmp_id = context.state.scope.generate('tmp');
103-
104-
/** @type {AssignmentExpression[]} */
105-
const original_assignments = [];
106-
107-
/** @type {Expression[]} */
108-
const assignments = [];
109-
110-
const paths = extract_paths(node.left);
111-
112-
for (const path of paths) {
113-
const value = path.expression?.(b.id(tmp_id));
114-
const assignment = b.assignment('=', path.node, value);
115-
original_assignments.push(assignment);
116-
assignments.push(serialize_set_binding(assignment, context, () => assignment));
117-
}
118-
119-
if (assignments.every((assignment, i) => assignment === original_assignments[i])) {
120-
// No change to output -> nothing to transform -> we can keep the original assignment
121-
return fallback();
122-
}
123-
124-
return b.call(
125-
b.thunk(
126-
b.block([
127-
b.const(tmp_id, /** @type {Expression} */ (visit(node.right))),
128-
b.stmt(b.sequence(assignments)),
129-
b.return(b.id(tmp_id))
130-
])
131-
)
132-
);
133-
}
134-
135-
if (node.left.type !== 'Identifier' && node.left.type !== 'MemberExpression') {
136-
throw new Error(`Unexpected assignment type ${node.left.type}`);
137-
}
138-
139-
let left = node.left;
140-
141-
while (left.type === 'MemberExpression') {
142-
// @ts-expect-error
143-
left = left.object;
144-
}
145-
146-
if (left.type !== 'Identifier') {
147-
return fallback();
148-
}
149-
150-
const is_store = is_store_name(left.name);
151-
const left_name = is_store ? left.name.slice(1) : left.name;
152-
const binding = state.scope.get(left_name);
153-
154-
if (!binding) return fallback();
155-
156-
if (binding.mutation !== null) {
157-
return binding.mutation(node, context);
158-
}
159-
160-
if (
161-
binding.kind !== 'state' &&
162-
binding.kind !== 'frozen_state' &&
163-
binding.kind !== 'prop' &&
164-
binding.kind !== 'bindable_prop' &&
165-
binding.kind !== 'each' &&
166-
binding.kind !== 'legacy_reactive' &&
167-
!is_store
168-
) {
169-
// TODO error if it's a computed (or rest prop)? or does that already happen elsewhere?
170-
return fallback();
171-
}
172-
173-
const value = get_assignment_value(node, { state, visit });
174-
if (left === node.left) {
175-
if (is_store) {
176-
return b.call('$.store_set', b.id(left_name), /** @type {Expression} */ (visit(node.right)));
177-
}
178-
return fallback();
179-
} else if (is_store) {
180-
return b.call(
181-
'$.mutate_store',
182-
b.assignment('??=', b.id('$$store_subs'), b.object([])),
183-
b.literal(left.name),
184-
b.id(left_name),
185-
b.assignment(node.operator, /** @type {Pattern} */ (visit(node.left)), value)
186-
);
187-
}
188-
return fallback();
189-
}
190-
19161
/** @type {Visitors} */
19262
const global_visitors = {
63+
AssignmentExpression,
19364
Identifier,
194-
AssignmentExpression(node, context) {
195-
return serialize_set_binding(node, context, context.next);
196-
},
197-
UpdateExpression(node, context) {
198-
const { state, next } = context;
199-
const argument = node.argument;
200-
201-
if (argument.type === 'Identifier' && state.scope.get(argument.name)?.kind === 'store_sub') {
202-
return b.call(
203-
node.prefix ? '$.update_store_pre' : '$.update_store',
204-
b.assignment('??=', b.id('$$store_subs'), b.object([])),
205-
b.literal(argument.name),
206-
b.id(argument.name.slice(1)),
207-
node.operator === '--' && b.literal(-1)
208-
);
209-
}
210-
211-
return next();
212-
},
213-
CallExpression(node, context) {
214-
const rune = get_rune(node, context.state.scope);
215-
216-
if (rune === '$host') {
217-
return b.id('undefined');
218-
}
219-
220-
if (rune === '$effect.tracking') {
221-
return b.literal(false);
222-
}
223-
224-
if (rune === '$effect.root') {
225-
// ignore $effect.root() calls, just return a noop which mimics the cleanup function
226-
return b.arrow([], b.block([]));
227-
}
228-
229-
if (rune === '$state.snapshot') {
230-
return b.call('$.snapshot', /** @type {Expression} */ (context.visit(node.arguments[0])));
231-
}
232-
233-
if (rune === '$state.is') {
234-
return b.call(
235-
'Object.is',
236-
/** @type {Expression} */ (context.visit(node.arguments[0])),
237-
/** @type {Expression} */ (context.visit(node.arguments[1]))
238-
);
239-
}
240-
241-
if (rune === '$inspect' || rune === '$inspect().with') {
242-
return transform_inspect_rune(node, context);
243-
}
244-
245-
context.next();
246-
}
65+
UpdateExpression,
66+
CallExpression
24767
};
24868

24969
/** @type {Visitors} */
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 './shared/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: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
/** @import { CallExpression, Expression } from 'estree' */
2+
/** @import { Context } from '../../types' */
3+
import { get_rune } from '../../../../scope.js';
4+
import * as b from '../../../../../utils/builders.js';
5+
import { transform_inspect_rune } from '../../../utils.js';
6+
7+
/**
8+
* @param {CallExpression} node
9+
* @param {Context} context
10+
*/
11+
export function CallExpression(node, context) {
12+
const rune = get_rune(node, context.state.scope);
13+
14+
if (rune === '$host') {
15+
return b.id('undefined');
16+
}
17+
18+
if (rune === '$effect.tracking') {
19+
return b.literal(false);
20+
}
21+
22+
if (rune === '$effect.root') {
23+
// ignore $effect.root() calls, just return a noop which mimics the cleanup function
24+
return b.arrow([], b.block([]));
25+
}
26+
27+
if (rune === '$state.snapshot') {
28+
return b.call('$.snapshot', /** @type {Expression} */ (context.visit(node.arguments[0])));
29+
}
30+
31+
if (rune === '$state.is') {
32+
return b.call(
33+
'Object.is',
34+
/** @type {Expression} */ (context.visit(node.arguments[0])),
35+
/** @type {Expression} */ (context.visit(node.arguments[1]))
36+
);
37+
}
38+
39+
if (rune === '$inspect' || rune === '$inspect().with') {
40+
return transform_inspect_rune(node, context);
41+
}
42+
43+
context.next();
44+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
/** @import { UpdateExpression } from 'estree' */
2+
/** @import { Context } from '../../types' */
3+
import * as b from '../../../../../utils/builders.js';
4+
5+
/**
6+
* @param {UpdateExpression} node
7+
* @param {Context} context
8+
*/
9+
export function UpdateExpression(node, context) {
10+
const argument = node.argument;
11+
12+
if (
13+
argument.type === 'Identifier' &&
14+
context.state.scope.get(argument.name)?.kind === 'store_sub'
15+
) {
16+
return b.call(
17+
node.prefix ? '$.update_store_pre' : '$.update_store',
18+
b.assignment('??=', b.id('$$store_subs'), b.object([])),
19+
b.literal(argument.name),
20+
b.id(argument.name.slice(1)),
21+
node.operator === '--' && b.literal(-1)
22+
);
23+
}
24+
25+
return context.next();
26+
}

0 commit comments

Comments
 (0)