ripple 0.2.103 → 0.2.105
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 +2 -1
- package/src/compiler/phases/1-parse/index.js +40 -1
- package/src/compiler/phases/2-analyze/index.js +18 -3
- package/src/compiler/phases/3-transform/client/index.js +14 -0
- package/src/compiler/phases/3-transform/segments.js +531 -12
- package/src/compiler/phases/3-transform/server/index.js +237 -19
- package/src/compiler/types/index.d.ts +5 -0
- package/src/runtime/index-client.js +2 -0
- package/src/runtime/internal/client/css.js +70 -0
- package/src/runtime/internal/server/css-registry.js +35 -0
- package/src/runtime/internal/server/index.js +32 -17
- package/src/server/index.js +2 -1
- package/tests/client/__snapshots__/tracked-expression.test.ripple.snap +34 -0
- package/tests/client/compiler.test.ripple +98 -10
- package/tests/client/tracked-expression.test.ripple +46 -0
|
@@ -16,6 +16,7 @@ import {
|
|
|
16
16
|
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
|
+
import { render_stylesheets } from '../stylesheet.js';
|
|
19
20
|
|
|
20
21
|
function add_ripple_internal_import(context) {
|
|
21
22
|
if (!context.state.to_ts) {
|
|
@@ -93,17 +94,33 @@ const visitors = {
|
|
|
93
94
|
|
|
94
95
|
if (node.css !== null && node.css) {
|
|
95
96
|
context.state.stylesheets.push(node.css);
|
|
97
|
+
// Register CSS hash during rendering
|
|
98
|
+
body_statements.unshift(
|
|
99
|
+
b.stmt(
|
|
100
|
+
b.call(
|
|
101
|
+
b.member(b.id('__output'), b.id('register_css')),
|
|
102
|
+
b.literal(node.css.hash),
|
|
103
|
+
),
|
|
104
|
+
),
|
|
105
|
+
);
|
|
96
106
|
}
|
|
97
107
|
|
|
98
|
-
|
|
108
|
+
let component_fn = b.function(
|
|
99
109
|
node.id,
|
|
100
110
|
node.params.length > 0 ? [b.id('__output'), node.params[0]] : [b.id('__output')],
|
|
101
111
|
b.block([
|
|
102
112
|
...(metadata.await
|
|
103
|
-
? [b.
|
|
113
|
+
? [b.return(b.call('_$_.async', b.thunk(b.block(body_statements), true)))]
|
|
104
114
|
: body_statements),
|
|
105
115
|
]),
|
|
106
116
|
);
|
|
117
|
+
|
|
118
|
+
// Mark function as async if needed
|
|
119
|
+
if (metadata.await) {
|
|
120
|
+
component_fn = b.async(component_fn);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
return component_fn;
|
|
107
124
|
},
|
|
108
125
|
|
|
109
126
|
CallExpression(node, context) {
|
|
@@ -127,6 +144,20 @@ const visitors = {
|
|
|
127
144
|
return context.next();
|
|
128
145
|
},
|
|
129
146
|
|
|
147
|
+
TSTypeAliasDeclaration(_, context) {
|
|
148
|
+
if (!context.state.to_ts) {
|
|
149
|
+
return b.empty;
|
|
150
|
+
}
|
|
151
|
+
context.next();
|
|
152
|
+
},
|
|
153
|
+
|
|
154
|
+
TSInterfaceDeclaration(_, context) {
|
|
155
|
+
if (!context.state.to_ts) {
|
|
156
|
+
return b.empty;
|
|
157
|
+
}
|
|
158
|
+
context.next();
|
|
159
|
+
},
|
|
160
|
+
|
|
130
161
|
ExportNamedDeclaration(node, context) {
|
|
131
162
|
if (!context.state.to_ts && node.exportKind === 'type') {
|
|
132
163
|
return b.empty;
|
|
@@ -164,13 +195,10 @@ const visitors = {
|
|
|
164
195
|
let class_attribute = null;
|
|
165
196
|
|
|
166
197
|
const handle_static_attr = (name, value) => {
|
|
167
|
-
const
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
: `="${value === true ? '' : escape_html(value, true)}"`
|
|
172
|
-
}`,
|
|
173
|
-
);
|
|
198
|
+
const attr_str = ` ${name}${is_boolean_attribute(name) && value === true
|
|
199
|
+
? ''
|
|
200
|
+
: `="${value === true ? '' : escape_html(value, true)}"`
|
|
201
|
+
}`;
|
|
174
202
|
|
|
175
203
|
if (is_spreading) {
|
|
176
204
|
// For spread attributes, store just the actual value, not the full attribute string
|
|
@@ -181,7 +209,7 @@ const visitors = {
|
|
|
181
209
|
spread_attributes.push(b.prop('init', b.literal(name), actual_value));
|
|
182
210
|
} else {
|
|
183
211
|
state.init.push(
|
|
184
|
-
b.stmt(b.call(b.member(b.id('__output'), b.id('push')), b.literal(
|
|
212
|
+
b.stmt(b.call(b.member(b.id('__output'), b.id('push')), b.literal(attr_str))),
|
|
185
213
|
);
|
|
186
214
|
}
|
|
187
215
|
};
|
|
@@ -241,7 +269,8 @@ const visitors = {
|
|
|
241
269
|
let expression = visit(class_attribute.value, { ...state, metadata });
|
|
242
270
|
|
|
243
271
|
if (node.metadata.scoped && state.component.css) {
|
|
244
|
-
|
|
272
|
+
// Pass array to clsx so it can handle objects properly
|
|
273
|
+
expression = b.array([expression, b.literal(state.component.css.hash)]);
|
|
245
274
|
}
|
|
246
275
|
|
|
247
276
|
state.init.push(
|
|
@@ -256,7 +285,7 @@ const visitors = {
|
|
|
256
285
|
} else if (node.metadata.scoped && state.component.css) {
|
|
257
286
|
const value = state.component.css.hash;
|
|
258
287
|
|
|
259
|
-
|
|
288
|
+
handle_static_attr('class', value);
|
|
260
289
|
}
|
|
261
290
|
|
|
262
291
|
if (spread_attributes !== null && spread_attributes.length > 0) {
|
|
@@ -337,11 +366,33 @@ const visitors = {
|
|
|
337
366
|
}
|
|
338
367
|
}
|
|
339
368
|
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
369
|
+
// For SSR, determine if we should await based on component metadata
|
|
370
|
+
const component_call = b.call(visit(node.id, state), b.id('__output'), b.object(props));
|
|
371
|
+
|
|
372
|
+
// Check if this is a locally defined component and if it's async
|
|
373
|
+
const component_name = node.id.type === 'Identifier' ? node.id.name : null;
|
|
374
|
+
const local_metadata = component_name
|
|
375
|
+
? state.component_metadata.find((m) => m.id === component_name)
|
|
376
|
+
: null;
|
|
343
377
|
|
|
344
|
-
|
|
378
|
+
if (local_metadata) {
|
|
379
|
+
// Component is defined locally - we know if it's async or not
|
|
380
|
+
if (local_metadata.async) {
|
|
381
|
+
state.init.push(b.stmt(b.await(component_call)));
|
|
382
|
+
} else {
|
|
383
|
+
state.init.push(b.stmt(component_call));
|
|
384
|
+
}
|
|
385
|
+
} else {
|
|
386
|
+
// Component is imported or dynamic - check .async property at runtime
|
|
387
|
+
const conditional_await = b.conditional(
|
|
388
|
+
b.member(visit(node.id, state), b.id('async')),
|
|
389
|
+
b.await(component_call),
|
|
390
|
+
component_call
|
|
391
|
+
);
|
|
392
|
+
state.init.push(b.stmt(conditional_await));
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
}, ForOfStatement(node, context) {
|
|
345
396
|
if (!is_inside_component(context)) {
|
|
346
397
|
context.next();
|
|
347
398
|
return;
|
|
@@ -392,6 +443,131 @@ const visitors = {
|
|
|
392
443
|
}
|
|
393
444
|
},
|
|
394
445
|
|
|
446
|
+
ImportDeclaration(node, context) {
|
|
447
|
+
if (!context.state.to_ts && node.importKind === 'type') {
|
|
448
|
+
return b.empty;
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
return {
|
|
452
|
+
...node,
|
|
453
|
+
specifiers: node.specifiers
|
|
454
|
+
.filter((spec) => spec.importKind !== 'type')
|
|
455
|
+
.map((spec) => context.visit(spec)),
|
|
456
|
+
};
|
|
457
|
+
},
|
|
458
|
+
|
|
459
|
+
TryStatement(node, context) {
|
|
460
|
+
if (!is_inside_component(context)) {
|
|
461
|
+
return context.next();
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
// If there's a pending block, this is an async operation
|
|
465
|
+
const has_pending = node.pending !== null;
|
|
466
|
+
if (has_pending && context.state.metadata?.await === false) {
|
|
467
|
+
context.state.metadata.await = true;
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
const metadata = { await: false };
|
|
471
|
+
const body = transform_body(node.block.body, {
|
|
472
|
+
...context,
|
|
473
|
+
state: { ...context.state, metadata },
|
|
474
|
+
});
|
|
475
|
+
|
|
476
|
+
// Check if the try block itself contains async operations
|
|
477
|
+
const is_async = metadata.await || has_pending;
|
|
478
|
+
|
|
479
|
+
if (is_async) {
|
|
480
|
+
if (context.state.metadata?.await === false) {
|
|
481
|
+
context.state.metadata.await = true;
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
// For SSR with pending block: render the resolved content wrapped in async
|
|
485
|
+
// In a streaming SSR implementation, we'd render pending first, then stream resolved
|
|
486
|
+
const try_statements = node.handler !== null
|
|
487
|
+
? [
|
|
488
|
+
b.try(
|
|
489
|
+
b.block(body),
|
|
490
|
+
b.catch_clause(
|
|
491
|
+
node.handler.param || b.id('error'),
|
|
492
|
+
b.block(
|
|
493
|
+
transform_body(node.handler.body.body, {
|
|
494
|
+
...context,
|
|
495
|
+
state: { ...context.state, scope: context.state.scopes.get(node.handler.body) },
|
|
496
|
+
}),
|
|
497
|
+
),
|
|
498
|
+
),
|
|
499
|
+
),
|
|
500
|
+
]
|
|
501
|
+
: body;
|
|
502
|
+
|
|
503
|
+
context.state.init.push(
|
|
504
|
+
b.stmt(b.await(b.call('_$_.async', b.thunk(b.block(try_statements), true)))),
|
|
505
|
+
);
|
|
506
|
+
} else {
|
|
507
|
+
// No async, just regular try/catch
|
|
508
|
+
if (node.handler !== null) {
|
|
509
|
+
const handler_body = transform_body(node.handler.body.body, {
|
|
510
|
+
...context,
|
|
511
|
+
state: { ...context.state, scope: context.state.scopes.get(node.handler.body) },
|
|
512
|
+
});
|
|
513
|
+
|
|
514
|
+
context.state.init.push(
|
|
515
|
+
b.try(
|
|
516
|
+
b.block(body),
|
|
517
|
+
b.catch_clause(
|
|
518
|
+
node.handler.param || b.id('error'),
|
|
519
|
+
b.block(handler_body),
|
|
520
|
+
),
|
|
521
|
+
),
|
|
522
|
+
);
|
|
523
|
+
} else {
|
|
524
|
+
context.state.init.push(...body);
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
},
|
|
528
|
+
|
|
529
|
+
AwaitExpression(node, context) {
|
|
530
|
+
if (context.state.to_ts) {
|
|
531
|
+
return context.next();
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
if (context.state.metadata?.await === false) {
|
|
535
|
+
context.state.metadata.await = true;
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
return b.await(context.visit(node.argument));
|
|
539
|
+
},
|
|
540
|
+
|
|
541
|
+
TrackedObjectExpression(node, context) {
|
|
542
|
+
// For SSR, we just evaluate the object as-is since there's no reactivity
|
|
543
|
+
return b.object(node.properties.map((prop) => context.visit(prop)));
|
|
544
|
+
},
|
|
545
|
+
|
|
546
|
+
TrackedArrayExpression(node, context) {
|
|
547
|
+
// For SSR, we just evaluate the array as-is since there's no reactivity
|
|
548
|
+
return b.array(node.elements.map((el) => context.visit(el)));
|
|
549
|
+
},
|
|
550
|
+
|
|
551
|
+
MemberExpression(node, context) {
|
|
552
|
+
const parent = context.path.at(-1);
|
|
553
|
+
|
|
554
|
+
if (node.tracked || (node.property.type === 'Identifier' && node.property.tracked)) {
|
|
555
|
+
add_ripple_internal_import(context);
|
|
556
|
+
|
|
557
|
+
return b.call(
|
|
558
|
+
'_$_.get',
|
|
559
|
+
b.member(
|
|
560
|
+
context.visit(node.object),
|
|
561
|
+
node.computed ? context.visit(node.property) : node.property,
|
|
562
|
+
node.computed,
|
|
563
|
+
node.optional,
|
|
564
|
+
),
|
|
565
|
+
);
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
return context.next();
|
|
569
|
+
},
|
|
570
|
+
|
|
395
571
|
Text(node, { visit, state }) {
|
|
396
572
|
const metadata = { await: false };
|
|
397
573
|
const expression = visit(node.expression, { ...state, metadata });
|
|
@@ -408,21 +584,66 @@ const visitors = {
|
|
|
408
584
|
);
|
|
409
585
|
}
|
|
410
586
|
},
|
|
587
|
+
|
|
588
|
+
Html(node, { visit, state }) {
|
|
589
|
+
const metadata = { await: false };
|
|
590
|
+
const expression = visit(node.expression, { ...state, metadata });
|
|
591
|
+
|
|
592
|
+
// For Html nodes, we render the content as-is without escaping
|
|
593
|
+
if (expression.type === 'Literal') {
|
|
594
|
+
state.init.push(
|
|
595
|
+
b.stmt(b.call(b.member(b.id('__output'), b.id('push')), b.literal(expression.value))),
|
|
596
|
+
);
|
|
597
|
+
} else {
|
|
598
|
+
// If it's dynamic, we need to evaluate it and push it directly (not escaped)
|
|
599
|
+
state.init.push(b.stmt(b.call(b.member(b.id('__output'), b.id('push')), expression)));
|
|
600
|
+
}
|
|
601
|
+
},
|
|
411
602
|
};
|
|
412
603
|
|
|
413
604
|
export function transform_server(filename, source, analysis) {
|
|
605
|
+
// Use component metadata collected during the analyze phase
|
|
606
|
+
const component_metadata = analysis.component_metadata || [];
|
|
607
|
+
|
|
414
608
|
const state = {
|
|
415
609
|
imports: new Set(),
|
|
416
610
|
init: null,
|
|
417
611
|
scope: analysis.scope,
|
|
418
612
|
scopes: analysis.scopes,
|
|
419
613
|
stylesheets: [],
|
|
614
|
+
component_metadata,
|
|
420
615
|
};
|
|
421
616
|
|
|
422
617
|
const program = /** @type {ESTree.Program} */ (
|
|
423
618
|
walk(analysis.ast, { ...state, namespace: 'html' }, visitors)
|
|
424
619
|
);
|
|
425
620
|
|
|
621
|
+
const css = render_stylesheets(state.stylesheets);
|
|
622
|
+
|
|
623
|
+
// Add CSS registration if there are stylesheets
|
|
624
|
+
if (state.stylesheets.length > 0 && css) {
|
|
625
|
+
// Register each stylesheet's CSS
|
|
626
|
+
for (const stylesheet of state.stylesheets) {
|
|
627
|
+
const css_for_component = render_stylesheets([stylesheet]);
|
|
628
|
+
program.body.push(
|
|
629
|
+
b.stmt(
|
|
630
|
+
b.call('_$_.register_css', b.literal(stylesheet.hash), b.literal(css_for_component)),
|
|
631
|
+
),
|
|
632
|
+
);
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
// Add async property to component functions
|
|
637
|
+
for (const metadata of state.component_metadata) {
|
|
638
|
+
if (metadata.async) {
|
|
639
|
+
program.body.push(
|
|
640
|
+
b.stmt(
|
|
641
|
+
b.assignment('=', b.member(b.id(metadata.id), b.id('async')), b.true),
|
|
642
|
+
),
|
|
643
|
+
);
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
|
|
426
647
|
for (const import_node of state.imports) {
|
|
427
648
|
program.body.unshift(b.stmt(b.id(import_node)));
|
|
428
649
|
}
|
|
@@ -432,9 +653,6 @@ export function transform_server(filename, source, analysis) {
|
|
|
432
653
|
sourceMapSource: path.basename(filename),
|
|
433
654
|
});
|
|
434
655
|
|
|
435
|
-
// TODO: extract css
|
|
436
|
-
const css = '';
|
|
437
|
-
|
|
438
656
|
return {
|
|
439
657
|
ast: program,
|
|
440
658
|
js,
|
|
@@ -65,6 +65,11 @@ export interface TrackedArrayExpression extends Omit<ArrayExpression, 'type'> {
|
|
|
65
65
|
elements: (Expression | null)[];
|
|
66
66
|
}
|
|
67
67
|
|
|
68
|
+
export interface TrackedExpression extends Omit<Expression, 'type'> {
|
|
69
|
+
argument: Expression;
|
|
70
|
+
type: 'TrackedExpression';
|
|
71
|
+
}
|
|
72
|
+
|
|
68
73
|
/**
|
|
69
74
|
* Tracked object expression node
|
|
70
75
|
*/
|
|
@@ -5,6 +5,7 @@ import { handle_root_events } from './internal/client/events.js';
|
|
|
5
5
|
import { init_operations } from './internal/client/operations.js';
|
|
6
6
|
import { active_block, tracked, derived } from './internal/client/runtime.js';
|
|
7
7
|
import { create_anchor } from './internal/client/utils.js';
|
|
8
|
+
import { remove_ssr_css } from './internal/client/css.js';
|
|
8
9
|
|
|
9
10
|
// Re-export JSX runtime functions for jsxImportSource: "ripple"
|
|
10
11
|
export { jsx, jsxs, Fragment } from '../jsx-runtime.js';
|
|
@@ -16,6 +17,7 @@ export { jsx, jsxs, Fragment } from '../jsx-runtime.js';
|
|
|
16
17
|
*/
|
|
17
18
|
export function mount(component, options) {
|
|
18
19
|
init_operations();
|
|
20
|
+
remove_ssr_css();
|
|
19
21
|
|
|
20
22
|
const props = options.props || {};
|
|
21
23
|
const target = options.target;
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { DEV } from 'esm-env';
|
|
2
|
+
|
|
3
|
+
export function remove_ssr_css() {
|
|
4
|
+
if (!document || typeof requestAnimationFrame !== "function") {
|
|
5
|
+
return;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
remove_styles();
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function remove_styles() {
|
|
12
|
+
if (DEV) {
|
|
13
|
+
const styles = document.querySelector('style[data-vite-dev-id]');
|
|
14
|
+
if (styles) {
|
|
15
|
+
remove();
|
|
16
|
+
} else {
|
|
17
|
+
requestAnimationFrame(remove_styles);
|
|
18
|
+
}
|
|
19
|
+
} else {
|
|
20
|
+
remove_when_css_loaded(() => requestAnimationFrame(remove));
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function remove() {
|
|
25
|
+
document.querySelectorAll("style[data-ripple-ssr]").forEach((el) => el.remove());
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* @param {function} callback
|
|
30
|
+
* @returns {void}
|
|
31
|
+
*/
|
|
32
|
+
function remove_when_css_loaded(callback) {
|
|
33
|
+
/** @type {HTMLLinkElement[]} */
|
|
34
|
+
const links = Array.from(document.querySelectorAll('link[rel="stylesheet"]'));
|
|
35
|
+
let remaining = links.length;
|
|
36
|
+
|
|
37
|
+
if (remaining === 0) {
|
|
38
|
+
callback();
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const done = () => {
|
|
43
|
+
remaining--;
|
|
44
|
+
if (remaining === 0) {
|
|
45
|
+
// clean up all listeners
|
|
46
|
+
links.forEach((link) => {
|
|
47
|
+
link.removeEventListener('load', onLoad);
|
|
48
|
+
link.removeEventListener('error', onError);
|
|
49
|
+
});
|
|
50
|
+
callback();
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
function onLoad() {
|
|
55
|
+
done();
|
|
56
|
+
}
|
|
57
|
+
function onError() {
|
|
58
|
+
done();
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
links.forEach((link) => {
|
|
62
|
+
if (link.sheet) {
|
|
63
|
+
// already loaded (possibly cached)
|
|
64
|
+
done();
|
|
65
|
+
} else {
|
|
66
|
+
link.addEventListener('load', onLoad);
|
|
67
|
+
link.addEventListener('error', onError);
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Global CSS registry for SSR
|
|
3
|
+
* Maps CSS hashes to their content
|
|
4
|
+
* This persists across requests for performance (CSS is immutable per hash)
|
|
5
|
+
* @type {Map<string, string>}
|
|
6
|
+
*/
|
|
7
|
+
const css_registry = new Map();
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Register a component's CSS
|
|
11
|
+
* Only sets if the hash doesn't already exist (CSS is immutable per hash)
|
|
12
|
+
* @param {string} hash - The CSS hash
|
|
13
|
+
* @param {string} content - The CSS content
|
|
14
|
+
*/
|
|
15
|
+
export function register_component_css(hash, content) {
|
|
16
|
+
if (!css_registry.has(hash)) {
|
|
17
|
+
css_registry.set(hash, content);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Get CSS content for a set of hashes
|
|
23
|
+
* @param {Set<string>} hashes
|
|
24
|
+
* @returns {string}
|
|
25
|
+
*/
|
|
26
|
+
export function get_css_for_hashes(hashes) {
|
|
27
|
+
const css_parts = [];
|
|
28
|
+
for (const hash of hashes) {
|
|
29
|
+
const content = css_registry.get(hash);
|
|
30
|
+
if (content) {
|
|
31
|
+
css_parts.push(content);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return css_parts.join('\n');
|
|
35
|
+
}
|
|
@@ -1,14 +1,12 @@
|
|
|
1
1
|
/** @import { Component, Derived } from '#server' */
|
|
2
|
-
|
|
3
|
-
import {
|
|
4
|
-
import { is_tracked_object } from '../client/utils';
|
|
5
|
-
|
|
2
|
+
import { DERIVED, UNINITIALIZED } from '../client/constants.js';
|
|
3
|
+
import { is_tracked_object } from '../client/utils.js';
|
|
6
4
|
import { escape } from '../../../utils/escaping.js';
|
|
7
5
|
import { is_boolean_attribute } from '../../../compiler/utils';
|
|
8
|
-
|
|
9
6
|
import { clsx } from 'clsx';
|
|
10
7
|
|
|
11
8
|
export { escape };
|
|
9
|
+
export { register_component_css as register_css } from './css-registry.js';
|
|
12
10
|
|
|
13
11
|
/** @type {Component | null} */
|
|
14
12
|
export let active_component = null;
|
|
@@ -29,6 +27,8 @@ const replacements = {
|
|
|
29
27
|
class Output {
|
|
30
28
|
head = '';
|
|
31
29
|
body = '';
|
|
30
|
+
/** @type {Set<string>} */
|
|
31
|
+
css = new Set();
|
|
32
32
|
/** @type {Output | null} */
|
|
33
33
|
#parent = null;
|
|
34
34
|
|
|
@@ -46,25 +46,32 @@ class Output {
|
|
|
46
46
|
push(str) {
|
|
47
47
|
this.body += str;
|
|
48
48
|
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* @param {string} hash
|
|
52
|
+
* @returns {void}
|
|
53
|
+
*/
|
|
54
|
+
register_css(hash) {
|
|
55
|
+
this.css.add(hash);
|
|
56
|
+
}
|
|
49
57
|
}
|
|
50
58
|
|
|
51
59
|
/**
|
|
52
60
|
* @param {((output: Output, props: Record<string, any>) => void | Promise<void>) & { async?: boolean }} component
|
|
53
|
-
* @returns {Promise<{head: string, body: string}>}
|
|
61
|
+
* @returns {Promise<{head: string, body: string, css: Set<string>}>}
|
|
54
62
|
*/
|
|
55
63
|
export async function render(component) {
|
|
56
64
|
const output = new Output(null);
|
|
57
65
|
|
|
58
|
-
// TODO add expando "async" property to component functions during SSR
|
|
59
66
|
if (component.async) {
|
|
60
67
|
await component(output, {});
|
|
61
68
|
} else {
|
|
62
69
|
component(output, {});
|
|
63
70
|
}
|
|
64
71
|
|
|
65
|
-
const { head, body } = output;
|
|
72
|
+
const { head, body, css } = output;
|
|
66
73
|
|
|
67
|
-
return { head, body };
|
|
74
|
+
return { head, body, css };
|
|
68
75
|
}
|
|
69
76
|
|
|
70
77
|
/**
|
|
@@ -91,7 +98,15 @@ export function pop_component() {
|
|
|
91
98
|
* @returns {Promise<void>}
|
|
92
99
|
*/
|
|
93
100
|
export async function async(fn) {
|
|
94
|
-
|
|
101
|
+
await fn();
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* @returns {boolean}
|
|
106
|
+
*/
|
|
107
|
+
export function aborted() {
|
|
108
|
+
// For SSR, we don't abort rendering
|
|
109
|
+
return false;
|
|
95
110
|
}
|
|
96
111
|
|
|
97
112
|
/**
|
|
@@ -118,7 +133,7 @@ export function get(tracked) {
|
|
|
118
133
|
return tracked;
|
|
119
134
|
}
|
|
120
135
|
|
|
121
|
-
return (tracked.f & DERIVED) !== 0 ? get_derived(/** @type {Derived} */
|
|
136
|
+
return (tracked.f & DERIVED) !== 0 ? get_derived(/** @type {Derived} */(tracked)) : tracked.v;
|
|
122
137
|
}
|
|
123
138
|
|
|
124
139
|
/**
|
|
@@ -153,13 +168,13 @@ export function spread_attrs(attrs, css_hash) {
|
|
|
153
168
|
|
|
154
169
|
if (typeof value === 'function') continue;
|
|
155
170
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
171
|
+
if (is_tracked_object(value)) {
|
|
172
|
+
value = get(value);
|
|
173
|
+
}
|
|
159
174
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
175
|
+
if (name === 'class' && css_hash) {
|
|
176
|
+
value = value == null ? css_hash : [value, css_hash];
|
|
177
|
+
}
|
|
163
178
|
|
|
164
179
|
attr_str += attr(name, value, is_boolean_attribute(name));
|
|
165
180
|
}
|
package/src/server/index.js
CHANGED
|
@@ -1 +1,2 @@
|
|
|
1
|
-
export { render } from '../runtime/internal/server/index.js';
|
|
1
|
+
export { render } from '../runtime/internal/server/index.js';
|
|
2
|
+
export { get_css_for_hashes } from '../runtime/internal/server/css-registry.js';
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
|
2
|
+
|
|
3
|
+
exports[`TrackedExpression tests > should handle the syntax correctly 1`] = `
|
|
4
|
+
<div>
|
|
5
|
+
<div>
|
|
6
|
+
0
|
|
7
|
+
</div>
|
|
8
|
+
<div>
|
|
9
|
+
0
|
|
10
|
+
</div>
|
|
11
|
+
<div>
|
|
12
|
+
1
|
|
13
|
+
</div>
|
|
14
|
+
<div>
|
|
15
|
+
2
|
|
16
|
+
</div>
|
|
17
|
+
<div>
|
|
18
|
+
2
|
|
19
|
+
</div>
|
|
20
|
+
<div>
|
|
21
|
+
3
|
|
22
|
+
</div>
|
|
23
|
+
<div>
|
|
24
|
+
4
|
|
25
|
+
</div>
|
|
26
|
+
<div>
|
|
27
|
+
false
|
|
28
|
+
</div>
|
|
29
|
+
<div>
|
|
30
|
+
true
|
|
31
|
+
</div>
|
|
32
|
+
|
|
33
|
+
</div>
|
|
34
|
+
`;
|