ripple 0.2.108 → 0.2.110
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/compiler/phases/1-parse/index.js +31 -10
- package/src/compiler/phases/2-analyze/index.js +90 -1
- package/src/compiler/phases/3-transform/client/index.js +48 -7
- package/src/compiler/phases/3-transform/server/index.js +134 -8
- package/src/compiler/scope.js +15 -1
- package/src/compiler/types/index.d.ts +2 -2
- package/src/compiler/utils.js +2 -0
- package/src/runtime/internal/client/composite.js +37 -5
- package/src/runtime/internal/client/for.js +48 -20
- package/src/runtime/internal/client/index.js +2 -0
- package/src/runtime/internal/client/render.js +47 -20
- package/src/runtime/internal/client/rpc.js +14 -0
- package/src/utils/builders.js +15 -2
- package/tests/client/__snapshots__/compiler.test.ripple.snap +13 -0
- package/tests/client/__snapshots__/for.test.ripple.snap +5 -5
- package/tests/client/compiler.test.ripple +15 -0
- package/tests/client/composite.test.ripple +31 -0
- package/tests/client/dynamic-elements.test.ripple +207 -0
- package/tests/client/for.test.ripple +7 -7
- package/tests/client/switch.test.ripple +152 -0
- package/tests/server/__snapshots__/compiler.test.ripple.snap +37 -0
- package/tests/server/compiler.test.ripple +39 -0
- package/tests/server/switch.test.ripple +27 -0
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"description": "Ripple is an elegant TypeScript UI framework",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"author": "Dominic Gannaway",
|
|
6
|
-
"version": "0.2.
|
|
6
|
+
"version": "0.2.110",
|
|
7
7
|
"type": "module",
|
|
8
8
|
"module": "src/runtime/index-client.js",
|
|
9
9
|
"main": "src/runtime/index-client.js",
|
|
@@ -165,18 +165,19 @@ function RipplePlugin(config) {
|
|
|
165
165
|
|
|
166
166
|
// Check if this is #server
|
|
167
167
|
if (this.input.slice(this.pos, this.pos + 7) === '#server') {
|
|
168
|
-
// Check that next char after 'server' is whitespace or
|
|
168
|
+
// Check that next char after 'server' is whitespace, {, . (dot), or EOF
|
|
169
169
|
const charAfter =
|
|
170
170
|
this.pos + 7 < this.input.length ? this.input.charCodeAt(this.pos + 7) : -1;
|
|
171
171
|
if (
|
|
172
|
-
charAfter === 123 ||
|
|
173
|
-
charAfter ===
|
|
174
|
-
charAfter ===
|
|
175
|
-
charAfter ===
|
|
176
|
-
charAfter ===
|
|
177
|
-
charAfter ===
|
|
172
|
+
charAfter === 123 || // {
|
|
173
|
+
charAfter === 46 || // . (dot)
|
|
174
|
+
charAfter === 32 || // space
|
|
175
|
+
charAfter === 9 || // tab
|
|
176
|
+
charAfter === 10 || // newline
|
|
177
|
+
charAfter === 13 || // carriage return
|
|
178
|
+
charAfter === -1 // EOF
|
|
178
179
|
) {
|
|
179
|
-
// { or whitespace or EOF
|
|
180
|
+
// { or . or whitespace or EOF
|
|
180
181
|
this.pos += 7; // consume '#server'
|
|
181
182
|
return this.finishToken(tt.name, '#server');
|
|
182
183
|
}
|
|
@@ -379,6 +380,13 @@ function RipplePlugin(config) {
|
|
|
379
380
|
return this.parseTrackedExpression();
|
|
380
381
|
}
|
|
381
382
|
|
|
383
|
+
// Check if this is #server identifier for server function calls
|
|
384
|
+
if (this.type === tt.name && this.value === '#server') {
|
|
385
|
+
const node = this.startNode();
|
|
386
|
+
this.next();
|
|
387
|
+
return this.finishNode(node, 'ServerIdentifier');
|
|
388
|
+
}
|
|
389
|
+
|
|
382
390
|
// Check if this is a tuple literal starting with #[
|
|
383
391
|
if (this.type === tt.bracketL && this.value === '#[') {
|
|
384
392
|
return this.parseTrackedArrayExpression();
|
|
@@ -388,7 +396,6 @@ function RipplePlugin(config) {
|
|
|
388
396
|
|
|
389
397
|
return super.parseExprAtom(refDestructuringErrors, forNew, forInit);
|
|
390
398
|
}
|
|
391
|
-
|
|
392
399
|
/**
|
|
393
400
|
* Parse `@(expression)` syntax for unboxing tracked values
|
|
394
401
|
* Creates a TrackedExpression node with the argument property
|
|
@@ -419,7 +426,21 @@ function RipplePlugin(config) {
|
|
|
419
426
|
parseServerBlock() {
|
|
420
427
|
const node = this.startNode();
|
|
421
428
|
this.next();
|
|
422
|
-
|
|
429
|
+
|
|
430
|
+
const body = this.startNode();
|
|
431
|
+
node.body = body;
|
|
432
|
+
body.body = [];
|
|
433
|
+
|
|
434
|
+
this.expect(tt.braceL);
|
|
435
|
+
this.enterScope(0);
|
|
436
|
+
while (this.type !== tt.braceR) {
|
|
437
|
+
var stmt = this.parseStatement(null, true);
|
|
438
|
+
body.body.push(stmt);
|
|
439
|
+
}
|
|
440
|
+
this.next();
|
|
441
|
+
this.exitScope();
|
|
442
|
+
this.finishNode(body, 'BlockStatement');
|
|
443
|
+
|
|
423
444
|
this.awaitPos = 0;
|
|
424
445
|
return this.finishNode(node, 'ServerBlock');
|
|
425
446
|
}
|
|
@@ -96,11 +96,46 @@ const visitors = {
|
|
|
96
96
|
return context.next({ ...context.state, function_depth: 0, expression: null });
|
|
97
97
|
},
|
|
98
98
|
|
|
99
|
+
ServerBlock(node, context) {
|
|
100
|
+
node.metadata = {
|
|
101
|
+
...node.metadata,
|
|
102
|
+
exports: [],
|
|
103
|
+
};
|
|
104
|
+
context.visit(node.body, { ...context.state, inside_server_block: true });
|
|
105
|
+
},
|
|
106
|
+
|
|
99
107
|
Identifier(node, context) {
|
|
100
108
|
const binding = context.state.scope.get(node.name);
|
|
101
109
|
const parent = context.path.at(-1);
|
|
102
110
|
|
|
103
|
-
if (
|
|
111
|
+
if (
|
|
112
|
+
is_reference(node, /** @type {Node} */ (parent)) &&
|
|
113
|
+
binding &&
|
|
114
|
+
context.state.inside_server_block
|
|
115
|
+
) {
|
|
116
|
+
let current_scope = binding.scope;
|
|
117
|
+
|
|
118
|
+
while (current_scope !== null) {
|
|
119
|
+
if (current_scope.server_block) {
|
|
120
|
+
break;
|
|
121
|
+
}
|
|
122
|
+
const parent_scope = current_scope.parent;
|
|
123
|
+
if (parent_scope === null) {
|
|
124
|
+
error(
|
|
125
|
+
`Cannot reference client-side variable "${node.name}" from a server block`,
|
|
126
|
+
context.state.analysis.module.filename,
|
|
127
|
+
node,
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
current_scope = parent_scope;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if (
|
|
135
|
+
binding?.kind === 'prop' ||
|
|
136
|
+
binding?.kind === 'prop_fallback' ||
|
|
137
|
+
binding?.kind === 'for_pattern'
|
|
138
|
+
) {
|
|
104
139
|
mark_as_tracked(context.path);
|
|
105
140
|
if (context.state.metadata?.tracking === false) {
|
|
106
141
|
context.state.metadata.tracking = true;
|
|
@@ -312,6 +347,7 @@ const visitors = {
|
|
|
312
347
|
}
|
|
313
348
|
|
|
314
349
|
node.metadata = {
|
|
350
|
+
...node.metadata,
|
|
315
351
|
has_template: false,
|
|
316
352
|
};
|
|
317
353
|
|
|
@@ -347,7 +383,38 @@ const visitors = {
|
|
|
347
383
|
}
|
|
348
384
|
}
|
|
349
385
|
|
|
386
|
+
if (node.key) {
|
|
387
|
+
const state = context.state;
|
|
388
|
+
const pattern = node.left.declarations[0].id;
|
|
389
|
+
const paths = extract_paths(pattern);
|
|
390
|
+
const scope = state.scopes.get(node);
|
|
391
|
+
const pattern_id = b.id(scope.generate('pattern'));
|
|
392
|
+
|
|
393
|
+
node.left.declarations[0].id = pattern_id;
|
|
394
|
+
|
|
395
|
+
for (const path of paths) {
|
|
396
|
+
const name = path.node.name;
|
|
397
|
+
const binding = context.state.scope.get(name);
|
|
398
|
+
|
|
399
|
+
binding.kind = 'for_pattern';
|
|
400
|
+
if (!binding.metadata) {
|
|
401
|
+
binding.metadata = {
|
|
402
|
+
pattern: pattern_id,
|
|
403
|
+
};
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
if (binding !== null) {
|
|
407
|
+
binding.transform = {
|
|
408
|
+
read: () => {
|
|
409
|
+
return path.expression(b.call('_$_.get', pattern_id));
|
|
410
|
+
},
|
|
411
|
+
};
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
|
|
350
416
|
node.metadata = {
|
|
417
|
+
...node.metadata,
|
|
351
418
|
has_template: false,
|
|
352
419
|
};
|
|
353
420
|
context.next();
|
|
@@ -361,6 +428,23 @@ const visitors = {
|
|
|
361
428
|
}
|
|
362
429
|
},
|
|
363
430
|
|
|
431
|
+
ExportNamedDeclaration(node, context) {
|
|
432
|
+
if (!context.state.inside_server_block) {
|
|
433
|
+
return context.next();
|
|
434
|
+
}
|
|
435
|
+
const server_block = context.path.find((n) => n.type === 'ServerBlock');
|
|
436
|
+
const declaration = node.declaration;
|
|
437
|
+
|
|
438
|
+
if (declaration && declaration.type === 'FunctionDeclaration') {
|
|
439
|
+
server_block.metadata.exports.push(declaration.id.name);
|
|
440
|
+
} else {
|
|
441
|
+
// TODO
|
|
442
|
+
throw new Error('Not implemented');
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
return context.next();
|
|
446
|
+
},
|
|
447
|
+
|
|
364
448
|
TSTypeReference(node, context) {
|
|
365
449
|
// bug in our acorn pasrer: it uses typeParameters instead of typeArguments
|
|
366
450
|
if (node.typeParameters) {
|
|
@@ -376,6 +460,7 @@ const visitors = {
|
|
|
376
460
|
}
|
|
377
461
|
|
|
378
462
|
node.metadata = {
|
|
463
|
+
...node.metadata,
|
|
379
464
|
has_template: false,
|
|
380
465
|
};
|
|
381
466
|
|
|
@@ -391,6 +476,7 @@ const visitors = {
|
|
|
391
476
|
|
|
392
477
|
if (node.alternate) {
|
|
393
478
|
node.metadata = {
|
|
479
|
+
...node.metadata,
|
|
394
480
|
has_template: false,
|
|
395
481
|
};
|
|
396
482
|
context.visit(node.alternate, context.state);
|
|
@@ -417,6 +503,7 @@ const visitors = {
|
|
|
417
503
|
}
|
|
418
504
|
|
|
419
505
|
node.metadata = {
|
|
506
|
+
...node.metadata,
|
|
420
507
|
has_template: false,
|
|
421
508
|
};
|
|
422
509
|
|
|
@@ -431,6 +518,7 @@ const visitors = {
|
|
|
431
518
|
}
|
|
432
519
|
|
|
433
520
|
node.metadata = {
|
|
521
|
+
...node.metadata,
|
|
434
522
|
has_template: false,
|
|
435
523
|
};
|
|
436
524
|
|
|
@@ -672,6 +760,7 @@ export function analyze(ast, filename) {
|
|
|
672
760
|
scopes,
|
|
673
761
|
analysis,
|
|
674
762
|
inside_head: false,
|
|
763
|
+
inside_server_block: false,
|
|
675
764
|
},
|
|
676
765
|
visitors,
|
|
677
766
|
);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/** @import {Expression, FunctionExpression,
|
|
1
|
+
/** @import {Expression, FunctionExpression, Node, Program} from 'estree' */
|
|
2
2
|
|
|
3
3
|
import { walk } from 'zimmerframe';
|
|
4
4
|
import path from 'node:path';
|
|
@@ -37,6 +37,7 @@ import is_reference from 'is-reference';
|
|
|
37
37
|
import { object } from '../../../../utils/ast.js';
|
|
38
38
|
import { render_stylesheets } from '../stylesheet.js';
|
|
39
39
|
import { is_event_attribute, is_passive_event } from '../../../../utils/events.js';
|
|
40
|
+
import { createHash } from 'node:crypto';
|
|
40
41
|
|
|
41
42
|
function add_ripple_internal_import(context) {
|
|
42
43
|
if (!context.state.to_ts) {
|
|
@@ -54,6 +55,7 @@ function visit_function(node, context) {
|
|
|
54
55
|
const state = context.state;
|
|
55
56
|
|
|
56
57
|
delete node.returnType;
|
|
58
|
+
delete node.typeParameters;
|
|
57
59
|
|
|
58
60
|
for (const param of node.params) {
|
|
59
61
|
delete param.typeAnnotation;
|
|
@@ -168,7 +170,8 @@ const visitors = {
|
|
|
168
170
|
(node.tracked ||
|
|
169
171
|
binding?.kind === 'prop' ||
|
|
170
172
|
binding?.kind === 'index' ||
|
|
171
|
-
binding?.kind === 'prop_fallback'
|
|
173
|
+
binding?.kind === 'prop_fallback' ||
|
|
174
|
+
binding?.kind === 'for_pattern') &&
|
|
172
175
|
binding?.node !== node
|
|
173
176
|
) {
|
|
174
177
|
if (context.state.metadata?.tracking === false) {
|
|
@@ -178,14 +181,17 @@ const visitors = {
|
|
|
178
181
|
add_ripple_internal_import(context);
|
|
179
182
|
return b.call('_$_.get', build_getter(node, context));
|
|
180
183
|
}
|
|
181
|
-
|
|
182
|
-
add_ripple_internal_import(context);
|
|
183
|
-
return build_getter(node, context);
|
|
184
184
|
}
|
|
185
|
+
add_ripple_internal_import(context);
|
|
186
|
+
return build_getter(node, context);
|
|
185
187
|
}
|
|
186
188
|
}
|
|
187
189
|
},
|
|
188
190
|
|
|
191
|
+
ServerIdentifier(node, context) {
|
|
192
|
+
return b.id('_$_server_$_');
|
|
193
|
+
},
|
|
194
|
+
|
|
189
195
|
ImportDeclaration(node, context) {
|
|
190
196
|
if (!context.state.to_ts && node.importKind === 'type') {
|
|
191
197
|
return b.empty;
|
|
@@ -1200,6 +1206,14 @@ const visitors = {
|
|
|
1200
1206
|
return context.next();
|
|
1201
1207
|
},
|
|
1202
1208
|
|
|
1209
|
+
TSInstantiationExpression(node, context) {
|
|
1210
|
+
if (!context.state.to_ts) {
|
|
1211
|
+
// In JavaScript, just return the expression wrapped in parentheses
|
|
1212
|
+
return b.sequence([context.visit(node.expression)]);
|
|
1213
|
+
}
|
|
1214
|
+
return context.next();
|
|
1215
|
+
},
|
|
1216
|
+
|
|
1203
1217
|
ExportNamedDeclaration(node, context) {
|
|
1204
1218
|
if (!context.state.to_ts && node.exportKind === 'type') {
|
|
1205
1219
|
return b.empty;
|
|
@@ -1282,8 +1296,34 @@ const visitors = {
|
|
|
1282
1296
|
return b.block(statements);
|
|
1283
1297
|
},
|
|
1284
1298
|
|
|
1285
|
-
ServerBlock() {
|
|
1286
|
-
|
|
1299
|
+
ServerBlock(node, context) {
|
|
1300
|
+
const exports = node.metadata.exports;
|
|
1301
|
+
|
|
1302
|
+
if (exports.length === 0) {
|
|
1303
|
+
return b.empty;
|
|
1304
|
+
}
|
|
1305
|
+
const file_path = context.state.filename;
|
|
1306
|
+
|
|
1307
|
+
return b.const(
|
|
1308
|
+
'_$_server_$_',
|
|
1309
|
+
b.object(
|
|
1310
|
+
exports.map((name) => {
|
|
1311
|
+
const func_path = file_path + '#' + name;
|
|
1312
|
+
// needs to be a sha256 hash of func_path, to avoid leaking file structure
|
|
1313
|
+
const hash = createHash('sha256').update(func_path).digest('hex').slice(0, 8);
|
|
1314
|
+
|
|
1315
|
+
return b.prop(
|
|
1316
|
+
'init',
|
|
1317
|
+
b.id(name),
|
|
1318
|
+
b.function(
|
|
1319
|
+
null,
|
|
1320
|
+
[b.rest(b.id('args'))],
|
|
1321
|
+
b.block([b.return(b.call('_$_.rpc', b.literal(hash), b.id('args')))]),
|
|
1322
|
+
),
|
|
1323
|
+
);
|
|
1324
|
+
}),
|
|
1325
|
+
),
|
|
1326
|
+
);
|
|
1287
1327
|
},
|
|
1288
1328
|
|
|
1289
1329
|
Program(node, context) {
|
|
@@ -1792,6 +1832,7 @@ export function transform_client(filename, source, analysis, to_ts) {
|
|
|
1792
1832
|
scopes: analysis.scopes,
|
|
1793
1833
|
stylesheets: [],
|
|
1794
1834
|
to_ts,
|
|
1835
|
+
filename,
|
|
1795
1836
|
};
|
|
1796
1837
|
|
|
1797
1838
|
const program = /** @type {Program} */ (
|
|
@@ -17,6 +17,7 @@ import is_reference from 'is-reference';
|
|
|
17
17
|
import { escape } from '../../../../utils/escaping.js';
|
|
18
18
|
import { is_event_attribute } from '../../../../utils/events.js';
|
|
19
19
|
import { render_stylesheets } from '../stylesheet.js';
|
|
20
|
+
import { createHash } from 'node:crypto';
|
|
20
21
|
|
|
21
22
|
function add_ripple_internal_import(context) {
|
|
22
23
|
if (!context.state.to_ts) {
|
|
@@ -31,6 +32,10 @@ function transform_children(children, context) {
|
|
|
31
32
|
const normalized = normalize_children(children, context);
|
|
32
33
|
|
|
33
34
|
for (const node of normalized) {
|
|
35
|
+
if (node.type === 'BreakStatement') {
|
|
36
|
+
state.init.push(b.break);
|
|
37
|
+
continue;
|
|
38
|
+
}
|
|
34
39
|
if (
|
|
35
40
|
node.type === 'VariableDeclaration' ||
|
|
36
41
|
node.type === 'ExpressionStatement' ||
|
|
@@ -132,6 +137,39 @@ const visitors = {
|
|
|
132
137
|
return context.next();
|
|
133
138
|
},
|
|
134
139
|
|
|
140
|
+
FunctionDeclaration(node, context) {
|
|
141
|
+
if (!context.state.to_ts) {
|
|
142
|
+
delete node.returnType;
|
|
143
|
+
delete node.typeParameters;
|
|
144
|
+
for (const param of node.params) {
|
|
145
|
+
delete param.typeAnnotation;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
return context.next();
|
|
149
|
+
},
|
|
150
|
+
|
|
151
|
+
FunctionExpression(node, context) {
|
|
152
|
+
if (!context.state.to_ts) {
|
|
153
|
+
delete node.returnType;
|
|
154
|
+
delete node.typeParameters;
|
|
155
|
+
for (const param of node.params) {
|
|
156
|
+
delete param.typeAnnotation;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
return context.next();
|
|
160
|
+
},
|
|
161
|
+
|
|
162
|
+
ArrowFunctionExpression(node, context) {
|
|
163
|
+
if (!context.state.to_ts) {
|
|
164
|
+
delete node.returnType;
|
|
165
|
+
delete node.typeParameters;
|
|
166
|
+
for (const param of node.params) {
|
|
167
|
+
delete param.typeAnnotation;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
return context.next();
|
|
171
|
+
},
|
|
172
|
+
|
|
135
173
|
TSAsExpression(node, context) {
|
|
136
174
|
if (!context.state.to_ts) {
|
|
137
175
|
return context.visit(node.expression);
|
|
@@ -139,6 +177,14 @@ const visitors = {
|
|
|
139
177
|
return context.next();
|
|
140
178
|
},
|
|
141
179
|
|
|
180
|
+
TSInstantiationExpression(node, context) {
|
|
181
|
+
if (!context.state.to_ts) {
|
|
182
|
+
// In JavaScript, just return the expression wrapped in parentheses
|
|
183
|
+
return b.sequence([context.visit(node.expression)]);
|
|
184
|
+
}
|
|
185
|
+
return context.next();
|
|
186
|
+
},
|
|
187
|
+
|
|
142
188
|
TSTypeAliasDeclaration(_, context) {
|
|
143
189
|
if (!context.state.to_ts) {
|
|
144
190
|
return b.empty;
|
|
@@ -157,8 +203,23 @@ const visitors = {
|
|
|
157
203
|
if (!context.state.to_ts && node.exportKind === 'type') {
|
|
158
204
|
return b.empty;
|
|
159
205
|
}
|
|
160
|
-
|
|
161
|
-
|
|
206
|
+
if (!context.state.inside_server_block) {
|
|
207
|
+
return context.next();
|
|
208
|
+
}
|
|
209
|
+
const declaration = node.declaration;
|
|
210
|
+
|
|
211
|
+
if (declaration && declaration.type === 'FunctionDeclaration') {
|
|
212
|
+
return b.stmt(
|
|
213
|
+
b.assignment(
|
|
214
|
+
'=',
|
|
215
|
+
b.member(b.id('_$_server_$_'), b.id(declaration.id.name)),
|
|
216
|
+
context.visit(declaration),
|
|
217
|
+
),
|
|
218
|
+
);
|
|
219
|
+
} else {
|
|
220
|
+
// TODO
|
|
221
|
+
throw new Error('Not implemented');
|
|
222
|
+
}
|
|
162
223
|
},
|
|
163
224
|
|
|
164
225
|
VariableDeclaration(node, context) {
|
|
@@ -380,15 +441,43 @@ const visitors = {
|
|
|
380
441
|
}
|
|
381
442
|
} else {
|
|
382
443
|
// Component is imported or dynamic - check .async property at runtime
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
b.
|
|
386
|
-
|
|
444
|
+
// Use if-statement instead of ternary to avoid parser issues with await in conditionals
|
|
445
|
+
state.init.push(
|
|
446
|
+
b.if(
|
|
447
|
+
b.member(visit(node.id, state), b.id('async')),
|
|
448
|
+
b.block([b.stmt(b.await(component_call))]),
|
|
449
|
+
b.block([b.stmt(component_call)]),
|
|
450
|
+
),
|
|
387
451
|
);
|
|
388
|
-
|
|
452
|
+
|
|
453
|
+
// Mark parent component as async since we're using await
|
|
454
|
+
if (state.metadata?.await === false) {
|
|
455
|
+
state.metadata.await = true;
|
|
456
|
+
}
|
|
389
457
|
}
|
|
390
458
|
}
|
|
391
459
|
},
|
|
460
|
+
SwitchStatement(node, context) {
|
|
461
|
+
if (!is_inside_component(context)) {
|
|
462
|
+
return context.next();
|
|
463
|
+
}
|
|
464
|
+
const cases = [];
|
|
465
|
+
for (const switch_case of node.cases) {
|
|
466
|
+
const consequent_scope =
|
|
467
|
+
context.state.scopes.get(switch_case.consequent) || context.state.scope;
|
|
468
|
+
const consequent = b.block(
|
|
469
|
+
transform_body(switch_case.consequent, {
|
|
470
|
+
...context,
|
|
471
|
+
state: { ...context.state, scope: consequent_scope },
|
|
472
|
+
}),
|
|
473
|
+
);
|
|
474
|
+
cases.push(
|
|
475
|
+
b.switch_case(switch_case.test ? context.visit(switch_case.test) : null, consequent.body),
|
|
476
|
+
);
|
|
477
|
+
}
|
|
478
|
+
context.state.init.push(b.switch(context.visit(node.discriminant), cases));
|
|
479
|
+
},
|
|
480
|
+
|
|
392
481
|
ForOfStatement(node, context) {
|
|
393
482
|
if (!is_inside_component(context)) {
|
|
394
483
|
context.next();
|
|
@@ -440,6 +529,10 @@ const visitors = {
|
|
|
440
529
|
}
|
|
441
530
|
},
|
|
442
531
|
|
|
532
|
+
ServerIdentifier(node, context) {
|
|
533
|
+
return b.id('_$_server_$_');
|
|
534
|
+
},
|
|
535
|
+
|
|
443
536
|
ImportDeclaration(node, context) {
|
|
444
537
|
if (!context.state.to_ts && node.importKind === 'type') {
|
|
445
538
|
return b.empty;
|
|
@@ -599,7 +692,38 @@ const visitors = {
|
|
|
599
692
|
},
|
|
600
693
|
|
|
601
694
|
ServerBlock(node, context) {
|
|
602
|
-
|
|
695
|
+
const exports = node.metadata.exports;
|
|
696
|
+
|
|
697
|
+
if (exports.length === 0) {
|
|
698
|
+
return context.visit(node.body);
|
|
699
|
+
}
|
|
700
|
+
const file_path = context.state.filename;
|
|
701
|
+
const block = context.visit(node.body, { ...context.state, inside_server_block: true });
|
|
702
|
+
const rpc_modules = globalThis.rpc_modules;
|
|
703
|
+
|
|
704
|
+
if (rpc_modules) {
|
|
705
|
+
for (const name of exports) {
|
|
706
|
+
const func_path = file_path + '#' + name;
|
|
707
|
+
// needs to be a sha256 hash of func_path, to avoid leaking file structure
|
|
708
|
+
const hash = createHash('sha256').update(func_path).digest('hex').slice(0, 8);
|
|
709
|
+
rpc_modules.set(hash, [file_path, name]);
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
return b.export(
|
|
714
|
+
b.const(
|
|
715
|
+
'_$_server_$_',
|
|
716
|
+
b.call(
|
|
717
|
+
b.thunk(
|
|
718
|
+
b.block([
|
|
719
|
+
b.var('_$_server_$_', b.object([])),
|
|
720
|
+
...block.body,
|
|
721
|
+
b.return(b.id('_$_server_$_')),
|
|
722
|
+
]),
|
|
723
|
+
),
|
|
724
|
+
),
|
|
725
|
+
),
|
|
726
|
+
);
|
|
603
727
|
},
|
|
604
728
|
};
|
|
605
729
|
|
|
@@ -614,6 +738,8 @@ export function transform_server(filename, source, analysis) {
|
|
|
614
738
|
scopes: analysis.scopes,
|
|
615
739
|
stylesheets: [],
|
|
616
740
|
component_metadata,
|
|
741
|
+
inside_server_block: false,
|
|
742
|
+
filename,
|
|
617
743
|
};
|
|
618
744
|
|
|
619
745
|
const program = /** @type {ESTree.Program} */ (
|
package/src/compiler/scope.js
CHANGED
|
@@ -169,6 +169,14 @@ export function create_scopes(ast, root, parent) {
|
|
|
169
169
|
next({ scope });
|
|
170
170
|
},
|
|
171
171
|
|
|
172
|
+
ServerBlock(node, { state, next }) {
|
|
173
|
+
const scope = state.scope.child();
|
|
174
|
+
scope.server_block = true;
|
|
175
|
+
scopes.set(node, scope);
|
|
176
|
+
|
|
177
|
+
next({ scope });
|
|
178
|
+
},
|
|
179
|
+
|
|
172
180
|
FunctionExpression(node, { state, next }) {
|
|
173
181
|
const scope = state.scope.child();
|
|
174
182
|
scopes.set(node, scope);
|
|
@@ -327,6 +335,12 @@ export class Scope {
|
|
|
327
335
|
*/
|
|
328
336
|
tracing = null;
|
|
329
337
|
|
|
338
|
+
/**
|
|
339
|
+
* Is this scope a top-level server block scope
|
|
340
|
+
* @type {boolean}
|
|
341
|
+
*/
|
|
342
|
+
server_block = false;
|
|
343
|
+
|
|
330
344
|
/**
|
|
331
345
|
*
|
|
332
346
|
* @param {ScopeRoot} root
|
|
@@ -353,7 +367,7 @@ export class Scope {
|
|
|
353
367
|
return this.parent.declare(node, kind, declaration_kind);
|
|
354
368
|
}
|
|
355
369
|
|
|
356
|
-
if (declaration_kind === 'import') {
|
|
370
|
+
if (declaration_kind === 'import' && !this.parent.server_block) {
|
|
357
371
|
return this.parent.declare(node, kind, declaration_kind, initial);
|
|
358
372
|
}
|
|
359
373
|
}
|
|
@@ -151,7 +151,7 @@ export type DeclarationKind =
|
|
|
151
151
|
/**
|
|
152
152
|
* Binding kinds
|
|
153
153
|
*/
|
|
154
|
-
export type BindingKind = 'normal' | '
|
|
154
|
+
export type BindingKind = 'normal' | 'for_pattern' | 'rest_prop' | 'prop' | 'prop_fallback';
|
|
155
155
|
|
|
156
156
|
/**
|
|
157
157
|
* A variable binding in a scope
|
|
@@ -299,4 +299,4 @@ export interface DelegatedEventResult {
|
|
|
299
299
|
hoisted: boolean;
|
|
300
300
|
/** The hoisted function */
|
|
301
301
|
function?: FunctionExpression | FunctionDeclaration | ArrowFunctionExpression;
|
|
302
|
-
}
|
|
302
|
+
}
|
package/src/compiler/utils.js
CHANGED
|
@@ -316,6 +316,8 @@ function get_hoisted_params(node, context) {
|
|
|
316
316
|
if (binding !== null && !scope.declarations.has(reference) && binding.initial !== node) {
|
|
317
317
|
if (binding.kind === 'prop') {
|
|
318
318
|
push_unique(b.id('__props'));
|
|
319
|
+
} else if (binding.kind === 'for_pattern') {
|
|
320
|
+
push_unique(binding.metadata.pattern);
|
|
319
321
|
} else if (binding.kind === 'prop_fallback') {
|
|
320
322
|
push_unique(b.id(binding.node.name));
|
|
321
323
|
} else if (
|
|
@@ -2,10 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
import { branch, destroy_block, render } from './blocks.js';
|
|
4
4
|
import { COMPOSITE_BLOCK } from './constants.js';
|
|
5
|
+
import { apply_element_spread } from './render';
|
|
5
6
|
import { active_block } from './runtime.js';
|
|
6
7
|
|
|
7
8
|
/**
|
|
8
|
-
* @
|
|
9
|
+
* @typedef {((anchor: Node, props: Record<string, any>, block: Block | null) => void)} ComponentFunction
|
|
10
|
+
* @param {() => ComponentFunction | keyof HTMLElementTagNameMap} get_component
|
|
9
11
|
* @param {Node} node
|
|
10
12
|
* @param {Record<string, any>} props
|
|
11
13
|
* @returns {void}
|
|
@@ -23,9 +25,39 @@ export function composite(get_component, node, props) {
|
|
|
23
25
|
b = null;
|
|
24
26
|
}
|
|
25
27
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
28
|
+
if (typeof component === 'function') {
|
|
29
|
+
// Handle as regular component
|
|
30
|
+
b = branch(() => {
|
|
31
|
+
var block = active_block;
|
|
32
|
+
/** @type {ComponentFunction} */ (component)(anchor, props, block);
|
|
33
|
+
});
|
|
34
|
+
} else {
|
|
35
|
+
// Custom element
|
|
36
|
+
b = branch(() => {
|
|
37
|
+
var block = /** @type {Block} */ (active_block);
|
|
38
|
+
|
|
39
|
+
var element = document.createElement(
|
|
40
|
+
/** @type {keyof HTMLElementTagNameMap} */ (component),
|
|
41
|
+
);
|
|
42
|
+
/** @type {ChildNode} */ (anchor).before(element);
|
|
43
|
+
|
|
44
|
+
if (block.s === null) {
|
|
45
|
+
block.s = {
|
|
46
|
+
start: element,
|
|
47
|
+
end: element,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const spread_fn = apply_element_spread(element, () => props || {});
|
|
52
|
+
spread_fn();
|
|
53
|
+
|
|
54
|
+
if (typeof props?.children === 'function') {
|
|
55
|
+
var child_anchor = document.createComment('');
|
|
56
|
+
element.appendChild(child_anchor);
|
|
57
|
+
|
|
58
|
+
props?.children?.(child_anchor, {}, block);
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
}
|
|
30
62
|
}, COMPOSITE_BLOCK);
|
|
31
63
|
}
|