ripple 0.2.151 → 0.2.153
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/README.md +3 -3
- package/package.json +5 -5
- package/src/compiler/phases/1-parse/index.js +1 -1
- package/src/compiler/phases/3-transform/client/index.js +37 -16
- package/src/compiler/phases/3-transform/server/index.js +43 -25
- package/src/runtime/internal/client/events.js +5 -1
- package/src/runtime/internal/client/index.js +2 -1
- package/src/runtime/internal/client/render.js +18 -15
- package/src/runtime/internal/client/runtime.js +78 -10
- package/src/runtime/internal/server/index.js +51 -11
- package/src/server/index.js +1 -1
- package/tests/client/array/array.derived.test.ripple +61 -33
- package/tests/client/array/array.iteration.test.ripple +3 -1
- package/tests/client/array/array.mutations.test.ripple +19 -15
- package/tests/client/array/array.static.test.ripple +115 -104
- package/tests/client/array/array.to-methods.test.ripple +3 -3
- package/tests/client/basic/basic.attributes.test.ripple +110 -57
- package/tests/client/basic/basic.collections.test.ripple +41 -22
- package/tests/client/basic/basic.errors.test.ripple +12 -6
- package/tests/client/basic/basic.events.test.ripple +51 -33
- package/tests/client/basic/basic.reactivity.test.ripple +120 -56
- package/tests/client/basic/basic.rendering.test.ripple +49 -19
- package/tests/client/basic/basic.styling.test.ripple +2 -2
- package/tests/client/basic/basic.utilities.test.ripple +1 -1
- package/tests/client/boundaries.test.ripple +70 -58
- package/tests/client/compiler/compiler.assignments.test.ripple +32 -4
- package/tests/client/compiler/compiler.attributes.test.ripple +46 -46
- package/tests/client/compiler/compiler.basic.test.ripple +18 -15
- package/tests/client/compiler/compiler.tracked-access.test.ripple +53 -42
- package/tests/client/compiler/compiler.typescript.test.ripple +1 -2
- package/tests/client/composite/composite.dynamic-components.test.ripple +6 -6
- package/tests/client/composite/composite.generics.test.ripple +39 -36
- package/tests/client/composite/composite.props.test.ripple +4 -3
- package/tests/client/composite/composite.reactivity.test.ripple +112 -27
- package/tests/client/composite/composite.render.test.ripple +9 -8
- package/tests/client/computed-properties.test.ripple +24 -24
- package/tests/client/context.test.ripple +11 -9
- package/tests/client/date.test.ripple +3 -1
- package/tests/client/dynamic-elements.test.ripple +103 -78
- package/tests/client/for.test.ripple +27 -17
- package/tests/client/head.test.ripple +42 -6
- package/tests/client/html.test.ripple +42 -32
- package/tests/client/input-value.test.ripple +4 -4
- package/tests/client/map.test.ripple +140 -141
- package/tests/client/media-query.test.ripple +31 -31
- package/tests/client/object.test.ripple +148 -112
- package/tests/client/portal.test.ripple +29 -15
- package/tests/client/ref.test.ripple +9 -3
- package/tests/client/set.test.ripple +111 -111
- package/tests/client/tracked-expression.test.ripple +16 -17
- package/tests/client/url/url.derived.test.ripple +19 -9
- package/tests/client/url/url.parsing.test.ripple +24 -8
- package/tests/client/url/url.partial-removal.test.ripple +12 -4
- package/tests/client/url/url.reactivity.test.ripple +63 -25
- package/tests/client/url/url.serialization.test.ripple +18 -6
- package/tests/client/url-search-params/url-search-params.derived.test.ripple +10 -6
- package/tests/client/url-search-params/url-search-params.iteration.test.ripple +3 -1
- package/tests/client/url-search-params/url-search-params.mutation.test.ripple +26 -14
- package/tests/client/url-search-params/url-search-params.tracked-url.test.ripple +3 -1
- package/tests/server/await.test.ripple +23 -22
- package/tests/server/basic.test.ripple +1 -1
- package/tests/server/compiler.test.ripple +3 -7
- package/tests/server/composite.test.ripple +38 -36
- package/tests/server/for.test.ripple +9 -5
- package/tests/server/if.test.ripple +1 -1
- package/tests/server/streaming-ssr.test.ripple +67 -0
- package/types/server.d.ts +5 -4
package/README.md
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
<picture>
|
|
2
|
-
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/
|
|
3
|
-
<img src="https://raw.githubusercontent.com/
|
|
2
|
+
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/Ripple-TS/ripple/master/assets/ripple-dark.png">
|
|
3
|
+
<img src="https://raw.githubusercontent.com/Ripple-TS/ripple/master/assets/ripple-light.png" alt="Ripple - the elegant TypeScript UI framework" />
|
|
4
4
|
</picture>
|
|
5
5
|
|
|
6
6
|
# What is Ripple?
|
|
7
7
|
|
|
8
|
-
Ripple is an elegant TypeScript UI framework. To find out more, view [Ripple's Github README](https://github.com/
|
|
8
|
+
Ripple is an elegant TypeScript UI framework. To find out more, view [Ripple's Github README](https://github.com/Ripple-TS/ripple).
|
package/package.json
CHANGED
|
@@ -3,19 +3,19 @@
|
|
|
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.153",
|
|
7
7
|
"type": "module",
|
|
8
8
|
"module": "src/runtime/index-client.js",
|
|
9
9
|
"main": "src/runtime/index-client.js",
|
|
10
10
|
"repository": {
|
|
11
11
|
"type": "git",
|
|
12
|
-
"url": "git+https://github.com/
|
|
12
|
+
"url": "git+https://github.com/Ripple-TS/ripple.git",
|
|
13
13
|
"directory": "packages/ripple"
|
|
14
14
|
},
|
|
15
15
|
"bugs": {
|
|
16
|
-
"url": "https://github.com/
|
|
16
|
+
"url": "https://github.com/Ripple-TS/ripple/issues"
|
|
17
17
|
},
|
|
18
|
-
"homepage": "https://
|
|
18
|
+
"homepage": "https://ripple-ts.com",
|
|
19
19
|
"keywords": [
|
|
20
20
|
"ripple",
|
|
21
21
|
"UI",
|
|
@@ -81,6 +81,6 @@
|
|
|
81
81
|
"typescript": "^5.9.2"
|
|
82
82
|
},
|
|
83
83
|
"peerDependencies": {
|
|
84
|
-
"ripple": "0.2.
|
|
84
|
+
"ripple": "0.2.153"
|
|
85
85
|
}
|
|
86
86
|
}
|
|
@@ -1795,7 +1795,7 @@ function RipplePlugin(config) {
|
|
|
1795
1795
|
/**
|
|
1796
1796
|
* Acorn doesn't add comments to the AST by itself. This factory returns the capabilities
|
|
1797
1797
|
* to add them after the fact. They are needed in order to support `ripple-ignore` comments
|
|
1798
|
-
* in JS code and so that `prettier-plugin
|
|
1798
|
+
* in JS code and so that `prettier-plugin` doesn't remove all comments when formatting.
|
|
1799
1799
|
* @param {string} source
|
|
1800
1800
|
* @param {CommentWithLocation[]} comments
|
|
1801
1801
|
* @param {number} [index=0] - Starting index
|
|
@@ -1240,29 +1240,50 @@ const visitors = {
|
|
|
1240
1240
|
// We're calling a component from within svg/mathml context
|
|
1241
1241
|
const is_with_ns = state.namespace !== DEFAULT_NAMESPACE;
|
|
1242
1242
|
|
|
1243
|
+
let object_props;
|
|
1244
|
+
if (is_spreading) {
|
|
1245
|
+
// Optimization: if only one spread with no other props, pass it directly
|
|
1246
|
+
if (props.length === 1 && props[0].type === 'SpreadElement') {
|
|
1247
|
+
object_props = b.call('_$_.spread_props', b.thunk(props[0].argument));
|
|
1248
|
+
} else {
|
|
1249
|
+
// Multiple items: build array of objects/spreads for proper merge order
|
|
1250
|
+
const items = [];
|
|
1251
|
+
let current_obj_props = [];
|
|
1252
|
+
|
|
1253
|
+
for (const prop of props) {
|
|
1254
|
+
if (prop.type === 'SpreadElement') {
|
|
1255
|
+
// Flush accumulated regular props as an object
|
|
1256
|
+
if (current_obj_props.length > 0) {
|
|
1257
|
+
items.push(b.object(current_obj_props));
|
|
1258
|
+
current_obj_props = [];
|
|
1259
|
+
}
|
|
1260
|
+
// Add the spread argument directly
|
|
1261
|
+
items.push(prop.argument);
|
|
1262
|
+
} else {
|
|
1263
|
+
// Accumulate regular properties
|
|
1264
|
+
current_obj_props.push(prop);
|
|
1265
|
+
}
|
|
1266
|
+
}
|
|
1267
|
+
|
|
1268
|
+
// Flush any remaining regular props
|
|
1269
|
+
if (current_obj_props.length > 0) {
|
|
1270
|
+
items.push(b.object(current_obj_props));
|
|
1271
|
+
}
|
|
1272
|
+
|
|
1273
|
+
object_props = b.call('_$_.spread_props', b.thunk(b.array(items)));
|
|
1274
|
+
}
|
|
1275
|
+
} else {
|
|
1276
|
+
object_props = b.object(props);
|
|
1277
|
+
}
|
|
1243
1278
|
if (metadata.tracking) {
|
|
1244
|
-
const shared = b.call(
|
|
1245
|
-
'_$_.composite',
|
|
1246
|
-
b.thunk(visit(node.id, state)),
|
|
1247
|
-
id,
|
|
1248
|
-
is_spreading
|
|
1249
|
-
? b.call('_$_.spread_props', b.thunk(b.object(props)), b.id('__block'))
|
|
1250
|
-
: b.object(props),
|
|
1251
|
-
);
|
|
1279
|
+
const shared = b.call('_$_.composite', b.thunk(visit(node.id, state)), id, object_props);
|
|
1252
1280
|
state.init.push(
|
|
1253
1281
|
is_with_ns
|
|
1254
1282
|
? b.call('_$_.with_ns', b.literal(state.namespace), b.thunk(shared))
|
|
1255
1283
|
: b.stmt(shared),
|
|
1256
1284
|
);
|
|
1257
1285
|
} else {
|
|
1258
|
-
const shared = b.call(
|
|
1259
|
-
visit(node.id, state),
|
|
1260
|
-
id,
|
|
1261
|
-
is_spreading
|
|
1262
|
-
? b.call('_$_.spread_props', b.thunk(b.object(props)), b.id('__block'))
|
|
1263
|
-
: b.object(props),
|
|
1264
|
-
b.id('_$_.active_block'),
|
|
1265
|
-
);
|
|
1286
|
+
const shared = b.call(visit(node.id, state), id, object_props, b.id('_$_.active_block'));
|
|
1266
1287
|
state.init.push(
|
|
1267
1288
|
is_with_ns
|
|
1268
1289
|
? b.call('_$_.with_ns', b.literal(state.namespace), b.thunk(shared))
|
|
@@ -120,14 +120,23 @@ const visitors = {
|
|
|
120
120
|
component_fn = b.async(component_fn);
|
|
121
121
|
}
|
|
122
122
|
|
|
123
|
-
const declaration = b.function_declaration(
|
|
123
|
+
const declaration = b.function_declaration(
|
|
124
|
+
node.id,
|
|
125
|
+
component_fn.params,
|
|
126
|
+
component_fn.body,
|
|
127
|
+
component_fn.async,
|
|
128
|
+
);
|
|
124
129
|
|
|
125
130
|
if (metadata.await) {
|
|
126
131
|
const parent = context.path.at(-1);
|
|
127
132
|
if (parent.type === 'Program' || parent.type === 'BlockStatement') {
|
|
128
133
|
const body = parent.body;
|
|
129
134
|
const index = body.indexOf(node);
|
|
130
|
-
body.splice(
|
|
135
|
+
body.splice(
|
|
136
|
+
index + 1,
|
|
137
|
+
0,
|
|
138
|
+
b.stmt(b.assignment('=', b.member(node.id, b.id('async')), b.true)),
|
|
139
|
+
);
|
|
131
140
|
}
|
|
132
141
|
}
|
|
133
142
|
|
|
@@ -281,10 +290,11 @@ const visitors = {
|
|
|
281
290
|
let class_attribute = null;
|
|
282
291
|
|
|
283
292
|
const handle_static_attr = (name, value) => {
|
|
284
|
-
const attr_str = ` ${name}${
|
|
293
|
+
const attr_str = ` ${name}${
|
|
294
|
+
is_boolean_attribute(name) && value === true
|
|
285
295
|
? ''
|
|
286
296
|
: `="${value === true ? '' : escape_html(value, true)}"`
|
|
287
|
-
|
|
297
|
+
}`;
|
|
288
298
|
|
|
289
299
|
if (is_spreading) {
|
|
290
300
|
// For spread attributes, store just the actual value, not the full attribute string
|
|
@@ -566,15 +576,13 @@ const visitors = {
|
|
|
566
576
|
return b.call(
|
|
567
577
|
'set',
|
|
568
578
|
context.visit(left, { ...context.state, metadata: { tracking: false } }),
|
|
569
|
-
context.visit(node.right)
|
|
579
|
+
context.visit(node.right),
|
|
570
580
|
);
|
|
571
581
|
}
|
|
572
582
|
|
|
573
583
|
return context.next();
|
|
574
584
|
},
|
|
575
585
|
|
|
576
|
-
|
|
577
|
-
|
|
578
586
|
ServerIdentifier(node, context) {
|
|
579
587
|
return b.id('_$_server_$_');
|
|
580
588
|
},
|
|
@@ -617,27 +625,36 @@ const visitors = {
|
|
|
617
625
|
context.state.metadata.await = true;
|
|
618
626
|
}
|
|
619
627
|
|
|
628
|
+
// Render pending block first
|
|
629
|
+
if (node.pending) {
|
|
630
|
+
const pending_body = transform_body(node.pending.body, {
|
|
631
|
+
...context,
|
|
632
|
+
state: { ...context.state, scope: context.state.scopes.get(node.pending) },
|
|
633
|
+
});
|
|
634
|
+
context.state.init.push(...pending_body);
|
|
635
|
+
}
|
|
636
|
+
|
|
620
637
|
// For SSR with pending block: render the resolved content wrapped in async
|
|
621
638
|
// In a streaming SSR implementation, we'd render pending first, then stream resolved
|
|
622
639
|
const try_statements =
|
|
623
640
|
node.handler !== null
|
|
624
641
|
? [
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
642
|
+
b.try(
|
|
643
|
+
b.block(body),
|
|
644
|
+
b.catch_clause(
|
|
645
|
+
node.handler.param || b.id('error'),
|
|
646
|
+
b.block(
|
|
647
|
+
transform_body(node.handler.body.body, {
|
|
648
|
+
...context,
|
|
649
|
+
state: {
|
|
650
|
+
...context.state,
|
|
651
|
+
scope: context.state.scopes.get(node.handler.body),
|
|
652
|
+
},
|
|
653
|
+
}),
|
|
654
|
+
),
|
|
637
655
|
),
|
|
638
656
|
),
|
|
639
|
-
|
|
640
|
-
]
|
|
657
|
+
]
|
|
641
658
|
: body;
|
|
642
659
|
|
|
643
660
|
context.state.init.push(
|
|
@@ -664,8 +681,8 @@ const visitors = {
|
|
|
664
681
|
},
|
|
665
682
|
|
|
666
683
|
AwaitExpression(node, context) {
|
|
667
|
-
context.state.scope.server_block = true
|
|
668
|
-
context.inside_server_block = true
|
|
684
|
+
context.state.scope.server_block = true;
|
|
685
|
+
context.inside_server_block = true;
|
|
669
686
|
if (context.state.to_ts) {
|
|
670
687
|
return context.next();
|
|
671
688
|
}
|
|
@@ -715,7 +732,9 @@ const visitors = {
|
|
|
715
732
|
|
|
716
733
|
if (expression.type === 'Literal') {
|
|
717
734
|
state.init.push(
|
|
718
|
-
b.stmt(
|
|
735
|
+
b.stmt(
|
|
736
|
+
b.call(b.member(b.id('__output'), b.id('push')), b.literal(escape(expression.value))),
|
|
737
|
+
),
|
|
719
738
|
);
|
|
720
739
|
} else {
|
|
721
740
|
state.init.push(
|
|
@@ -810,7 +829,6 @@ export function transform_server(filename, source, analysis) {
|
|
|
810
829
|
}
|
|
811
830
|
|
|
812
831
|
// Add async property to component functions
|
|
813
|
-
|
|
814
832
|
|
|
815
833
|
for (const import_node of state.imports) {
|
|
816
834
|
program.body.unshift(b.stmt(b.id(import_node)));
|
|
@@ -29,6 +29,8 @@ export function on(element, type, handler, options = {}) {
|
|
|
29
29
|
};
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
+
let last_propagated_event = null;
|
|
33
|
+
|
|
32
34
|
/**
|
|
33
35
|
* @this {EventTarget}
|
|
34
36
|
* @param {Event} event
|
|
@@ -41,6 +43,8 @@ export function handle_event_propagation(event) {
|
|
|
41
43
|
var path = event.composedPath?.() || [];
|
|
42
44
|
var current_target = /** @type {null | Element} */ (path[0] || event.target);
|
|
43
45
|
|
|
46
|
+
last_propagated_event = event;
|
|
47
|
+
|
|
44
48
|
// composedPath contains list of nodes the event has propagated through.
|
|
45
49
|
// We check __root to skip all nodes below it in case this is a
|
|
46
50
|
// parent of the __root node, which indicates that there's nested
|
|
@@ -48,7 +52,7 @@ export function handle_event_propagation(event) {
|
|
|
48
52
|
var path_idx = 0;
|
|
49
53
|
|
|
50
54
|
// @ts-expect-error is added below
|
|
51
|
-
var handled_at = event.__root;
|
|
55
|
+
var handled_at = last_propagated_event === event && event.__root;
|
|
52
56
|
|
|
53
57
|
if (handled_at) {
|
|
54
58
|
var at_idx = path.indexOf(handled_at);
|
|
@@ -124,17 +124,21 @@ export function apply_styles(element, newStyles) {
|
|
|
124
124
|
|
|
125
125
|
/**
|
|
126
126
|
* @param {Element} element
|
|
127
|
-
* @param {Record<string, any>}
|
|
127
|
+
* @param {Record<string, any>} prev
|
|
128
|
+
* @param {Record<string, any>} next
|
|
128
129
|
* @returns {void}
|
|
129
130
|
*/
|
|
130
|
-
export function set_attributes(element,
|
|
131
|
+
export function set_attributes(element, prev, next) {
|
|
131
132
|
let found_enumerable_keys = false;
|
|
132
133
|
|
|
133
|
-
for (const key in
|
|
134
|
+
for (const key in next) {
|
|
134
135
|
if (key === 'children') continue;
|
|
135
136
|
found_enumerable_keys = true;
|
|
136
137
|
|
|
137
|
-
let value =
|
|
138
|
+
let value = next[key];
|
|
139
|
+
if (prev[key] === value && key !== '#class') {
|
|
140
|
+
continue;
|
|
141
|
+
}
|
|
138
142
|
if (is_tracked_object(value)) {
|
|
139
143
|
value = get(value);
|
|
140
144
|
}
|
|
@@ -143,13 +147,16 @@ export function set_attributes(element, attributes) {
|
|
|
143
147
|
|
|
144
148
|
// Only if no enumerable keys but attributes object exists
|
|
145
149
|
// This handles spread_props Proxy objects from dynamic elements with {...spread}
|
|
146
|
-
if (!found_enumerable_keys &&
|
|
147
|
-
const allKeys = Reflect.ownKeys(
|
|
150
|
+
if (!found_enumerable_keys && next) {
|
|
151
|
+
const allKeys = Reflect.ownKeys(next);
|
|
148
152
|
for (const key of allKeys) {
|
|
149
153
|
if (key === 'children') continue;
|
|
150
154
|
if (typeof key === 'symbol') continue; // Skip symbols - handled by apply_element_spread
|
|
151
155
|
|
|
152
|
-
let value =
|
|
156
|
+
let value = next[key];
|
|
157
|
+
if (prev[key] === value && key !== '#class') {
|
|
158
|
+
continue;
|
|
159
|
+
}
|
|
153
160
|
if (is_tracked_object(value)) {
|
|
154
161
|
value = get(value);
|
|
155
162
|
}
|
|
@@ -285,13 +292,13 @@ export function set_selected(element, selected) {
|
|
|
285
292
|
* @returns {() => void}
|
|
286
293
|
*/
|
|
287
294
|
export function apply_element_spread(element, fn) {
|
|
288
|
-
/** @type {Record<string | symbol, any>
|
|
289
|
-
var prev;
|
|
295
|
+
/** @type {Record<string | symbol, any>} */
|
|
296
|
+
var prev = {};
|
|
290
297
|
/** @type {Record<symbol, Block>} */
|
|
291
298
|
var effects = {};
|
|
292
299
|
|
|
293
300
|
return () => {
|
|
294
|
-
var next = fn();
|
|
301
|
+
var next = { ...fn() };
|
|
295
302
|
|
|
296
303
|
for (let symbol of get_own_property_symbols(effects)) {
|
|
297
304
|
if (!next[symbol]) {
|
|
@@ -300,10 +307,6 @@ export function apply_element_spread(element, fn) {
|
|
|
300
307
|
}
|
|
301
308
|
|
|
302
309
|
for (const symbol of get_own_property_symbols(next)) {
|
|
303
|
-
// Ensure we are not trying to write to a proxied object
|
|
304
|
-
if (TRACKED_OBJECT in next) {
|
|
305
|
-
next = { ...next };
|
|
306
|
-
}
|
|
307
310
|
var ref_fn = next[symbol];
|
|
308
311
|
|
|
309
312
|
if (symbol.description === REF_PROP && (!prev || ref_fn !== prev[symbol])) {
|
|
@@ -316,7 +319,7 @@ export function apply_element_spread(element, fn) {
|
|
|
316
319
|
next[symbol] = ref_fn;
|
|
317
320
|
}
|
|
318
321
|
|
|
319
|
-
set_attributes(element, next);
|
|
322
|
+
set_attributes(element, prev, next);
|
|
320
323
|
|
|
321
324
|
prev = next;
|
|
322
325
|
};
|
|
@@ -145,10 +145,13 @@ export function run_teardown(block) {
|
|
|
145
145
|
*/
|
|
146
146
|
export function with_block(block, fn) {
|
|
147
147
|
var prev_block = active_block;
|
|
148
|
+
var previous_component = active_component;
|
|
148
149
|
active_block = block;
|
|
150
|
+
active_component = block.co;
|
|
149
151
|
try {
|
|
150
152
|
return fn();
|
|
151
153
|
} finally {
|
|
154
|
+
active_component = previous_component;
|
|
152
155
|
active_block = prev_block;
|
|
153
156
|
}
|
|
154
157
|
}
|
|
@@ -919,42 +922,107 @@ export function flush_sync(fn) {
|
|
|
919
922
|
|
|
920
923
|
/**
|
|
921
924
|
* @param {() => Object} fn
|
|
922
|
-
* @param {Block} block
|
|
923
925
|
* @returns {Object}
|
|
924
926
|
*/
|
|
925
|
-
export function spread_props(fn
|
|
926
|
-
|
|
927
|
-
return proxy_tracked(computed);
|
|
927
|
+
export function spread_props(fn) {
|
|
928
|
+
return proxy_props(fn);
|
|
928
929
|
}
|
|
929
930
|
|
|
930
931
|
/**
|
|
931
|
-
* @param {
|
|
932
|
+
* @param {() => Object} fn
|
|
932
933
|
* @returns {Object}
|
|
933
934
|
*/
|
|
934
|
-
export function
|
|
935
|
+
export function proxy_props(fn) {
|
|
935
936
|
return new Proxy(
|
|
936
937
|
{},
|
|
937
938
|
{
|
|
938
939
|
get(_, property) {
|
|
939
|
-
|
|
940
|
+
/** @type {Record<string | symbol, any> | Record<string | symbol, any>[]} */
|
|
941
|
+
var obj = fn();
|
|
942
|
+
|
|
943
|
+
// Handle array of objects/spreads (for multiple props)
|
|
944
|
+
if (is_array(obj)) {
|
|
945
|
+
// Search in reverse order (right-to-left) since later props override earlier ones
|
|
946
|
+
/** @type {Record<string | symbol, any>} */
|
|
947
|
+
var item;
|
|
948
|
+
for (var i = obj.length - 1; i >= 0; i--) {
|
|
949
|
+
item = obj[i];
|
|
950
|
+
if (property in item) {
|
|
951
|
+
return item[property];
|
|
952
|
+
}
|
|
953
|
+
}
|
|
954
|
+
return undefined;
|
|
955
|
+
}
|
|
956
|
+
|
|
957
|
+
// Single object case
|
|
940
958
|
return obj[property];
|
|
941
959
|
},
|
|
942
960
|
has(_, property) {
|
|
943
961
|
if (property === TRACKED_OBJECT) {
|
|
944
962
|
return true;
|
|
945
963
|
}
|
|
946
|
-
|
|
964
|
+
/** @type {Record<string | symbol, any> | Record<string | symbol, any>[]} */
|
|
965
|
+
var obj = fn();
|
|
966
|
+
|
|
967
|
+
// Handle array of objects/spreads
|
|
968
|
+
if (is_array(obj)) {
|
|
969
|
+
for (var i = obj.length - 1; i >= 0; i--) {
|
|
970
|
+
if (property in obj[i]) {
|
|
971
|
+
return true;
|
|
972
|
+
}
|
|
973
|
+
}
|
|
974
|
+
return false;
|
|
975
|
+
}
|
|
976
|
+
|
|
947
977
|
return property in obj;
|
|
948
978
|
},
|
|
949
979
|
getOwnPropertyDescriptor(_, key) {
|
|
950
|
-
|
|
980
|
+
/** @type {Record<string | symbol, any> | Record<string | symbol, any>[]} */
|
|
981
|
+
var obj = fn();
|
|
982
|
+
|
|
983
|
+
// Handle array of objects/spreads
|
|
984
|
+
if (is_array(obj)) {
|
|
985
|
+
/** @type {Record<string | symbol, any>} */
|
|
986
|
+
var item;
|
|
987
|
+
for (var i = obj.length - 1; i >= 0; i--) {
|
|
988
|
+
item = obj[i];
|
|
989
|
+
if (key in item) {
|
|
990
|
+
return get_descriptor(item, key);
|
|
991
|
+
}
|
|
992
|
+
}
|
|
993
|
+
return undefined;
|
|
994
|
+
}
|
|
951
995
|
|
|
952
996
|
if (key in obj) {
|
|
953
997
|
return get_descriptor(obj, key);
|
|
954
998
|
}
|
|
955
999
|
},
|
|
956
1000
|
ownKeys() {
|
|
957
|
-
|
|
1001
|
+
/** @type {Record<string | symbol, any> | Record<string | symbol, any>[]} */
|
|
1002
|
+
var obj = fn();
|
|
1003
|
+
/** @type {Record<string | symbol, 1>} */
|
|
1004
|
+
var done = {};
|
|
1005
|
+
/** @type {(string | symbol)[]} */
|
|
1006
|
+
var keys = [];
|
|
1007
|
+
|
|
1008
|
+
// Handle array of objects/spreads
|
|
1009
|
+
if (is_array(obj)) {
|
|
1010
|
+
// Collect all keys from all objects, order doesn't matter
|
|
1011
|
+
/** @type {Record<string | symbol, any>} */
|
|
1012
|
+
var item;
|
|
1013
|
+
for (var i = 0; i < obj.length; i++) {
|
|
1014
|
+
item = obj[i];
|
|
1015
|
+
for (const key of Reflect.ownKeys(item)) {
|
|
1016
|
+
if (done[key]) {
|
|
1017
|
+
continue;
|
|
1018
|
+
}
|
|
1019
|
+
done[key] = 1;
|
|
1020
|
+
keys.push(key);
|
|
1021
|
+
}
|
|
1022
|
+
}
|
|
1023
|
+
return keys;
|
|
1024
|
+
}
|
|
1025
|
+
|
|
958
1026
|
return Reflect.ownKeys(obj);
|
|
959
1027
|
},
|
|
960
1028
|
},
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
import { Readable } from 'stream';
|
|
1
2
|
/** @import { Component, Derived } from '#server' */
|
|
2
|
-
/** @import { render } from 'ripple/server'*/
|
|
3
|
+
/** @import { render, renderToStream, SSRComponent } from 'ripple/server'*/
|
|
3
4
|
import { DERIVED, UNINITIALIZED } from '../client/constants.js';
|
|
4
5
|
import { is_tracked_object } from '../client/utils.js';
|
|
5
6
|
import { escape } from '../../../utils/escaping.js';
|
|
@@ -34,12 +35,16 @@ class Output {
|
|
|
34
35
|
promises = [];
|
|
35
36
|
/** @type {Output | null} */
|
|
36
37
|
#parent = null;
|
|
38
|
+
/** @type {import('stream').Readable | null} */
|
|
39
|
+
#stream = null;
|
|
37
40
|
|
|
38
41
|
/**
|
|
39
42
|
* @param {Output | null} parent
|
|
43
|
+
* @param {import('stream').Readable | null} stream
|
|
40
44
|
*/
|
|
41
|
-
constructor(parent) {
|
|
45
|
+
constructor(parent, stream = null) {
|
|
42
46
|
this.#parent = parent;
|
|
47
|
+
this.#stream = stream;
|
|
43
48
|
}
|
|
44
49
|
|
|
45
50
|
/**
|
|
@@ -47,7 +52,11 @@ class Output {
|
|
|
47
52
|
* @returns {void}
|
|
48
53
|
*/
|
|
49
54
|
push(str) {
|
|
50
|
-
this
|
|
55
|
+
if (this.#stream) {
|
|
56
|
+
this.#stream.push(str);
|
|
57
|
+
} else {
|
|
58
|
+
this.body += str;
|
|
59
|
+
}
|
|
51
60
|
}
|
|
52
61
|
|
|
53
62
|
/**
|
|
@@ -61,7 +70,7 @@ class Output {
|
|
|
61
70
|
|
|
62
71
|
/** @type {render} */
|
|
63
72
|
export async function render(component) {
|
|
64
|
-
const output = new Output(null);
|
|
73
|
+
const output = new Output(null, null);
|
|
65
74
|
let head = '';
|
|
66
75
|
let body = '';
|
|
67
76
|
let css = new Set();
|
|
@@ -76,14 +85,45 @@ export async function render(component) {
|
|
|
76
85
|
await Promise.all(output.promises);
|
|
77
86
|
}
|
|
78
87
|
|
|
79
|
-
head = output.head
|
|
80
|
-
body = output.body
|
|
81
|
-
css = output.css
|
|
88
|
+
head = output.head;
|
|
89
|
+
body = output.body;
|
|
90
|
+
css = output.css;
|
|
91
|
+
} catch (error) {
|
|
92
|
+
console.log(error);
|
|
82
93
|
}
|
|
83
|
-
|
|
84
|
-
|
|
94
|
+
return { head, body, css };
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/** @type {renderToStream} */
|
|
98
|
+
export function renderToStream(component) {
|
|
99
|
+
const stream = new Readable({
|
|
100
|
+
read() {},
|
|
101
|
+
});
|
|
102
|
+
const output = new Output(null, stream);
|
|
103
|
+
render_in_chunks(component, stream, output);
|
|
104
|
+
return stream;
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
*
|
|
108
|
+
* @param {SSRComponent} component
|
|
109
|
+
* @param {Readable} stream
|
|
110
|
+
* @param {Output} output
|
|
111
|
+
*/
|
|
112
|
+
async function render_in_chunks(component, stream, output) {
|
|
113
|
+
try {
|
|
114
|
+
if (component.async) {
|
|
115
|
+
await component(output, {});
|
|
116
|
+
} else {
|
|
117
|
+
component(output, {});
|
|
118
|
+
}
|
|
119
|
+
if (output.promises.length > 0) {
|
|
120
|
+
await Promise.all(output.promises);
|
|
121
|
+
}
|
|
122
|
+
stream.push(null);
|
|
123
|
+
} catch (error) {
|
|
124
|
+
console.error(error);
|
|
125
|
+
stream.emit('error', error);
|
|
85
126
|
}
|
|
86
|
-
return { head, body, css }
|
|
87
127
|
}
|
|
88
128
|
/**
|
|
89
129
|
* @returns {void}
|
|
@@ -144,7 +184,7 @@ export function get(tracked) {
|
|
|
144
184
|
return tracked;
|
|
145
185
|
}
|
|
146
186
|
|
|
147
|
-
return (tracked.f & DERIVED) !== 0 ? get_derived(/** @type {Derived} */(tracked)) : tracked.v;
|
|
187
|
+
return (tracked.f & DERIVED) !== 0 ? get_derived(/** @type {Derived} */ (tracked)) : tracked.v;
|
|
148
188
|
}
|
|
149
189
|
|
|
150
190
|
/**
|
package/src/server/index.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export { render } from '../runtime/internal/server/index.js';
|
|
1
|
+
export { render, renderToStream } from '../runtime/internal/server/index.js';
|
|
2
2
|
export { get_css_for_hashes } from '../runtime/internal/server/css-registry.js';
|
|
3
3
|
export { executeServerFunction } from '../runtime/internal/server/rpc.js';
|