ripple 0.2.180 → 0.2.183
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 +4 -2
- package/src/compiler/errors.js +3 -1
- package/src/compiler/index.d.ts +2 -1
- package/src/compiler/phases/1-parse/index.js +525 -311
- package/src/compiler/phases/1-parse/style.js +3 -1
- package/src/compiler/phases/2-analyze/css-analyze.js +116 -97
- package/src/compiler/phases/2-analyze/index.js +80 -50
- package/src/compiler/phases/2-analyze/prune.js +200 -58
- package/src/compiler/phases/2-analyze/validation.js +9 -7
- package/src/compiler/phases/3-transform/client/index.js +871 -394
- package/src/compiler/phases/3-transform/segments.js +6 -4
- package/src/compiler/phases/3-transform/server/index.js +278 -121
- package/src/compiler/scope.js +45 -93
- package/src/compiler/types/index.d.ts +619 -199
- package/src/compiler/types/parse.d.ts +1580 -0
- package/src/compiler/utils.js +62 -74
- package/src/runtime/internal/client/blocks.js +4 -1
- package/src/utils/ast.js +247 -192
- package/src/utils/builders.js +309 -247
- package/src/utils/sanitize_template_string.js +2 -2
|
@@ -1,4 +1,15 @@
|
|
|
1
|
-
/** @import * as
|
|
1
|
+
/** @import * as AST from 'estree'; */
|
|
2
|
+
/** @import { SourceMapMappings } from '@jridgewell/sourcemap-codec'; */
|
|
3
|
+
/** @import { TSESTree } from '@typescript-eslint/types'; */
|
|
4
|
+
/**
|
|
5
|
+
@import {
|
|
6
|
+
TransformServerContext,
|
|
7
|
+
TransformServerState,
|
|
8
|
+
Visitors,
|
|
9
|
+
AnalysisResult,
|
|
10
|
+
ScopeInterface,
|
|
11
|
+
Visitor
|
|
12
|
+
} from '#compiler' */
|
|
2
13
|
|
|
3
14
|
import * as b from '../../../../utils/builders.js';
|
|
4
15
|
import { walk } from 'zimmerframe';
|
|
@@ -19,6 +30,9 @@ import { is_event_attribute } from '../../../../utils/events.js';
|
|
|
19
30
|
import { render_stylesheets } from '../stylesheet.js';
|
|
20
31
|
import { createHash } from 'node:crypto';
|
|
21
32
|
|
|
33
|
+
/**
|
|
34
|
+
* @param {TransformServerContext} context
|
|
35
|
+
*/
|
|
22
36
|
function add_ripple_internal_import(context) {
|
|
23
37
|
if (!context.state.to_ts) {
|
|
24
38
|
if (!context.state.imports.has(`import * as _$_ from 'ripple/internal/server'`)) {
|
|
@@ -27,13 +41,17 @@ function add_ripple_internal_import(context) {
|
|
|
27
41
|
}
|
|
28
42
|
}
|
|
29
43
|
|
|
44
|
+
/**
|
|
45
|
+
* @param {AST.Node[]} children
|
|
46
|
+
* @param {TransformServerContext} context
|
|
47
|
+
*/
|
|
30
48
|
function transform_children(children, context) {
|
|
31
|
-
const { visit, state
|
|
49
|
+
const { visit, state } = context;
|
|
32
50
|
const normalized = normalize_children(children, context);
|
|
33
51
|
|
|
34
52
|
for (const node of normalized) {
|
|
35
53
|
if (node.type === 'BreakStatement') {
|
|
36
|
-
state.init
|
|
54
|
+
state.init?.push(b.break);
|
|
37
55
|
continue;
|
|
38
56
|
}
|
|
39
57
|
if (
|
|
@@ -48,33 +66,41 @@ function transform_children(children, context) {
|
|
|
48
66
|
node.type === 'Component'
|
|
49
67
|
) {
|
|
50
68
|
const metadata = { await: false };
|
|
51
|
-
state.init
|
|
69
|
+
state.init?.push(/** @type {AST.Statement} */ (visit(node, { ...state, metadata })));
|
|
52
70
|
if (metadata.await) {
|
|
53
|
-
state.init
|
|
71
|
+
state.init?.push(b.if(b.call('_$_.aborted'), b.return(null)));
|
|
54
72
|
if (state.metadata?.await === false) {
|
|
55
73
|
state.metadata.await = true;
|
|
56
74
|
}
|
|
57
75
|
}
|
|
58
76
|
} else {
|
|
59
|
-
visit(node, { ...state
|
|
77
|
+
visit(node, { ...state });
|
|
60
78
|
}
|
|
61
79
|
}
|
|
62
80
|
}
|
|
63
81
|
|
|
64
|
-
|
|
82
|
+
/**
|
|
83
|
+
* @param {AST.Node[]} body
|
|
84
|
+
* @param {TransformServerContext} context
|
|
85
|
+
* @returns {AST.Statement[]}
|
|
86
|
+
*/
|
|
87
|
+
function transform_body(body, context) {
|
|
88
|
+
const { state } = context;
|
|
89
|
+
/** @type {TransformServerState} */
|
|
65
90
|
const body_state = {
|
|
66
91
|
...state,
|
|
67
92
|
init: [],
|
|
68
93
|
metadata: state.metadata,
|
|
69
94
|
};
|
|
70
95
|
|
|
71
|
-
transform_children(body, {
|
|
96
|
+
transform_children(body, { ...context, state: body_state });
|
|
72
97
|
|
|
73
|
-
return body_state.init;
|
|
98
|
+
return /** @type {AST.Statement[]} */ (body_state.init);
|
|
74
99
|
}
|
|
75
100
|
|
|
101
|
+
/** @type {Visitors<AST.Node, TransformServerState>} */
|
|
76
102
|
const visitors = {
|
|
77
|
-
_:
|
|
103
|
+
_: (node, { next, state }) => {
|
|
78
104
|
const scope = state.scopes.get(node);
|
|
79
105
|
|
|
80
106
|
if (scope && scope !== state.scope) {
|
|
@@ -120,6 +146,26 @@ const visitors = {
|
|
|
120
146
|
component_fn = b.async(component_fn);
|
|
121
147
|
}
|
|
122
148
|
|
|
149
|
+
// Anonymous components return a FunctionExpression
|
|
150
|
+
if (!node.id) {
|
|
151
|
+
// For async anonymous components, we need to set .async on the function
|
|
152
|
+
if (metadata.await) {
|
|
153
|
+
// Use IIFE pattern: (fn => (fn.async = true, fn))(function() { ... })
|
|
154
|
+
return b.call(
|
|
155
|
+
b.arrow(
|
|
156
|
+
[b.id('fn')],
|
|
157
|
+
b.sequence([
|
|
158
|
+
b.assignment('=', b.member(b.id('fn'), b.id('async')), b.true),
|
|
159
|
+
b.id('fn'),
|
|
160
|
+
]),
|
|
161
|
+
),
|
|
162
|
+
component_fn,
|
|
163
|
+
);
|
|
164
|
+
}
|
|
165
|
+
return component_fn;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Named components return a FunctionDeclaration
|
|
123
169
|
const declaration = b.function_declaration(
|
|
124
170
|
node.id,
|
|
125
171
|
component_fn.params,
|
|
@@ -129,8 +175,8 @@ const visitors = {
|
|
|
129
175
|
|
|
130
176
|
if (metadata.await) {
|
|
131
177
|
const parent = context.path.at(-1);
|
|
132
|
-
if (parent
|
|
133
|
-
const body = parent.body;
|
|
178
|
+
if (parent?.type === 'Program' || parent?.type === 'BlockStatement') {
|
|
179
|
+
const body = /** @type {AST.RippleProgram} */ (parent).body;
|
|
134
180
|
const index = body.indexOf(node);
|
|
135
181
|
body.splice(
|
|
136
182
|
index + 1,
|
|
@@ -160,7 +206,10 @@ const visitors = {
|
|
|
160
206
|
const argsToUse = node.arguments.length > 0 ? node.arguments : callee.arguments;
|
|
161
207
|
// For SSR, use regular Map/Set
|
|
162
208
|
const constructorName = callee.type === 'TrackedMapExpression' ? 'Map' : 'Set';
|
|
163
|
-
return b.new(
|
|
209
|
+
return b.new(
|
|
210
|
+
b.id(constructorName),
|
|
211
|
+
.../** @type {AST.Expression[]} */ (argsToUse.map((arg) => context.visit(arg))),
|
|
212
|
+
);
|
|
164
213
|
}
|
|
165
214
|
|
|
166
215
|
if (!context.state.to_ts) {
|
|
@@ -231,7 +280,7 @@ const visitors = {
|
|
|
231
280
|
TSInstantiationExpression(node, context) {
|
|
232
281
|
if (!context.state.to_ts) {
|
|
233
282
|
// In JavaScript, just return the expression wrapped in parentheses
|
|
234
|
-
return b.sequence([context.visit(node.expression)]);
|
|
283
|
+
return b.sequence(/** @type {AST.Expression[]} */ ([context.visit(node.expression)]));
|
|
235
284
|
}
|
|
236
285
|
return context.next();
|
|
237
286
|
},
|
|
@@ -264,7 +313,8 @@ const visitors = {
|
|
|
264
313
|
b.assignment(
|
|
265
314
|
'=',
|
|
266
315
|
b.member(b.id('_$_server_$_'), b.id(declaration.id.name)),
|
|
267
|
-
|
|
316
|
+
/** @type {AST.Expression} */
|
|
317
|
+
(context.visit(declaration)),
|
|
268
318
|
),
|
|
269
319
|
);
|
|
270
320
|
} else {
|
|
@@ -288,6 +338,7 @@ const visitors = {
|
|
|
288
338
|
|
|
289
339
|
const is_dom_element = is_element_dom_element(node);
|
|
290
340
|
const is_spreading = node.attributes.some((attr) => attr.type === 'SpreadAttribute');
|
|
341
|
+
/** @type {(AST.Property | AST.SpreadElement)[] | null} */
|
|
291
342
|
const spread_attributes = is_spreading ? [] : null;
|
|
292
343
|
const child_namespace = is_dom_element
|
|
293
344
|
? determine_namespace_for_children(node.id.name, state.namespace)
|
|
@@ -296,11 +347,15 @@ const visitors = {
|
|
|
296
347
|
if (is_dom_element) {
|
|
297
348
|
const is_void = is_void_element(node.id.name);
|
|
298
349
|
|
|
299
|
-
state.init
|
|
350
|
+
state.init?.push(
|
|
300
351
|
b.stmt(b.call(b.member(b.id('__output'), b.id('push')), b.literal(`<${node.id.name}`))),
|
|
301
352
|
);
|
|
302
353
|
let class_attribute = null;
|
|
303
354
|
|
|
355
|
+
/**
|
|
356
|
+
* @param {string} name
|
|
357
|
+
* @param {string | number | bigint | boolean | RegExp | null | undefined} value
|
|
358
|
+
*/
|
|
304
359
|
const handle_static_attr = (name, value) => {
|
|
305
360
|
const attr_str = ` ${name}${
|
|
306
361
|
is_boolean_attribute(name) && value === true
|
|
@@ -314,9 +369,9 @@ const visitors = {
|
|
|
314
369
|
is_boolean_attribute(name) && value === true
|
|
315
370
|
? b.literal(true)
|
|
316
371
|
: b.literal(value === true ? '' : value);
|
|
317
|
-
spread_attributes
|
|
372
|
+
spread_attributes?.push(b.prop('init', b.literal(name), actual_value));
|
|
318
373
|
} else {
|
|
319
|
-
state.init
|
|
374
|
+
state.init?.push(
|
|
320
375
|
b.stmt(b.call(b.member(b.id('__output'), b.id('push')), b.literal(attr_str))),
|
|
321
376
|
);
|
|
322
377
|
}
|
|
@@ -347,9 +402,11 @@ const visitors = {
|
|
|
347
402
|
continue;
|
|
348
403
|
}
|
|
349
404
|
const metadata = { tracking: false, await: false };
|
|
350
|
-
const expression =
|
|
405
|
+
const expression = /** @type {AST.Expression} */ (
|
|
406
|
+
visit(attr.value, { ...state, metadata })
|
|
407
|
+
);
|
|
351
408
|
|
|
352
|
-
state.init
|
|
409
|
+
state.init?.push(
|
|
353
410
|
b.stmt(
|
|
354
411
|
b.call(
|
|
355
412
|
b.member(b.id('__output'), b.id('push')),
|
|
@@ -359,29 +416,34 @@ const visitors = {
|
|
|
359
416
|
);
|
|
360
417
|
}
|
|
361
418
|
} else if (attr.type === 'SpreadAttribute') {
|
|
362
|
-
spread_attributes
|
|
419
|
+
spread_attributes?.push(
|
|
420
|
+
b.spread(/** @type {AST.Expression} */ (visit(attr.argument, state))),
|
|
421
|
+
);
|
|
363
422
|
}
|
|
364
423
|
}
|
|
365
424
|
|
|
366
425
|
if (class_attribute !== null) {
|
|
367
|
-
|
|
368
|
-
|
|
426
|
+
const attr_value = /** @type {AST.Expression} */ (class_attribute.value);
|
|
427
|
+
if (attr_value.type === 'Literal') {
|
|
428
|
+
let value = attr_value.value;
|
|
369
429
|
|
|
370
|
-
if (node.metadata.scoped && state.component
|
|
430
|
+
if (node.metadata.scoped && state.component?.css) {
|
|
371
431
|
value = `${state.component.css.hash} ${value}`;
|
|
372
432
|
}
|
|
373
433
|
|
|
374
434
|
handle_static_attr(class_attribute.name.name, value);
|
|
375
435
|
} else {
|
|
376
436
|
const metadata = { tracking: false, await: false };
|
|
377
|
-
let expression =
|
|
437
|
+
let expression = /** @type {AST.Expression} */ (
|
|
438
|
+
visit(attr_value, { ...state, metadata })
|
|
439
|
+
);
|
|
378
440
|
|
|
379
|
-
if (node.metadata.scoped && state.component
|
|
441
|
+
if (node.metadata.scoped && state.component?.css) {
|
|
380
442
|
// Pass array to clsx so it can handle objects properly
|
|
381
443
|
expression = b.array([expression, b.literal(state.component.css.hash)]);
|
|
382
444
|
}
|
|
383
445
|
|
|
384
|
-
state.init
|
|
446
|
+
state.init?.push(
|
|
385
447
|
b.stmt(
|
|
386
448
|
b.call(
|
|
387
449
|
b.member(b.id('__output'), b.id('push')),
|
|
@@ -390,21 +452,21 @@ const visitors = {
|
|
|
390
452
|
),
|
|
391
453
|
);
|
|
392
454
|
}
|
|
393
|
-
} else if (node.metadata.scoped && state.component
|
|
455
|
+
} else if (node.metadata.scoped && state.component?.css) {
|
|
394
456
|
const value = state.component.css.hash;
|
|
395
457
|
|
|
396
458
|
handle_static_attr('class', value);
|
|
397
459
|
}
|
|
398
460
|
|
|
399
461
|
if (spread_attributes !== null && spread_attributes.length > 0) {
|
|
400
|
-
state.init
|
|
462
|
+
state.init?.push(
|
|
401
463
|
b.stmt(
|
|
402
464
|
b.call(
|
|
403
465
|
b.member(b.id('__output'), b.id('push')),
|
|
404
466
|
b.call(
|
|
405
467
|
'_$_.spread_attrs',
|
|
406
468
|
b.object(spread_attributes),
|
|
407
|
-
node.metadata.scoped && state.component
|
|
469
|
+
node.metadata.scoped && state.component?.css
|
|
408
470
|
? b.literal(state.component.css.hash)
|
|
409
471
|
: undefined,
|
|
410
472
|
),
|
|
@@ -413,24 +475,34 @@ const visitors = {
|
|
|
413
475
|
);
|
|
414
476
|
}
|
|
415
477
|
|
|
416
|
-
state.init
|
|
478
|
+
state.init?.push(b.stmt(b.call(b.member(b.id('__output'), b.id('push')), b.literal(`>`))));
|
|
417
479
|
|
|
418
480
|
if (!is_void) {
|
|
419
|
-
transform_children(
|
|
481
|
+
transform_children(
|
|
482
|
+
node.children,
|
|
483
|
+
/** @type {TransformServerContext} */ ({ visit, state: { ...state } }),
|
|
484
|
+
);
|
|
420
485
|
|
|
421
|
-
state.init
|
|
486
|
+
state.init?.push(
|
|
422
487
|
b.stmt(b.call(b.member(b.id('__output'), b.id('push')), b.literal(`</${node.id.name}>`))),
|
|
423
488
|
);
|
|
424
489
|
}
|
|
425
490
|
} else {
|
|
491
|
+
/** @type {(AST.Property | AST.SpreadElement)[]} */
|
|
426
492
|
const props = [];
|
|
493
|
+
/** @type {AST.Expression | null} */
|
|
427
494
|
let children_prop = null;
|
|
428
495
|
|
|
429
496
|
for (const attr of node.attributes) {
|
|
430
497
|
if (attr.type === 'Attribute') {
|
|
431
498
|
if (attr.name.type === 'Identifier') {
|
|
432
499
|
const metadata = { tracking: false, await: false };
|
|
433
|
-
let property =
|
|
500
|
+
let property = /** @type {AST.Expression} */ (
|
|
501
|
+
visit(/** @type {AST.Expression} */ (attr.value), {
|
|
502
|
+
...state,
|
|
503
|
+
metadata,
|
|
504
|
+
})
|
|
505
|
+
);
|
|
434
506
|
|
|
435
507
|
if (attr.name.name === 'children') {
|
|
436
508
|
children_prop = b.thunk(property);
|
|
@@ -438,13 +510,15 @@ const visitors = {
|
|
|
438
510
|
}
|
|
439
511
|
|
|
440
512
|
props.push(b.prop('init', attr.name, property));
|
|
441
|
-
} else if (attr.type === 'SpreadAttribute') {
|
|
442
|
-
props.push(
|
|
443
|
-
b.spread(
|
|
444
|
-
visit(attr.argument, { ...state, metadata: { ...state.metadata, spread: true } }),
|
|
445
|
-
),
|
|
446
|
-
);
|
|
447
513
|
}
|
|
514
|
+
} else if (attr.type === 'SpreadAttribute') {
|
|
515
|
+
props.push(
|
|
516
|
+
b.spread(
|
|
517
|
+
/** @type {AST.Expression} */ (
|
|
518
|
+
visit(attr.argument, { ...state, metadata: { ...state.metadata } })
|
|
519
|
+
),
|
|
520
|
+
),
|
|
521
|
+
);
|
|
448
522
|
}
|
|
449
523
|
}
|
|
450
524
|
|
|
@@ -452,30 +526,52 @@ const visitors = {
|
|
|
452
526
|
|
|
453
527
|
for (const child of node.children) {
|
|
454
528
|
if (child.type === 'Component') {
|
|
455
|
-
|
|
456
|
-
|
|
529
|
+
// in this case, id cannot be null
|
|
530
|
+
// as these are direct children of the component
|
|
531
|
+
const id = /** @type {AST.Identifier} */ (child.id);
|
|
532
|
+
props.push(
|
|
533
|
+
b.prop(
|
|
534
|
+
'init',
|
|
535
|
+
id,
|
|
536
|
+
/** @type {AST.Expression} */ (
|
|
537
|
+
visit(child, { ...state, namespace: child_namespace })
|
|
538
|
+
),
|
|
539
|
+
),
|
|
540
|
+
);
|
|
457
541
|
} else {
|
|
458
542
|
children_filtered.push(child);
|
|
459
543
|
}
|
|
460
544
|
}
|
|
461
545
|
|
|
462
546
|
if (children_filtered.length > 0) {
|
|
463
|
-
const component_scope = context.state.scopes.get(node);
|
|
464
|
-
const children =
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
547
|
+
const component_scope = /** @type {ScopeInterface} */ (context.state.scopes.get(node));
|
|
548
|
+
const children = /** @type {AST.Expression} */ (
|
|
549
|
+
visit(b.component(b.id('children'), [], children_filtered), {
|
|
550
|
+
...context.state,
|
|
551
|
+
scope: component_scope,
|
|
552
|
+
namespace: child_namespace,
|
|
553
|
+
})
|
|
554
|
+
);
|
|
469
555
|
|
|
470
556
|
if (children_prop) {
|
|
471
|
-
children_prop.body = b.logical(
|
|
557
|
+
/** @type {AST.ArrowFunctionExpression} */ (children_prop).body = b.logical(
|
|
558
|
+
'??',
|
|
559
|
+
/** @type {AST.Expression} */ (
|
|
560
|
+
/** @type {AST.ArrowFunctionExpression} */ (children_prop).body
|
|
561
|
+
),
|
|
562
|
+
children,
|
|
563
|
+
);
|
|
472
564
|
} else {
|
|
473
565
|
props.push(b.prop('init', b.id('children'), children));
|
|
474
566
|
}
|
|
475
567
|
}
|
|
476
568
|
|
|
477
569
|
// For SSR, determine if we should await based on component metadata
|
|
478
|
-
const component_call = b.call(
|
|
570
|
+
const component_call = b.call(
|
|
571
|
+
/** @type {AST.Expression} */ (visit(node.id, state)),
|
|
572
|
+
b.id('__output'),
|
|
573
|
+
b.object(props),
|
|
574
|
+
);
|
|
479
575
|
|
|
480
576
|
// Check if this is a locally defined component and if it's async
|
|
481
577
|
const component_name = node.id.type === 'Identifier' ? node.id.name : null;
|
|
@@ -486,16 +582,16 @@ const visitors = {
|
|
|
486
582
|
if (local_metadata) {
|
|
487
583
|
// Component is defined locally - we know if it's async or not
|
|
488
584
|
if (local_metadata.async) {
|
|
489
|
-
state.init
|
|
585
|
+
state.init?.push(b.stmt(b.await(component_call)));
|
|
490
586
|
} else {
|
|
491
|
-
state.init
|
|
587
|
+
state.init?.push(b.stmt(component_call));
|
|
492
588
|
}
|
|
493
589
|
} else {
|
|
494
590
|
// Component is imported or dynamic - check .async property at runtime
|
|
495
591
|
// Use if-statement instead of ternary to avoid parser issues with await in conditionals
|
|
496
|
-
state.init
|
|
592
|
+
state.init?.push(
|
|
497
593
|
b.if(
|
|
498
|
-
b.member(visit(node.id, state), b.id('async')),
|
|
594
|
+
b.member(/** @type {AST.Expression} */ (visit(node.id, state)), b.id('async')),
|
|
499
595
|
b.block([b.stmt(b.await(component_call))]),
|
|
500
596
|
b.block([b.stmt(component_call)]),
|
|
501
597
|
),
|
|
@@ -523,10 +619,15 @@ const visitors = {
|
|
|
523
619
|
}),
|
|
524
620
|
);
|
|
525
621
|
cases.push(
|
|
526
|
-
b.switch_case(
|
|
622
|
+
b.switch_case(
|
|
623
|
+
switch_case.test ? /** @type {AST.Expression} */ (context.visit(switch_case.test)) : null,
|
|
624
|
+
consequent.body,
|
|
625
|
+
),
|
|
527
626
|
);
|
|
528
627
|
}
|
|
529
|
-
context.state.init
|
|
628
|
+
context.state.init?.push(
|
|
629
|
+
b.switch(/** @type {AST.Expression} */ (context.visit(node.discriminant)), cases),
|
|
630
|
+
);
|
|
530
631
|
},
|
|
531
632
|
|
|
532
633
|
ForOfStatement(node, context) {
|
|
@@ -536,18 +637,23 @@ const visitors = {
|
|
|
536
637
|
}
|
|
537
638
|
const body_scope = context.state.scopes.get(node.body);
|
|
538
639
|
|
|
539
|
-
const body = transform_body(node.body.body, {
|
|
640
|
+
const body = transform_body(/** @type {AST.BlockStatement} */ (node.body).body, {
|
|
540
641
|
...context,
|
|
541
|
-
state: { ...context.state, scope: body_scope },
|
|
642
|
+
state: { ...context.state, scope: /** @type {ScopeInterface} */ (body_scope) },
|
|
542
643
|
});
|
|
543
644
|
|
|
544
645
|
if (node.index) {
|
|
545
|
-
context.state.init
|
|
546
|
-
body.push(b.update('++', node.index));
|
|
646
|
+
context.state.init?.push(b.var(node.index, b.literal(0)));
|
|
647
|
+
body.push(b.stmt(b.update('++', node.index)));
|
|
547
648
|
}
|
|
548
649
|
|
|
549
|
-
context.state.init
|
|
550
|
-
b.for_of(
|
|
650
|
+
context.state.init?.push(
|
|
651
|
+
b.for_of(
|
|
652
|
+
/** @type {AST.VariableDeclaration} */ (context.visit(node.left)),
|
|
653
|
+
/** @type {AST.Expression} */
|
|
654
|
+
(context.visit(node.right)),
|
|
655
|
+
b.block(body),
|
|
656
|
+
),
|
|
551
657
|
);
|
|
552
658
|
},
|
|
553
659
|
|
|
@@ -558,17 +664,23 @@ const visitors = {
|
|
|
558
664
|
}
|
|
559
665
|
|
|
560
666
|
const consequent = b.block(
|
|
561
|
-
transform_body(node.consequent.body, {
|
|
667
|
+
transform_body(/** @type {AST.BlockStatement} */ (node.consequent).body, {
|
|
562
668
|
...context,
|
|
563
|
-
state: {
|
|
669
|
+
state: {
|
|
670
|
+
...context.state,
|
|
671
|
+
scope: /** @type {ScopeInterface} */ (context.state.scopes.get(node.consequent)),
|
|
672
|
+
},
|
|
564
673
|
}),
|
|
565
674
|
);
|
|
566
675
|
|
|
676
|
+
/** @type {AST.BlockStatement | AST.IfStatement | null} */
|
|
567
677
|
let alternate = null;
|
|
568
678
|
if (node.alternate) {
|
|
569
679
|
const alternate_scope = context.state.scopes.get(node.alternate) || context.state.scope;
|
|
570
680
|
const alternate_body_nodes =
|
|
571
|
-
node.alternate.type === 'IfStatement'
|
|
681
|
+
node.alternate.type === 'IfStatement'
|
|
682
|
+
? [node.alternate]
|
|
683
|
+
: /** @type {AST.BlockStatement} */ (node.alternate).body;
|
|
572
684
|
|
|
573
685
|
alternate = b.block(
|
|
574
686
|
transform_body(alternate_body_nodes, {
|
|
@@ -578,7 +690,9 @@ const visitors = {
|
|
|
578
690
|
);
|
|
579
691
|
}
|
|
580
692
|
|
|
581
|
-
context.state.init
|
|
693
|
+
context.state.init?.push(
|
|
694
|
+
b.if(/** @type {AST.Expression} */ (context.visit(node.test)), consequent, alternate),
|
|
695
|
+
);
|
|
582
696
|
},
|
|
583
697
|
|
|
584
698
|
AssignmentExpression(node, context) {
|
|
@@ -587,8 +701,10 @@ const visitors = {
|
|
|
587
701
|
if (left.type === 'Identifier' && left.tracked) {
|
|
588
702
|
return b.call(
|
|
589
703
|
'set',
|
|
590
|
-
|
|
591
|
-
context.visit(
|
|
704
|
+
/** @type {AST.Expression} */
|
|
705
|
+
(context.visit(left, { ...context.state, metadata: { tracking: false } })),
|
|
706
|
+
/** @type {AST.Expression} */
|
|
707
|
+
(context.visit(node.right)),
|
|
592
708
|
);
|
|
593
709
|
}
|
|
594
710
|
|
|
@@ -599,17 +715,18 @@ const visitors = {
|
|
|
599
715
|
return b.id('_$_server_$_');
|
|
600
716
|
},
|
|
601
717
|
|
|
718
|
+
/** @type {Visitor<AST.ImportDeclaration, TransformServerState, AST.Node>} */
|
|
602
719
|
ImportDeclaration(node, context) {
|
|
603
720
|
if (!context.state.to_ts && node.importKind === 'type') {
|
|
604
721
|
return b.empty;
|
|
605
722
|
}
|
|
606
723
|
|
|
607
|
-
return {
|
|
724
|
+
return /** @type {AST.ImportDeclaration} */ ({
|
|
608
725
|
...node,
|
|
609
726
|
specifiers: node.specifiers
|
|
610
|
-
.filter((spec) => spec.importKind !== 'type')
|
|
727
|
+
.filter((spec) => /** @type {AST.ImportSpecifier} */ (spec).importKind !== 'type')
|
|
611
728
|
.map((spec) => context.visit(spec)),
|
|
612
|
-
};
|
|
729
|
+
});
|
|
613
730
|
},
|
|
614
731
|
|
|
615
732
|
TryStatement(node, context) {
|
|
@@ -641,79 +758,97 @@ const visitors = {
|
|
|
641
758
|
if (node.pending) {
|
|
642
759
|
const pending_body = transform_body(node.pending.body, {
|
|
643
760
|
...context,
|
|
644
|
-
state: {
|
|
761
|
+
state: {
|
|
762
|
+
...context.state,
|
|
763
|
+
scope: /** @type {ScopeInterface} */ (context.state.scopes.get(node.pending)),
|
|
764
|
+
},
|
|
645
765
|
});
|
|
646
|
-
context.state.init
|
|
766
|
+
context.state.init?.push(...pending_body);
|
|
647
767
|
}
|
|
648
768
|
|
|
649
769
|
// For SSR with pending block: render the resolved content wrapped in async
|
|
650
770
|
// In a streaming SSR implementation, we'd render pending first, then stream resolved
|
|
651
|
-
const
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
}),
|
|
666
|
-
|
|
667
|
-
),
|
|
771
|
+
const handler = node.handler;
|
|
772
|
+
/** @type {AST.Statement[]} */
|
|
773
|
+
let try_statements = body;
|
|
774
|
+
if (handler != null) {
|
|
775
|
+
try_statements = [
|
|
776
|
+
b.try(
|
|
777
|
+
b.block(body),
|
|
778
|
+
b.catch_clause(
|
|
779
|
+
handler.param || b.id('error'),
|
|
780
|
+
b.block(
|
|
781
|
+
transform_body(handler.body.body, {
|
|
782
|
+
...context,
|
|
783
|
+
state: {
|
|
784
|
+
...context.state,
|
|
785
|
+
scope: /** @type {ScopeInterface} */ (context.state.scopes.get(handler.body)),
|
|
786
|
+
},
|
|
787
|
+
}),
|
|
668
788
|
),
|
|
669
|
-
|
|
670
|
-
|
|
789
|
+
),
|
|
790
|
+
),
|
|
791
|
+
];
|
|
792
|
+
}
|
|
671
793
|
|
|
672
|
-
context.state.init
|
|
794
|
+
context.state.init?.push(
|
|
673
795
|
b.stmt(b.await(b.call('_$_.async', b.thunk(b.block(try_statements), true)))),
|
|
674
796
|
);
|
|
675
797
|
} else {
|
|
676
798
|
// No async, just regular try/catch
|
|
677
|
-
if (node.handler
|
|
799
|
+
if (node.handler != null) {
|
|
678
800
|
const handler_body = transform_body(node.handler.body.body, {
|
|
679
801
|
...context,
|
|
680
|
-
state: {
|
|
802
|
+
state: {
|
|
803
|
+
...context.state,
|
|
804
|
+
scope: /** @type {ScopeInterface} */ (context.state.scopes.get(node.handler.body)),
|
|
805
|
+
},
|
|
681
806
|
});
|
|
682
807
|
|
|
683
|
-
context.state.init
|
|
808
|
+
context.state.init?.push(
|
|
684
809
|
b.try(
|
|
685
810
|
b.block(body),
|
|
686
811
|
b.catch_clause(node.handler.param || b.id('error'), b.block(handler_body)),
|
|
687
812
|
),
|
|
688
813
|
);
|
|
689
814
|
} else {
|
|
690
|
-
context.state.init
|
|
815
|
+
context.state.init?.push(...body);
|
|
691
816
|
}
|
|
692
817
|
}
|
|
693
818
|
},
|
|
694
819
|
|
|
695
820
|
AwaitExpression(node, context) {
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
821
|
+
const { state } = context;
|
|
822
|
+
state.scope.server_block = true;
|
|
823
|
+
state.inside_server_block = true;
|
|
824
|
+
if (state.to_ts) {
|
|
699
825
|
return context.next();
|
|
700
826
|
}
|
|
701
827
|
|
|
702
|
-
if (
|
|
703
|
-
|
|
828
|
+
if (state.metadata?.await === false) {
|
|
829
|
+
state.metadata.await = true;
|
|
704
830
|
}
|
|
705
831
|
|
|
706
|
-
return b.await(context.visit(node.argument));
|
|
832
|
+
return b.await(/** @type {AST.AwaitExpression} */ (context.visit(node.argument)));
|
|
707
833
|
},
|
|
708
834
|
|
|
709
835
|
TrackedObjectExpression(node, context) {
|
|
710
836
|
// For SSR, we just evaluate the object as-is since there's no reactivity
|
|
711
|
-
return b.object(
|
|
837
|
+
return b.object(
|
|
838
|
+
/** @type {(AST.Property | AST.SpreadElement)[]} */
|
|
839
|
+
(node.properties.map((prop) => context.visit(prop))),
|
|
840
|
+
);
|
|
712
841
|
},
|
|
713
842
|
|
|
714
843
|
TrackedArrayExpression(node, context) {
|
|
715
844
|
// For SSR, we just evaluate the array as-is since there's no reactivity
|
|
716
|
-
return b.array(
|
|
845
|
+
return b.array(
|
|
846
|
+
/** @type {(AST.Expression | AST.SpreadElement)[]} */
|
|
847
|
+
(
|
|
848
|
+
/** @param {AST.Node} el */
|
|
849
|
+
node.elements.map((el) => context.visit(/** @type {AST.Node} */ (el)))
|
|
850
|
+
),
|
|
851
|
+
);
|
|
717
852
|
},
|
|
718
853
|
|
|
719
854
|
MemberExpression(node, context) {
|
|
@@ -723,8 +858,11 @@ const visitors = {
|
|
|
723
858
|
return b.call(
|
|
724
859
|
'get',
|
|
725
860
|
b.member(
|
|
726
|
-
|
|
727
|
-
|
|
861
|
+
/** @type {AST.Expression} */
|
|
862
|
+
(context.visit(node.object)),
|
|
863
|
+
node.computed
|
|
864
|
+
? /** @type {AST.Expression} */ (context.visit(node.property))
|
|
865
|
+
: node.property,
|
|
728
866
|
node.computed,
|
|
729
867
|
node.optional,
|
|
730
868
|
),
|
|
@@ -736,20 +874,20 @@ const visitors = {
|
|
|
736
874
|
|
|
737
875
|
Text(node, { visit, state }) {
|
|
738
876
|
const metadata = { await: false };
|
|
739
|
-
let expression = visit(node.expression, { ...state, metadata });
|
|
877
|
+
let expression = /** @type {AST.Expression} */ (visit(node.expression, { ...state, metadata }));
|
|
740
878
|
|
|
741
879
|
if (expression.type === 'Identifier' && expression.tracked) {
|
|
742
880
|
expression = b.call('get', expression);
|
|
743
881
|
}
|
|
744
882
|
|
|
745
883
|
if (expression.type === 'Literal') {
|
|
746
|
-
state.init
|
|
884
|
+
state.init?.push(
|
|
747
885
|
b.stmt(
|
|
748
886
|
b.call(b.member(b.id('__output'), b.id('push')), b.literal(escape(expression.value))),
|
|
749
887
|
),
|
|
750
888
|
);
|
|
751
889
|
} else {
|
|
752
|
-
state.init
|
|
890
|
+
state.init?.push(
|
|
753
891
|
b.stmt(b.call(b.member(b.id('__output'), b.id('push')), b.call('_$_.escape', expression))),
|
|
754
892
|
);
|
|
755
893
|
}
|
|
@@ -757,16 +895,18 @@ const visitors = {
|
|
|
757
895
|
|
|
758
896
|
Html(node, { visit, state }) {
|
|
759
897
|
const metadata = { await: false };
|
|
760
|
-
const expression =
|
|
898
|
+
const expression = /** @type {AST.Expression} */ (
|
|
899
|
+
visit(node.expression, { ...state, metadata })
|
|
900
|
+
);
|
|
761
901
|
|
|
762
902
|
// For Html nodes, we render the content as-is without escaping
|
|
763
903
|
if (expression.type === 'Literal') {
|
|
764
|
-
state.init
|
|
904
|
+
state.init?.push(
|
|
765
905
|
b.stmt(b.call(b.member(b.id('__output'), b.id('push')), b.literal(expression.value))),
|
|
766
906
|
);
|
|
767
907
|
} else {
|
|
768
908
|
// If it's dynamic, we need to evaluate it and push it directly (not escaped)
|
|
769
|
-
state.init
|
|
909
|
+
state.init?.push(b.stmt(b.call(b.member(b.id('__output'), b.id('push')), expression)));
|
|
770
910
|
}
|
|
771
911
|
},
|
|
772
912
|
|
|
@@ -777,7 +917,9 @@ const visitors = {
|
|
|
777
917
|
return context.visit(node.body);
|
|
778
918
|
}
|
|
779
919
|
const file_path = context.state.filename;
|
|
780
|
-
const block =
|
|
920
|
+
const block = /** @type {AST.BlockStatement} */ (
|
|
921
|
+
context.visit(node.body, { ...context.state, inside_server_block: true })
|
|
922
|
+
);
|
|
781
923
|
const rpc_modules = globalThis.rpc_modules;
|
|
782
924
|
|
|
783
925
|
if (rpc_modules) {
|
|
@@ -806,10 +948,18 @@ const visitors = {
|
|
|
806
948
|
},
|
|
807
949
|
};
|
|
808
950
|
|
|
951
|
+
/**
|
|
952
|
+
* @param {string} filename
|
|
953
|
+
* @param {string} source
|
|
954
|
+
* @param {AnalysisResult} analysis
|
|
955
|
+
* @param {boolean} minify_css
|
|
956
|
+
* @returns {{ ast: AST.Program; js: { code: string; map: SourceMapMappings | null }; css: string; }}
|
|
957
|
+
*/
|
|
809
958
|
export function transform_server(filename, source, analysis, minify_css) {
|
|
810
959
|
// Use component metadata collected during the analyze phase
|
|
811
960
|
const component_metadata = analysis.component_metadata || [];
|
|
812
961
|
|
|
962
|
+
/** @type {TransformServerState} */
|
|
813
963
|
const state = {
|
|
814
964
|
imports: new Set(),
|
|
815
965
|
init: null,
|
|
@@ -819,11 +969,13 @@ export function transform_server(filename, source, analysis, minify_css) {
|
|
|
819
969
|
component_metadata,
|
|
820
970
|
inside_server_block: false,
|
|
821
971
|
filename,
|
|
972
|
+
namespace: 'html',
|
|
973
|
+
// TODO: should we remove all `to_ts` usages we use the client rendering for that?
|
|
974
|
+
to_ts: false,
|
|
975
|
+
metadata: {},
|
|
822
976
|
};
|
|
823
977
|
|
|
824
|
-
const program =
|
|
825
|
-
walk(analysis.ast, { ...state, namespace: 'html' }, visitors)
|
|
826
|
-
);
|
|
978
|
+
const program = walk(analysis.ast, { ...state }, visitors);
|
|
827
979
|
|
|
828
980
|
const css = render_stylesheets(state.stylesheets, minify_css);
|
|
829
981
|
|
|
@@ -832,7 +984,7 @@ export function transform_server(filename, source, analysis, minify_css) {
|
|
|
832
984
|
// Register each stylesheet's CSS
|
|
833
985
|
for (const stylesheet of state.stylesheets) {
|
|
834
986
|
const css_for_component = render_stylesheets([stylesheet]);
|
|
835
|
-
program.body.push(
|
|
987
|
+
/** @type {AST.Program} */ (program).body.push(
|
|
836
988
|
b.stmt(
|
|
837
989
|
b.call('_$_.register_css', b.literal(stylesheet.hash), b.literal(css_for_component)),
|
|
838
990
|
),
|
|
@@ -843,16 +995,21 @@ export function transform_server(filename, source, analysis, minify_css) {
|
|
|
843
995
|
// Add async property to component functions
|
|
844
996
|
|
|
845
997
|
for (const import_node of state.imports) {
|
|
846
|
-
program.body.unshift(b.stmt(b.id(import_node)));
|
|
998
|
+
/** @type {AST.Program} */ (program).body.unshift(b.stmt(b.id(import_node)));
|
|
847
999
|
}
|
|
848
1000
|
|
|
849
|
-
|
|
1001
|
+
// ESRap unfortunately hard codes the type from '@typescript-eslint/types'
|
|
1002
|
+
// Our types from 'estree' are incompatible
|
|
1003
|
+
// So we'll just give it what it wants with an unknown cast.
|
|
1004
|
+
// Functionally, none of the properties that are different between the two types
|
|
1005
|
+
// are used by the printer, so this is safe.
|
|
1006
|
+
const js = print(/** @type {TSESTree.Node} */ (/** @type {unknown} */ (program)), ts(), {
|
|
850
1007
|
sourceMapContent: source,
|
|
851
1008
|
sourceMapSource: path.basename(filename),
|
|
852
1009
|
});
|
|
853
1010
|
|
|
854
1011
|
return {
|
|
855
|
-
ast: program,
|
|
1012
|
+
ast: /** @type {AST.Program} */ (program),
|
|
856
1013
|
js,
|
|
857
1014
|
css,
|
|
858
1015
|
};
|