ripple 0.2.210 → 0.2.212
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/CHANGELOG.md +25 -0
- package/README.md +3 -0
- package/package.json +5 -2
- package/src/compiler/phases/1-parse/index.js +9 -1
- package/src/compiler/phases/3-transform/client/index.js +145 -4
- package/src/compiler/types/index.d.ts +0 -6
- package/src/compiler/types/rpc.d.ts +5 -0
- package/src/runtime/internal/client/hydration.js +4 -0
- package/src/runtime/internal/client/rpc.js +31 -3
- package/src/runtime/internal/client/template.js +6 -3
- package/tests/client/compiler/compiler.try-in-function.test.ripple +159 -0
- package/tests/hydration/basic.test.js +23 -0
- package/tests/hydration/compiled/client/basic.js +65 -0
- package/tests/hydration/compiled/client/composite.js +139 -0
- package/tests/hydration/compiled/client/if-children.js +406 -0
- package/tests/hydration/compiled/client/portal.js +3 -0
- package/tests/hydration/compiled/server/basic.js +106 -0
- package/tests/hydration/compiled/server/composite.js +176 -0
- package/tests/hydration/compiled/server/if-children.js +685 -0
- package/tests/hydration/components/basic.ripple +30 -0
- package/tests/hydration/components/composite.ripple +37 -0
- package/tests/hydration/components/if-children.ripple +196 -0
- package/tests/hydration/composite.test.js +42 -0
- package/tests/hydration/if-children.test.js +272 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,30 @@
|
|
|
1
1
|
# ripple
|
|
2
2
|
|
|
3
|
+
## 0.2.212
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Fix hydration error when component is last sibling - added `hydrate_advance()`
|
|
8
|
+
to safely advance hydration position at end of component content without
|
|
9
|
+
throwing when no next sibling exists
|
|
10
|
+
|
|
11
|
+
- Updated dependencies []:
|
|
12
|
+
- ripple@0.2.212
|
|
13
|
+
|
|
14
|
+
## 0.2.211
|
|
15
|
+
|
|
16
|
+
### Patch Changes
|
|
17
|
+
|
|
18
|
+
- [#694](https://github.com/Ripple-TS/ripple/pull/694)
|
|
19
|
+
[`fa285f4`](https://github.com/Ripple-TS/ripple/commit/fa285f441ab8d748c3dfea6adb463e3ca6d614b5)
|
|
20
|
+
Thanks [@trueadm](https://github.com/trueadm)! - Add a compiler validation error
|
|
21
|
+
for rendering `children` through text interpolation (for example `{children}` or
|
|
22
|
+
`{props.children}`) and direct users to render children as a component
|
|
23
|
+
(`<@children />`) instead.
|
|
24
|
+
- Updated dependencies
|
|
25
|
+
[[`fa285f4`](https://github.com/Ripple-TS/ripple/commit/fa285f441ab8d748c3dfea6adb463e3ca6d614b5)]:
|
|
26
|
+
- ripple@0.2.211
|
|
27
|
+
|
|
3
28
|
## 0.2.210
|
|
4
29
|
|
|
5
30
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -5,5 +5,8 @@
|
|
|
5
5
|
|
|
6
6
|
# What is Ripple?
|
|
7
7
|
|
|
8
|
+
[](https://www.npmjs.com/package/ripple)
|
|
9
|
+
[](https://www.npmjs.com/package/ripple)
|
|
10
|
+
|
|
8
11
|
Ripple is an elegant TypeScript UI framework. To find out more, view
|
|
9
12
|
[Ripple's Github README](https://github.com/Ripple-TS/ripple).
|
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.212",
|
|
7
7
|
"type": "module",
|
|
8
8
|
"module": "src/runtime/index-client.js",
|
|
9
9
|
"main": "src/runtime/index-client.js",
|
|
@@ -41,6 +41,9 @@
|
|
|
41
41
|
"./compiler/internal/import": {
|
|
42
42
|
"types": "./src/compiler/types/import.d.ts"
|
|
43
43
|
},
|
|
44
|
+
"./compiler/internal/rpc": {
|
|
45
|
+
"types": "./src/compiler/types/rpc.d.ts"
|
|
46
|
+
},
|
|
44
47
|
"./compiler/internal/identifier/utils": {
|
|
45
48
|
"default": "./src/compiler/identifier-utils.js"
|
|
46
49
|
},
|
|
@@ -93,6 +96,6 @@
|
|
|
93
96
|
"vscode-languageserver-types": "^3.17.5"
|
|
94
97
|
},
|
|
95
98
|
"peerDependencies": {
|
|
96
|
-
"ripple": "0.2.
|
|
99
|
+
"ripple": "0.2.212"
|
|
97
100
|
}
|
|
98
101
|
}
|
|
@@ -2496,7 +2496,15 @@ function RipplePlugin(config) {
|
|
|
2496
2496
|
}
|
|
2497
2497
|
|
|
2498
2498
|
if (this.value === '#server') {
|
|
2499
|
-
|
|
2499
|
+
// Peek ahead to see if this is a server block (#server { ... }) vs
|
|
2500
|
+
// a server identifier expression (#server.fn(), #server.fn().then())
|
|
2501
|
+
let peek_pos = this.end;
|
|
2502
|
+
while (peek_pos < this.input.length && /\s/.test(this.input[peek_pos])) peek_pos++;
|
|
2503
|
+
if (peek_pos < this.input.length && this.input.charCodeAt(peek_pos) === 123) {
|
|
2504
|
+
// Next non-whitespace character is '{' — parse as server block
|
|
2505
|
+
return this.parseServerBlock();
|
|
2506
|
+
}
|
|
2507
|
+
// Otherwise fall through to parse as expression statement (e.g., #server.fn().then(...))
|
|
2500
2508
|
}
|
|
2501
2509
|
|
|
2502
2510
|
if (this.value === 'component') {
|
|
@@ -2308,13 +2308,17 @@ const visitors = {
|
|
|
2308
2308
|
},
|
|
2309
2309
|
|
|
2310
2310
|
TryStatement(node, context) {
|
|
2311
|
-
if (context.state.to_ts) {
|
|
2312
|
-
return transform_ts_child(node, context);
|
|
2313
|
-
}
|
|
2314
|
-
|
|
2315
2311
|
if (!is_inside_component(context)) {
|
|
2312
|
+
if (context.state.to_ts) {
|
|
2313
|
+
return transform_ts_child(node, SetContextForOutsideComponent(context));
|
|
2314
|
+
}
|
|
2315
|
+
|
|
2316
2316
|
return context.next();
|
|
2317
2317
|
}
|
|
2318
|
+
|
|
2319
|
+
if (context.state.to_ts) {
|
|
2320
|
+
return transform_ts_child(node, context);
|
|
2321
|
+
}
|
|
2318
2322
|
context.state.template?.push('<!>');
|
|
2319
2323
|
|
|
2320
2324
|
const id = context.state.flush_node?.();
|
|
@@ -2999,6 +3003,72 @@ function collect_returns_from_children(children) {
|
|
|
2999
3003
|
return returns;
|
|
3000
3004
|
}
|
|
3001
3005
|
|
|
3006
|
+
/**
|
|
3007
|
+
* Check if an Element has any dynamic content that would trigger flush_node().
|
|
3008
|
+
* An Element has dynamic content if it has:
|
|
3009
|
+
* - Dynamic attributes (tracked expressions in attribute values)
|
|
3010
|
+
* - Control flow children (IfStatement, ForOfStatement, etc.)
|
|
3011
|
+
* - Dynamic text children (non-Literal Text nodes)
|
|
3012
|
+
* - Non-DOM element children (components)
|
|
3013
|
+
* - Html children
|
|
3014
|
+
* - Dynamic descendants (recursive)
|
|
3015
|
+
* @param {AST.Element} element
|
|
3016
|
+
* @returns {boolean}
|
|
3017
|
+
*/
|
|
3018
|
+
function element_has_dynamic_content(element) {
|
|
3019
|
+
// Check for dynamic attributes
|
|
3020
|
+
for (const attr of element.attributes) {
|
|
3021
|
+
if (attr.type === 'Attribute') {
|
|
3022
|
+
// Dynamic value expression (not null, not Literal)
|
|
3023
|
+
if (attr.value !== null && attr.value.type !== 'Literal') {
|
|
3024
|
+
return true;
|
|
3025
|
+
}
|
|
3026
|
+
// Tracked attribute name
|
|
3027
|
+
if (attr.name.tracked) {
|
|
3028
|
+
return true;
|
|
3029
|
+
}
|
|
3030
|
+
} else if (attr.type === 'SpreadAttribute' || attr.type === 'RefAttribute') {
|
|
3031
|
+
return true;
|
|
3032
|
+
}
|
|
3033
|
+
}
|
|
3034
|
+
|
|
3035
|
+
// Check children for dynamic content
|
|
3036
|
+
for (const child of element.children) {
|
|
3037
|
+
if (
|
|
3038
|
+
child.type === 'IfStatement' ||
|
|
3039
|
+
child.type === 'TryStatement' ||
|
|
3040
|
+
child.type === 'ForOfStatement' ||
|
|
3041
|
+
child.type === 'SwitchStatement' ||
|
|
3042
|
+
child.type === 'TsxCompat' ||
|
|
3043
|
+
child.type === 'Html'
|
|
3044
|
+
) {
|
|
3045
|
+
return true;
|
|
3046
|
+
}
|
|
3047
|
+
if (child.type === 'Text' && child.expression.type !== 'Literal') {
|
|
3048
|
+
return true;
|
|
3049
|
+
}
|
|
3050
|
+
// Non-DOM element (component)
|
|
3051
|
+
if (
|
|
3052
|
+
child.type === 'Element' &&
|
|
3053
|
+
(child.id.type !== 'Identifier' || !is_element_dom_element(child))
|
|
3054
|
+
) {
|
|
3055
|
+
return true;
|
|
3056
|
+
}
|
|
3057
|
+
// Recursively check DOM element children
|
|
3058
|
+
if (
|
|
3059
|
+
child.type === 'Element' &&
|
|
3060
|
+
child.id.type === 'Identifier' &&
|
|
3061
|
+
is_element_dom_element(child)
|
|
3062
|
+
) {
|
|
3063
|
+
if (element_has_dynamic_content(child)) {
|
|
3064
|
+
return true;
|
|
3065
|
+
}
|
|
3066
|
+
}
|
|
3067
|
+
}
|
|
3068
|
+
|
|
3069
|
+
return false;
|
|
3070
|
+
}
|
|
3071
|
+
|
|
3002
3072
|
/**
|
|
3003
3073
|
*
|
|
3004
3074
|
* @param {AST.Node[]} children
|
|
@@ -3286,6 +3356,77 @@ function transform_children(children, context) {
|
|
|
3286
3356
|
flush_node: /** @type {TransformClientState['flush_node']} */ (flush_node),
|
|
3287
3357
|
namespace: state.namespace,
|
|
3288
3358
|
});
|
|
3359
|
+
|
|
3360
|
+
// After processing an element's children via child()/sibling() navigation,
|
|
3361
|
+
// hydrate_node is left deep inside the element. If there's a next sibling,
|
|
3362
|
+
// we need to restore hydrate_node so sibling() navigation works correctly.
|
|
3363
|
+
//
|
|
3364
|
+
// We only need pop() when we actually DESCEND into the element, which happens when:
|
|
3365
|
+
// - There are Element children (including DOM elements like <button>)
|
|
3366
|
+
// - There are non-literal Text children (we navigate to set text content)
|
|
3367
|
+
// - There are control flow / Html / component children
|
|
3368
|
+
//
|
|
3369
|
+
// The Element visitor already adds pop() for non-literal text, control flow,
|
|
3370
|
+
// Html, and component (non-DOM element) children. We need to ALSO add pop()
|
|
3371
|
+
// when there are DOM element children, which the Element visitor doesn't cover.
|
|
3372
|
+
const next_node = normalized[node_idx + 1];
|
|
3373
|
+
if (next_node && is_element_dom_element(node) && node.children.length > 0) {
|
|
3374
|
+
// Check if any child is a DOM element - this causes navigation but
|
|
3375
|
+
// the Element visitor doesn't add pop() for it
|
|
3376
|
+
const has_dom_element_children = node.children.some(
|
|
3377
|
+
(child) =>
|
|
3378
|
+
child.type === 'Element' &&
|
|
3379
|
+
child.id.type === 'Identifier' &&
|
|
3380
|
+
is_element_dom_element(child),
|
|
3381
|
+
);
|
|
3382
|
+
|
|
3383
|
+
// Check if the Element visitor already added pop()
|
|
3384
|
+
const element_visitor_adds_pop = node.children.some(
|
|
3385
|
+
(child) =>
|
|
3386
|
+
child.type === 'IfStatement' ||
|
|
3387
|
+
child.type === 'TryStatement' ||
|
|
3388
|
+
child.type === 'ForOfStatement' ||
|
|
3389
|
+
child.type === 'SwitchStatement' ||
|
|
3390
|
+
child.type === 'TsxCompat' ||
|
|
3391
|
+
child.type === 'Html' ||
|
|
3392
|
+
(child.type === 'Element' &&
|
|
3393
|
+
(child.id.type !== 'Identifier' || !is_element_dom_element(child))) ||
|
|
3394
|
+
(child.type === 'Text' && child.expression.type !== 'Literal'),
|
|
3395
|
+
);
|
|
3396
|
+
|
|
3397
|
+
// Add pop() if we have DOM element children AND the Element visitor didn't already add pop()
|
|
3398
|
+
if (has_dom_element_children && !element_visitor_adds_pop) {
|
|
3399
|
+
// Only add pop() if next_node will actually generate a sibling() call.
|
|
3400
|
+
// Static Text nodes (Literals) and static Elements don't call flush_node().
|
|
3401
|
+
let needs_sibling_call = false;
|
|
3402
|
+
if (next_node.type === 'Element') {
|
|
3403
|
+
// Static DOM elements with no dynamic content don't generate sibling()
|
|
3404
|
+
if (is_element_dom_element(next_node)) {
|
|
3405
|
+
needs_sibling_call = element_has_dynamic_content(next_node);
|
|
3406
|
+
} else {
|
|
3407
|
+
// Components always generate sibling()
|
|
3408
|
+
needs_sibling_call = true;
|
|
3409
|
+
}
|
|
3410
|
+
} else if (next_node.type === 'Text') {
|
|
3411
|
+
// Only dynamic text generates sibling()
|
|
3412
|
+
needs_sibling_call = next_node.expression.type !== 'Literal';
|
|
3413
|
+
} else if (
|
|
3414
|
+
next_node.type === 'Html' ||
|
|
3415
|
+
next_node.type === 'IfStatement' ||
|
|
3416
|
+
next_node.type === 'TryStatement' ||
|
|
3417
|
+
next_node.type === 'ForOfStatement' ||
|
|
3418
|
+
next_node.type === 'SwitchStatement' ||
|
|
3419
|
+
next_node.type === 'TsxCompat'
|
|
3420
|
+
) {
|
|
3421
|
+
needs_sibling_call = true;
|
|
3422
|
+
}
|
|
3423
|
+
|
|
3424
|
+
if (needs_sibling_call) {
|
|
3425
|
+
const id = flush_node();
|
|
3426
|
+
state.init?.push(b.stmt(b.call('_$_.pop', id)));
|
|
3427
|
+
}
|
|
3428
|
+
}
|
|
3429
|
+
}
|
|
3289
3430
|
} else if (node.type === 'TsxCompat') {
|
|
3290
3431
|
skipped = 0;
|
|
3291
3432
|
|
|
@@ -8,12 +8,6 @@ import type { RippleCompileError, CompileOptions } from 'ripple/compiler';
|
|
|
8
8
|
import type { Position } from 'acorn';
|
|
9
9
|
import type { RequireAllOrNone } from '#helpers';
|
|
10
10
|
|
|
11
|
-
export type RpcModules = Map<string, [string, string]>;
|
|
12
|
-
|
|
13
|
-
declare global {
|
|
14
|
-
var rpc_modules: RpcModules | undefined;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
11
|
export type NameSpace = keyof typeof NAMESPACE_URI;
|
|
18
12
|
interface BaseNodeMetaData {
|
|
19
13
|
scoped?: boolean;
|
|
@@ -28,6 +28,10 @@ export function hydrate_next() {
|
|
|
28
28
|
return set_hydrate_node(get_next_sibling(/** @type {Node} */ (hydrate_node)));
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
+
export function hydrate_advance() {
|
|
32
|
+
hydrate_node = get_next_sibling(/** @type {Node} */ (hydrate_node));
|
|
33
|
+
}
|
|
34
|
+
|
|
31
35
|
export function next(n = 1) {
|
|
32
36
|
if (hydrating) {
|
|
33
37
|
var node = hydrate_node;
|
|
@@ -6,21 +6,49 @@ import * as devalue from 'devalue';
|
|
|
6
6
|
*/
|
|
7
7
|
export async function rpc(hash, args) {
|
|
8
8
|
const body = devalue.stringify(args);
|
|
9
|
-
|
|
9
|
+
/** @type {Response} */
|
|
10
|
+
let response;
|
|
10
11
|
|
|
11
12
|
try {
|
|
12
|
-
|
|
13
|
+
response = await fetch('/_$_ripple_rpc_$_/' + hash, {
|
|
13
14
|
method: 'POST',
|
|
14
15
|
headers: {
|
|
15
16
|
'Content-Type': 'application/json',
|
|
16
17
|
},
|
|
17
18
|
body,
|
|
18
19
|
});
|
|
19
|
-
data = await response.text();
|
|
20
20
|
} catch (err) {
|
|
21
21
|
throw new Error('An error occurred while trying to call the server function.');
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
+
if (!response.ok) {
|
|
25
|
+
let message = `Server function call failed with status ${response.status}`;
|
|
26
|
+
let error_body;
|
|
27
|
+
try {
|
|
28
|
+
error_body = await response.text();
|
|
29
|
+
} catch {
|
|
30
|
+
// ignore parse errors, use default message
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if (error_body) {
|
|
34
|
+
try {
|
|
35
|
+
const parsed = JSON.parse(error_body);
|
|
36
|
+
|
|
37
|
+
if (parsed && typeof parsed.error === 'string' && parsed.error.length > 0) {
|
|
38
|
+
message = parsed.error;
|
|
39
|
+
} else {
|
|
40
|
+
message = error_body;
|
|
41
|
+
}
|
|
42
|
+
} catch {
|
|
43
|
+
message = error_body;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
throw new Error(message);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const data = await response.text();
|
|
51
|
+
|
|
24
52
|
if (data === '') {
|
|
25
53
|
throw new Error(
|
|
26
54
|
'The server function end-point did not return a response. Are you running a Ripple server?',
|
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
HYDRATION_START,
|
|
9
9
|
HYDRATION_END,
|
|
10
10
|
} from '../../../constants.js';
|
|
11
|
-
import { hydrate_next, hydrate_node, hydrating, pop } from './hydration.js';
|
|
11
|
+
import { hydrate_advance, hydrate_next, hydrate_node, hydrating, pop } from './hydration.js';
|
|
12
12
|
import { create_text, get_first_child, get_next_sibling, is_firefox } from './operations.js';
|
|
13
13
|
import { active_block, active_namespace } from './runtime.js';
|
|
14
14
|
|
|
@@ -190,7 +190,8 @@ export function append(anchor, dom, skip_advance) {
|
|
|
190
190
|
}
|
|
191
191
|
|
|
192
192
|
// During hydration, if anchor === dom, we're hydrating a child component
|
|
193
|
-
// where the "anchor" IS the content.
|
|
193
|
+
// where the "anchor" IS the content. Preserve the cursor on the
|
|
194
|
+
// template's hydrated end node so sibling traversal in the parent is correct.
|
|
194
195
|
if (anchor === dom) {
|
|
195
196
|
pop(dom);
|
|
196
197
|
return;
|
|
@@ -216,7 +217,9 @@ export function append(anchor, dom, skip_advance) {
|
|
|
216
217
|
}
|
|
217
218
|
}
|
|
218
219
|
|
|
219
|
-
|
|
220
|
+
// Only advance if there's a next sibling. At the end of a component's
|
|
221
|
+
// content, there might not be more siblings, and that's fine.
|
|
222
|
+
hydrate_advance();
|
|
220
223
|
return;
|
|
221
224
|
}
|
|
222
225
|
anchor.before(/** @type {Node} */ (dom));
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import { compile_to_volar_mappings } from 'ripple/compiler';
|
|
2
|
+
|
|
3
|
+
function count_occurrences(string: string, sub_string: string): number {
|
|
4
|
+
let count = 0;
|
|
5
|
+
let pos = string.indexOf(sub_string);
|
|
6
|
+
|
|
7
|
+
while (pos !== -1) {
|
|
8
|
+
count++;
|
|
9
|
+
pos = string.indexOf(sub_string, pos + sub_string.length);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
return count;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// TryStatement in the Volar transform should behave like IfStatement and
|
|
16
|
+
// SwitchStatement: try blocks inside functions within a component must not
|
|
17
|
+
// be duplicated into the component body.
|
|
18
|
+
|
|
19
|
+
describe('compiler > Volar transform does not duplicate try blocks from functions', () => {
|
|
20
|
+
it('try inside an async function', () => {
|
|
21
|
+
const source = `export component App() {
|
|
22
|
+
async function doWork() {
|
|
23
|
+
try {
|
|
24
|
+
await fetch('/api');
|
|
25
|
+
} catch (e) {
|
|
26
|
+
console.error(e);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
<div onclick={doWork}>{"click"}</div>
|
|
30
|
+
}`;
|
|
31
|
+
const result = compile_to_volar_mappings(source, 'test.ripple').code;
|
|
32
|
+
|
|
33
|
+
expect(count_occurrences(result, 'try {')).toBe(1);
|
|
34
|
+
expect(count_occurrences(result, 'catch')).toBe(1);
|
|
35
|
+
expect(count_occurrences(result, 'await fetch')).toBe(1);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it('try inside an arrow function', () => {
|
|
39
|
+
const source = `export component App() {
|
|
40
|
+
const doWork = async () => {
|
|
41
|
+
try {
|
|
42
|
+
await fetch('/api');
|
|
43
|
+
} catch (e) {}
|
|
44
|
+
};
|
|
45
|
+
<div onclick={doWork}>{"click"}</div>
|
|
46
|
+
}`;
|
|
47
|
+
const result = compile_to_volar_mappings(source, 'test.ripple').code;
|
|
48
|
+
|
|
49
|
+
expect(count_occurrences(result, 'try {')).toBe(1);
|
|
50
|
+
expect(count_occurrences(result, 'await fetch')).toBe(1);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it('try-catch-finally inside a function', () => {
|
|
54
|
+
const source = `export component App() {
|
|
55
|
+
async function save() {
|
|
56
|
+
try {
|
|
57
|
+
await fetch('/save');
|
|
58
|
+
} catch (e) {
|
|
59
|
+
console.error(e);
|
|
60
|
+
} finally {
|
|
61
|
+
console.log('done');
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
<div onclick={save}>{"save"}</div>
|
|
65
|
+
}`;
|
|
66
|
+
const result = compile_to_volar_mappings(source, 'test.ripple').code;
|
|
67
|
+
|
|
68
|
+
expect(count_occurrences(result, 'try {')).toBe(1);
|
|
69
|
+
expect(count_occurrences(result, 'catch')).toBe(1);
|
|
70
|
+
expect(count_occurrences(result, 'finally')).toBe(1);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it('try at component top level is preserved', () => {
|
|
74
|
+
const source = `export component App() {
|
|
75
|
+
try {
|
|
76
|
+
await fetch('/api');
|
|
77
|
+
} catch (e) {}
|
|
78
|
+
<div>{"hi"}</div>
|
|
79
|
+
}`;
|
|
80
|
+
const result = compile_to_volar_mappings(source, 'test.ripple').code;
|
|
81
|
+
|
|
82
|
+
expect(count_occurrences(result, 'try {')).toBe(1);
|
|
83
|
+
expect(count_occurrences(result, 'await fetch')).toBe(1);
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it('component-level try and function-level try coexist without duplication', () => {
|
|
87
|
+
const source = `export component App() {
|
|
88
|
+
try {
|
|
89
|
+
await fetch('/init');
|
|
90
|
+
} catch (e) {}
|
|
91
|
+
|
|
92
|
+
async function refresh() {
|
|
93
|
+
try {
|
|
94
|
+
await fetch('/refresh');
|
|
95
|
+
} catch (e) {}
|
|
96
|
+
}
|
|
97
|
+
<div onclick={refresh}>{"click"}</div>
|
|
98
|
+
}`;
|
|
99
|
+
const result = compile_to_volar_mappings(source, 'test.ripple').code;
|
|
100
|
+
|
|
101
|
+
expect(count_occurrences(result, 'try {')).toBe(2);
|
|
102
|
+
expect(count_occurrences(result, 'await fetch(\'/init\')')).toBe(1);
|
|
103
|
+
expect(count_occurrences(result, 'await fetch(\'/refresh\')')).toBe(1);
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it('try in nested functions', () => {
|
|
107
|
+
const source = `export component App() {
|
|
108
|
+
function outer() {
|
|
109
|
+
async function inner() {
|
|
110
|
+
try {
|
|
111
|
+
await fetch('/api');
|
|
112
|
+
} catch (e) {}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
<div>{"hi"}</div>
|
|
116
|
+
}`;
|
|
117
|
+
const result = compile_to_volar_mappings(source, 'test.ripple').code;
|
|
118
|
+
|
|
119
|
+
expect(count_occurrences(result, 'try {')).toBe(1);
|
|
120
|
+
expect(count_occurrences(result, 'await fetch')).toBe(1);
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it('multiple functions with try blocks each appear once', () => {
|
|
124
|
+
const source = `export component App() {
|
|
125
|
+
async function load() {
|
|
126
|
+
try {
|
|
127
|
+
await fetch('/load');
|
|
128
|
+
} catch (e) {}
|
|
129
|
+
}
|
|
130
|
+
async function save() {
|
|
131
|
+
try {
|
|
132
|
+
await fetch('/save');
|
|
133
|
+
} catch (e) {}
|
|
134
|
+
}
|
|
135
|
+
<div>{"hi"}</div>
|
|
136
|
+
}`;
|
|
137
|
+
const result = compile_to_volar_mappings(source, 'test.ripple').code;
|
|
138
|
+
|
|
139
|
+
expect(count_occurrences(result, 'try {')).toBe(2);
|
|
140
|
+
expect(count_occurrences(result, 'await fetch')).toBe(2);
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
it('try in object method', () => {
|
|
144
|
+
const source = `export component App() {
|
|
145
|
+
const handlers = {
|
|
146
|
+
async onClick() {
|
|
147
|
+
try {
|
|
148
|
+
await fetch('/api');
|
|
149
|
+
} catch (e) {}
|
|
150
|
+
}
|
|
151
|
+
};
|
|
152
|
+
<div>{"hi"}</div>
|
|
153
|
+
}`;
|
|
154
|
+
const result = compile_to_volar_mappings(source, 'test.ripple').code;
|
|
155
|
+
|
|
156
|
+
expect(count_occurrences(result, 'try {')).toBe(1);
|
|
157
|
+
expect(count_occurrences(result, 'await fetch')).toBe(1);
|
|
158
|
+
});
|
|
159
|
+
});
|
|
@@ -81,4 +81,27 @@ describe('hydration > basic', () => {
|
|
|
81
81
|
expect(container.querySelector('.playground-link')?.textContent).toBe('Playground');
|
|
82
82
|
expect(container.querySelector('.content')).toBeTruthy();
|
|
83
83
|
});
|
|
84
|
+
|
|
85
|
+
// Test for hydrate_advance() in append() - component as last sibling with no following siblings
|
|
86
|
+
it('hydrates component as last sibling (no following siblings)', async () => {
|
|
87
|
+
await hydrateComponent(
|
|
88
|
+
ServerComponents.ComponentAsLastSibling,
|
|
89
|
+
ClientComponents.ComponentAsLastSibling,
|
|
90
|
+
);
|
|
91
|
+
expect(container.querySelector('.wrapper')).toBeTruthy();
|
|
92
|
+
expect(container.querySelector('h1')?.textContent).toBe('Header');
|
|
93
|
+
expect(container.querySelector('p')?.textContent).toBe('Some content');
|
|
94
|
+
expect(container.querySelector('.last-child')?.textContent).toBe('I am the last child');
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it('hydrates nested component with inner component as last sibling', async () => {
|
|
98
|
+
await hydrateComponent(
|
|
99
|
+
ServerComponents.NestedComponentAsLastSibling,
|
|
100
|
+
ClientComponents.NestedComponentAsLastSibling,
|
|
101
|
+
);
|
|
102
|
+
expect(container.querySelector('.outer')).toBeTruthy();
|
|
103
|
+
expect(container.querySelector('h2')?.textContent).toBe('Section title');
|
|
104
|
+
expect(container.querySelector('.inner span')?.textContent).toBe('Inner text');
|
|
105
|
+
expect(container.querySelector('.inner .last-child')?.textContent).toBe('I am the last child');
|
|
106
|
+
});
|
|
84
107
|
});
|
|
@@ -21,6 +21,10 @@ var root_17 = _$_.template(`<main><div class="container"><!></div></main>`, 0);
|
|
|
21
21
|
var root_18 = _$_.template(`<div class="content"><p>Some content here</p></div>`, 0);
|
|
22
22
|
var root_20 = _$_.template(`<!><!><!><!>`, 1);
|
|
23
23
|
var root_19 = _$_.template(`<!>`, 1);
|
|
24
|
+
var root_21 = _$_.template(`<footer class="last-child">I am the last child</footer>`, 0);
|
|
25
|
+
var root_22 = _$_.template(`<div class="wrapper"><h1>Header</h1><p>Some content</p><!></div>`, 0);
|
|
26
|
+
var root_23 = _$_.template(`<div class="inner"><span>Inner text</span><!></div>`, 0);
|
|
27
|
+
var root_24 = _$_.template(`<section class="outer"><h2>Section title</h2><!></section>`, 0);
|
|
24
28
|
|
|
25
29
|
export function StaticText(__anchor, _, __block) {
|
|
26
30
|
_$_.push_component();
|
|
@@ -328,4 +332,65 @@ export function WebsiteIndex(__anchor, _, __block) {
|
|
|
328
332
|
|
|
329
333
|
_$_.append(__anchor, fragment_8);
|
|
330
334
|
_$_.pop_component();
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
function LastChild(__anchor, _, __block) {
|
|
338
|
+
_$_.push_component();
|
|
339
|
+
|
|
340
|
+
var footer_1 = root_21();
|
|
341
|
+
|
|
342
|
+
_$_.append(__anchor, footer_1);
|
|
343
|
+
_$_.pop_component();
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
export function ComponentAsLastSibling(__anchor, _, __block) {
|
|
347
|
+
_$_.push_component();
|
|
348
|
+
|
|
349
|
+
var div_11 = root_22();
|
|
350
|
+
|
|
351
|
+
{
|
|
352
|
+
var h1_1 = _$_.child(div_11);
|
|
353
|
+
var p_1 = _$_.sibling(h1_1);
|
|
354
|
+
var node_12 = _$_.sibling(p_1);
|
|
355
|
+
|
|
356
|
+
LastChild(node_12, {}, _$_.active_block);
|
|
357
|
+
_$_.pop(div_11);
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
_$_.append(__anchor, div_11);
|
|
361
|
+
_$_.pop_component();
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
function InnerContent(__anchor, _, __block) {
|
|
365
|
+
_$_.push_component();
|
|
366
|
+
|
|
367
|
+
var div_12 = root_23();
|
|
368
|
+
|
|
369
|
+
{
|
|
370
|
+
var span_5 = _$_.child(div_12);
|
|
371
|
+
var node_13 = _$_.sibling(span_5);
|
|
372
|
+
|
|
373
|
+
LastChild(node_13, {}, _$_.active_block);
|
|
374
|
+
_$_.pop(div_12);
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
_$_.append(__anchor, div_12);
|
|
378
|
+
_$_.pop_component();
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
export function NestedComponentAsLastSibling(__anchor, _, __block) {
|
|
382
|
+
_$_.push_component();
|
|
383
|
+
|
|
384
|
+
var section_1 = root_24();
|
|
385
|
+
|
|
386
|
+
{
|
|
387
|
+
var h2_1 = _$_.child(section_1);
|
|
388
|
+
var node_14 = _$_.sibling(h2_1);
|
|
389
|
+
|
|
390
|
+
InnerContent(node_14, {}, _$_.active_block);
|
|
391
|
+
_$_.pop(section_1);
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
_$_.append(__anchor, section_1);
|
|
395
|
+
_$_.pop_component();
|
|
331
396
|
}
|