ripple 0.2.212 → 0.2.213
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 +7 -0
- package/package.json +2 -2
- package/src/compiler/phases/1-parse/index.js +50 -2
- package/src/compiler/phases/2-analyze/index.js +13 -29
- package/src/compiler/phases/3-transform/client/index.js +0 -1
- package/src/compiler/types/index.d.ts +3 -1
- package/tests/client/compiler/compiler.basic.test.ripple +24 -0
- package/tests/hydration/build-components.js +2 -2
- package/tests/hydration/compiled/client/basic.js +2 -1
- package/tests/hydration/compiled/client/composite.js +1 -0
- package/tests/hydration/compiled/client/events.js +1 -0
- package/tests/hydration/compiled/client/for.js +1 -0
- package/tests/hydration/compiled/client/head.js +1 -0
- package/tests/hydration/compiled/client/html.js +1 -0
- package/tests/hydration/compiled/client/if-children.js +1 -0
- package/tests/hydration/compiled/client/if.js +1 -0
- package/tests/hydration/compiled/client/portal.js +1 -0
- package/tests/hydration/compiled/client/reactivity.js +1 -0
- package/tests/hydration/compiled/client/return.js +1 -0
- package/tests/hydration/compiled/client/switch.js +1 -0
- package/tests/hydration/compiled/server/basic.js +2 -1
- package/tests/hydration/compiled/server/composite.js +1 -0
- package/tests/hydration/compiled/server/events.js +1 -0
- package/tests/hydration/compiled/server/for.js +1 -0
- package/tests/hydration/compiled/server/head.js +1 -0
- package/tests/hydration/compiled/server/html.js +1 -0
- package/tests/hydration/compiled/server/if-children.js +1 -0
- package/tests/hydration/compiled/server/if.js +1 -0
- package/tests/hydration/compiled/server/portal.js +1 -0
- package/tests/hydration/compiled/server/reactivity.js +1 -0
- package/tests/hydration/compiled/server/return.js +1 -0
- package/tests/hydration/compiled/server/switch.js +1 -0
- package/tests/hydration/components/basic.ripple +2 -1
- package/tests/hydration/components/composite.ripple +3 -1
- package/tests/hydration/tsconfig.json +12 -0
- package/tests/hydration.d.ts +14 -0
package/CHANGELOG.md
CHANGED
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.213",
|
|
7
7
|
"type": "module",
|
|
8
8
|
"module": "src/runtime/index-client.js",
|
|
9
9
|
"main": "src/runtime/index-client.js",
|
|
@@ -96,6 +96,6 @@
|
|
|
96
96
|
"vscode-languageserver-types": "^3.17.5"
|
|
97
97
|
},
|
|
98
98
|
"peerDependencies": {
|
|
99
|
-
"ripple": "0.2.
|
|
99
|
+
"ripple": "0.2.213"
|
|
100
100
|
}
|
|
101
101
|
}
|
|
@@ -2667,7 +2667,7 @@ function get_comment_handlers(source, comments, index = 0) {
|
|
|
2667
2667
|
},
|
|
2668
2668
|
|
|
2669
2669
|
/**
|
|
2670
|
-
* @param {AST.Node} ast
|
|
2670
|
+
* @param {AST.Node | AST.CSS.StyleSheet} ast
|
|
2671
2671
|
*/
|
|
2672
2672
|
add_comments: (ast) => {
|
|
2673
2673
|
if (comments.length === 0) return;
|
|
@@ -2685,7 +2685,7 @@ function get_comment_handlers(source, comments, index = 0) {
|
|
|
2685
2685
|
|
|
2686
2686
|
walk(ast, null, {
|
|
2687
2687
|
_(node, { next, path }) {
|
|
2688
|
-
const metadata = node?.metadata;
|
|
2688
|
+
const metadata = /** @type {AST.Node} */ (node)?.metadata;
|
|
2689
2689
|
|
|
2690
2690
|
/**
|
|
2691
2691
|
* Check if a comment is inside an attribute expression
|
|
@@ -2758,6 +2758,36 @@ function get_comment_handlers(source, comments, index = 0) {
|
|
|
2758
2758
|
return element;
|
|
2759
2759
|
}
|
|
2760
2760
|
|
|
2761
|
+
// Skip CSS nodes entirely - they use CSS-local positions (relative to
|
|
2762
|
+
// the <style> tag content) which would incorrectly match against
|
|
2763
|
+
// absolute source positions of JS/HTML comments. Also consume any
|
|
2764
|
+
// CSS comments (which have absolute positions) that fall within the
|
|
2765
|
+
// parent <style> element's content range so they don't leak to
|
|
2766
|
+
// subsequent JS nodes.
|
|
2767
|
+
if (node.type === 'StyleSheet') {
|
|
2768
|
+
const styleElement = /** @type {AST.Element & AST.NodeWithLocation | undefined} */ (
|
|
2769
|
+
path.findLast(
|
|
2770
|
+
(ancestor) =>
|
|
2771
|
+
ancestor &&
|
|
2772
|
+
ancestor.type === 'Element' &&
|
|
2773
|
+
ancestor.id &&
|
|
2774
|
+
/** @type {AST.Identifier} */ (ancestor.id).name === 'style',
|
|
2775
|
+
)
|
|
2776
|
+
);
|
|
2777
|
+
if (styleElement) {
|
|
2778
|
+
const cssStart =
|
|
2779
|
+
/** @type {AST.NodeWithLocation} */ (styleElement.openingElement)?.end ??
|
|
2780
|
+
styleElement.start;
|
|
2781
|
+
const cssEnd =
|
|
2782
|
+
/** @type {AST.NodeWithLocation} */ (styleElement.closingElement)?.start ??
|
|
2783
|
+
styleElement.end;
|
|
2784
|
+
while (comments[0] && comments[0].start >= cssStart && comments[0].end <= cssEnd) {
|
|
2785
|
+
comments.shift();
|
|
2786
|
+
}
|
|
2787
|
+
}
|
|
2788
|
+
return;
|
|
2789
|
+
}
|
|
2790
|
+
|
|
2761
2791
|
if (metadata && metadata.commentContainerId !== undefined) {
|
|
2762
2792
|
// For empty template elements, keep comments as `innerComments`.
|
|
2763
2793
|
// The Prettier plugin uses `innerComments` to preserve them and
|
|
@@ -2771,6 +2801,24 @@ function get_comment_handlers(source, comments, index = 0) {
|
|
|
2771
2801
|
comments[0].context.containerId === metadata.commentContainerId &&
|
|
2772
2802
|
comments[0].context.beforeMeaningfulChild
|
|
2773
2803
|
) {
|
|
2804
|
+
// Check that the comment is actually in this element's own content
|
|
2805
|
+
// area, not positionally inside a child element. This handles the
|
|
2806
|
+
// case where jsx_parseOpeningElementAt() triggers jsx_readToken()
|
|
2807
|
+
// before the child element is pushed to the parser's #path, causing
|
|
2808
|
+
// comments inside the child to get the parent's containerId.
|
|
2809
|
+
const commentStart = comments[0].start;
|
|
2810
|
+
const isInsideChildElement = /** @type {AST.NodeWithChildren} */ (
|
|
2811
|
+
node
|
|
2812
|
+
).children?.some(
|
|
2813
|
+
(child) =>
|
|
2814
|
+
child &&
|
|
2815
|
+
child.start !== undefined &&
|
|
2816
|
+
child.end !== undefined &&
|
|
2817
|
+
commentStart >= child.start &&
|
|
2818
|
+
commentStart < child.end,
|
|
2819
|
+
);
|
|
2820
|
+
if (isInsideChildElement) break;
|
|
2821
|
+
|
|
2774
2822
|
const elementComment = /** @type {AST.CommentWithLocation} */ (comments.shift());
|
|
2775
2823
|
|
|
2776
2824
|
(metadata.elementLeadingComments ||= []).push(elementComment);
|
|
@@ -285,35 +285,19 @@ const visitors = {
|
|
|
285
285
|
}
|
|
286
286
|
}
|
|
287
287
|
|
|
288
|
-
if (
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
node.tracked &&
|
|
302
|
-
binding?.node !== node
|
|
303
|
-
) {
|
|
304
|
-
mark_as_tracked(context.path);
|
|
305
|
-
if (context.state.metadata?.tracking === false) {
|
|
306
|
-
context.state.metadata.tracking = true;
|
|
307
|
-
}
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
if (
|
|
311
|
-
is_reference(node, /** @type {AST.Node} */ (parent)) &&
|
|
312
|
-
node.tracked &&
|
|
313
|
-
binding?.node !== node
|
|
314
|
-
) {
|
|
315
|
-
if (context.state.metadata?.tracking === false) {
|
|
316
|
-
context.state.metadata.tracking = true;
|
|
288
|
+
if (node.tracked && binding) {
|
|
289
|
+
if (
|
|
290
|
+
binding.kind === 'prop' ||
|
|
291
|
+
binding.kind === 'prop_fallback' ||
|
|
292
|
+
binding.kind === 'for_pattern' ||
|
|
293
|
+
(is_reference(node, /** @type {AST.Node} */ (parent)) &&
|
|
294
|
+
node.tracked &&
|
|
295
|
+
binding.node !== node)
|
|
296
|
+
) {
|
|
297
|
+
mark_as_tracked(context.path);
|
|
298
|
+
if (context.state.metadata?.tracking === false) {
|
|
299
|
+
context.state.metadata.tracking = true;
|
|
300
|
+
}
|
|
317
301
|
}
|
|
318
302
|
}
|
|
319
303
|
|
|
@@ -116,7 +116,6 @@ function visit_function(node, context) {
|
|
|
116
116
|
if (
|
|
117
117
|
metadata?.tracked === true &&
|
|
118
118
|
!is_inside_component(context, true) &&
|
|
119
|
-
is_component_level_function(context) &&
|
|
120
119
|
body.type === 'BlockStatement'
|
|
121
120
|
) {
|
|
122
121
|
body = { ...body, body: [b.var('__block', b.call('_$_.scope')), ...body.body] };
|
|
@@ -394,7 +394,9 @@ declare module 'estree' {
|
|
|
394
394
|
value: AST.Property['value'] | Component;
|
|
395
395
|
}
|
|
396
396
|
|
|
397
|
-
export type RippleAttribute = Attribute | SpreadAttribute | RefAttribute;
|
|
397
|
+
export type RippleAttribute = AST.Attribute | AST.SpreadAttribute | AST.RefAttribute;
|
|
398
|
+
|
|
399
|
+
export type NodeWithChildren = AST.Element | AST.TsxCompat;
|
|
398
400
|
|
|
399
401
|
export namespace CSS {
|
|
400
402
|
export interface BaseNode {
|
|
@@ -357,4 +357,28 @@ export component App() {
|
|
|
357
357
|
`;
|
|
358
358
|
expect(() => compile(code, 'test.ripple')).not.toThrow();
|
|
359
359
|
});
|
|
360
|
+
|
|
361
|
+
it('should inject __block for track() calls inside class constructors', () => {
|
|
362
|
+
const source = `
|
|
363
|
+
import { track } from 'ripple';
|
|
364
|
+
|
|
365
|
+
class Store {
|
|
366
|
+
constructor() {
|
|
367
|
+
this.count = track(0);
|
|
368
|
+
this.items = #[1, 2, 3];
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
export component App() {
|
|
373
|
+
const store = new Store();
|
|
374
|
+
<div>{store.count}</div>
|
|
375
|
+
}
|
|
376
|
+
`;
|
|
377
|
+
const result = compile(source, 'test.ripple', { mode: 'client' });
|
|
378
|
+
const code = result.js.code;
|
|
379
|
+
|
|
380
|
+
// The constructor's compiled output should contain __block = _$_.scope()
|
|
381
|
+
expect(code).toContain('__block');
|
|
382
|
+
expect(code).toContain('_$_.scope()');
|
|
383
|
+
});
|
|
360
384
|
});
|
|
@@ -57,7 +57,7 @@ function buildComponents() {
|
|
|
57
57
|
const clientResult = compile(source, file, {
|
|
58
58
|
mode: 'client',
|
|
59
59
|
});
|
|
60
|
-
writeFileSync(join(clientOutDir, outputName), clientResult.js.code);
|
|
60
|
+
writeFileSync(join(clientOutDir, outputName), '// @ts-nocheck\n' + clientResult.js.code);
|
|
61
61
|
|
|
62
62
|
// Compile for server
|
|
63
63
|
const serverResult = compile(source, file, {
|
|
@@ -65,7 +65,7 @@ function buildComponents() {
|
|
|
65
65
|
});
|
|
66
66
|
// Transform imports to use server runtime
|
|
67
67
|
const serverCode = transformServerImports(serverResult.js.code);
|
|
68
|
-
writeFileSync(join(serverOutDir, outputName), serverCode);
|
|
68
|
+
writeFileSync(join(serverOutDir, outputName), '// @ts-nocheck\n' + serverCode);
|
|
69
69
|
|
|
70
70
|
console.log(`Compiled ${file} -> client & server`);
|
|
71
71
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
// @ts-nocheck
|
|
1
2
|
import * as _$_ from 'ripple/internal/client';
|
|
2
3
|
|
|
3
4
|
var root = _$_.template(`<div>Hello World</div>`, 0);
|
|
@@ -255,7 +256,7 @@ function Actions(__anchor, __props, __block) {
|
|
|
255
256
|
};
|
|
256
257
|
|
|
257
258
|
_$_.if(node_5, (__render) => {
|
|
258
|
-
if (_$_.
|
|
259
|
+
if (_$_.fallback(__props.playgroundVisible, false)) __render(consequent);
|
|
259
260
|
});
|
|
260
261
|
}
|
|
261
262
|
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
// @ts-nocheck
|
|
1
2
|
import * as _$_ from 'ripple/internal/server';
|
|
2
3
|
|
|
3
4
|
export function StaticText(__output) {
|
|
@@ -345,7 +346,7 @@ function Actions(__output, { playgroundVisible = false }) {
|
|
|
345
346
|
__output.push('</a>');
|
|
346
347
|
__output.push('<!--[-->');
|
|
347
348
|
|
|
348
|
-
if (
|
|
349
|
+
if (playgroundVisible) {
|
|
349
350
|
__output.push('<a');
|
|
350
351
|
__output.push(' href="/playground"');
|
|
351
352
|
__output.push(' class="playground-link"');
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
// Basic static components for hydration testing
|
|
2
|
+
import type { Component } from 'ripple';
|
|
2
3
|
|
|
3
4
|
export component StaticText() {
|
|
4
5
|
<div>{'Hello World'}</div>
|
|
@@ -96,7 +97,7 @@ component Actions({ playgroundVisible = false }: { playgroundVisible: boolean })
|
|
|
96
97
|
</div>
|
|
97
98
|
}
|
|
98
99
|
|
|
99
|
-
component Layout({ children }) {
|
|
100
|
+
component Layout({ children }: { children: Component }) {
|
|
100
101
|
<main>
|
|
101
102
|
<div class="container">
|
|
102
103
|
<children />
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export {};
|
|
2
|
+
|
|
3
|
+
interface CustomMatchers<R = unknown> {
|
|
4
|
+
/**
|
|
5
|
+
* Compares HTML strings after stripping hydration markers.
|
|
6
|
+
* Hydration markers are: <!--[--> <!--[!--> <!--]-->
|
|
7
|
+
*/
|
|
8
|
+
toBeHtml(expected: string): R;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
declare module 'vitest' {
|
|
12
|
+
interface Assertion<T = any> extends CustomMatchers<T> {}
|
|
13
|
+
interface AsymmetricMatchersContaining extends CustomMatchers {}
|
|
14
|
+
}
|