ripple 0.3.7 → 0.3.8
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 +37 -194
- package/src/compiler/phases/2-analyze/index.js +63 -18
- package/src/compiler/phases/3-transform/client/index.js +19 -3
- package/src/compiler/phases/3-transform/server/index.js +16 -24
- package/src/compiler/types/parse.d.ts +0 -8
- package/src/runtime/internal/client/composite.js +2 -2
- package/tests/client/array/array.copy-within.test.ripple +12 -12
- package/tests/client/array/array.derived.test.ripple +46 -46
- package/tests/client/array/array.iteration.test.ripple +10 -10
- package/tests/client/array/array.mutations.test.ripple +20 -20
- package/tests/client/array/array.to-methods.test.ripple +6 -6
- package/tests/client/async-suspend.test.ripple +5 -5
- package/tests/client/basic/basic.attributes.test.ripple +81 -81
- package/tests/client/basic/basic.collections.test.ripple +9 -9
- package/tests/client/basic/basic.components.test.ripple +28 -28
- package/tests/client/basic/basic.errors.test.ripple +18 -18
- package/tests/client/basic/basic.events.test.ripple +37 -37
- package/tests/client/basic/basic.get-set.test.ripple +6 -6
- package/tests/client/basic/basic.reactivity.test.ripple +68 -68
- package/tests/client/basic/basic.rendering.test.ripple +19 -19
- package/tests/client/basic/basic.utilities.test.ripple +3 -3
- package/tests/client/boundaries.test.ripple +12 -12
- package/tests/client/compiler/__snapshots__/compiler.assignments.test.ripple.snap +5 -5
- package/tests/client/compiler/compiler.assignments.test.ripple +19 -19
- package/tests/client/compiler/compiler.basic.test.ripple +16 -16
- package/tests/client/compiler/compiler.tracked-access.test.ripple +2 -2
- package/tests/client/composite/composite.dynamic-components.test.ripple +9 -9
- package/tests/client/composite/composite.props.test.ripple +11 -11
- package/tests/client/composite/composite.reactivity.test.ripple +43 -43
- package/tests/client/composite/composite.render.test.ripple +3 -3
- package/tests/client/computed-properties.test.ripple +4 -4
- package/tests/client/date.test.ripple +42 -42
- package/tests/client/dynamic-elements.test.ripple +42 -42
- package/tests/client/events.test.ripple +70 -70
- package/tests/client/for.test.ripple +25 -25
- package/tests/client/head.test.ripple +19 -19
- package/tests/client/html.test.ripple +3 -3
- package/tests/client/input-value.test.ripple +84 -84
- package/tests/client/lazy-destructuring.test.ripple +71 -16
- package/tests/client/map.test.ripple +16 -16
- package/tests/client/media-query.test.ripple +7 -7
- package/tests/client/portal.test.ripple +11 -11
- package/tests/client/ref.test.ripple +4 -4
- package/tests/client/return.test.ripple +52 -52
- package/tests/client/set.test.ripple +6 -6
- package/tests/client/svg.test.ripple +5 -5
- package/tests/client/switch.test.ripple +44 -44
- package/tests/client/try.test.ripple +5 -5
- package/tests/client/url/url.derived.test.ripple +6 -6
- package/tests/client/url-search-params/url-search-params.derived.test.ripple +8 -8
- package/tests/client/url-search-params/url-search-params.iteration.test.ripple +10 -10
- package/tests/client/url-search-params/url-search-params.mutation.test.ripple +10 -10
- package/tests/client/url-search-params/url-search-params.retrieval.test.ripple +18 -18
- package/tests/client/url-search-params/url-search-params.serialization.test.ripple +2 -2
- package/tests/hydration/compiled/client/events.js +25 -25
- package/tests/hydration/compiled/client/for.js +70 -66
- package/tests/hydration/compiled/client/head.js +25 -25
- package/tests/hydration/compiled/client/hmr.js +2 -2
- package/tests/hydration/compiled/client/html.js +3 -3
- package/tests/hydration/compiled/client/if-children.js +24 -24
- package/tests/hydration/compiled/client/if.js +18 -18
- package/tests/hydration/compiled/client/mixed-control-flow.js +9 -9
- package/tests/hydration/compiled/client/portal.js +3 -3
- package/tests/hydration/compiled/client/reactivity.js +16 -16
- package/tests/hydration/compiled/client/return.js +40 -40
- package/tests/hydration/compiled/client/switch.js +12 -12
- package/tests/hydration/compiled/server/events.js +19 -19
- package/tests/hydration/compiled/server/for.js +41 -41
- package/tests/hydration/compiled/server/head.js +26 -26
- package/tests/hydration/compiled/server/hmr.js +2 -2
- package/tests/hydration/compiled/server/html.js +2 -2
- package/tests/hydration/compiled/server/if-children.js +16 -16
- package/tests/hydration/compiled/server/if.js +11 -11
- package/tests/hydration/compiled/server/mixed-control-flow.js +6 -6
- package/tests/hydration/compiled/server/portal.js +2 -2
- package/tests/hydration/compiled/server/reactivity.js +16 -16
- package/tests/hydration/compiled/server/return.js +25 -25
- package/tests/hydration/compiled/server/switch.js +8 -8
- package/tests/hydration/components/events.ripple +25 -25
- package/tests/hydration/components/for.ripple +66 -66
- package/tests/hydration/components/head.ripple +16 -16
- package/tests/hydration/components/hmr.ripple +2 -2
- package/tests/hydration/components/html.ripple +3 -3
- package/tests/hydration/components/if-children.ripple +24 -24
- package/tests/hydration/components/if.ripple +18 -18
- package/tests/hydration/components/mixed-control-flow.ripple +9 -9
- package/tests/hydration/components/portal.ripple +3 -3
- package/tests/hydration/components/reactivity.ripple +16 -16
- package/tests/hydration/components/return.ripple +40 -40
- package/tests/hydration/components/switch.ripple +20 -20
- package/tests/server/await.test.ripple +3 -3
- package/tests/server/basic.attributes.test.ripple +34 -34
- package/tests/server/basic.components.test.ripple +10 -10
- package/tests/server/basic.test.ripple +38 -40
- package/tests/server/composite.props.test.ripple +9 -9
- package/tests/server/dynamic-elements.test.ripple +13 -12
- package/tests/server/head.test.ripple +11 -11
- package/tests/server/lazy-destructuring.test.ripple +27 -4
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.3.
|
|
6
|
+
"version": "0.3.8",
|
|
7
7
|
"type": "module",
|
|
8
8
|
"module": "src/runtime/index-client.js",
|
|
9
9
|
"main": "src/runtime/index-client.js",
|
|
@@ -105,6 +105,6 @@
|
|
|
105
105
|
"vscode-languageserver-types": "^3.17.5"
|
|
106
106
|
},
|
|
107
107
|
"peerDependencies": {
|
|
108
|
-
"ripple": "0.3.
|
|
108
|
+
"ripple": "0.3.8"
|
|
109
109
|
}
|
|
110
110
|
}
|
|
@@ -52,16 +52,6 @@ function DestructuringErrors() {
|
|
|
52
52
|
return this;
|
|
53
53
|
}
|
|
54
54
|
|
|
55
|
-
/**
|
|
56
|
-
* @param {AST.Identifier | ESTreeJSX.JSXIdentifier} node
|
|
57
|
-
* @param {string} name
|
|
58
|
-
*/
|
|
59
|
-
function set_tracked_name(node, name) {
|
|
60
|
-
node.name = name.slice(1);
|
|
61
|
-
node.metadata ??= { path: [] };
|
|
62
|
-
node.metadata.source_name = name;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
55
|
/**
|
|
66
56
|
* Convert JSX node types to regular JavaScript node types
|
|
67
57
|
* @param {ESTreeJSX.JSXIdentifier | ESTreeJSX.JSXMemberExpression | AST.Node} node - The JSX node to convert
|
|
@@ -678,7 +668,7 @@ function RipplePlugin(config) {
|
|
|
678
668
|
}
|
|
679
669
|
if (code === 64) {
|
|
680
670
|
// @ character
|
|
681
|
-
// Look ahead to see if this is followed by
|
|
671
|
+
// Look ahead to see if this is followed by an opening paren
|
|
682
672
|
if (this.pos + 1 < this.input.length) {
|
|
683
673
|
const nextChar = this.input.charCodeAt(this.pos + 1);
|
|
684
674
|
|
|
@@ -688,114 +678,11 @@ function RipplePlugin(config) {
|
|
|
688
678
|
this.pos += 2; // skip '@('
|
|
689
679
|
return this.finishToken(tt.parenL, '@(');
|
|
690
680
|
}
|
|
691
|
-
|
|
692
|
-
// Check if the next character can start an identifier
|
|
693
|
-
if (
|
|
694
|
-
(nextChar >= 65 && nextChar <= 90) || // A-Z
|
|
695
|
-
(nextChar >= 97 && nextChar <= 122) || // a-z
|
|
696
|
-
nextChar === 95 ||
|
|
697
|
-
nextChar === 36
|
|
698
|
-
) {
|
|
699
|
-
// _ or $
|
|
700
|
-
|
|
701
|
-
// Check if we're in an expression context
|
|
702
|
-
// In JSX expressions, inside parentheses, assignments, etc.
|
|
703
|
-
// we want to treat @ as an identifier prefix rather than decorator
|
|
704
|
-
const currentType = this.type;
|
|
705
|
-
/**
|
|
706
|
-
* @param {Parse.TokenType} type
|
|
707
|
-
* @param {Parse.Parser} parser
|
|
708
|
-
* @param {Parse.TokTypes} tt
|
|
709
|
-
* @returns {boolean}
|
|
710
|
-
*/
|
|
711
|
-
function inExpression(type, parser, tt) {
|
|
712
|
-
return (
|
|
713
|
-
parser.exprAllowed ||
|
|
714
|
-
type === tt.braceL || // Inside { }
|
|
715
|
-
type === tt.parenL || // Inside ( )
|
|
716
|
-
type === tt.eq || // After =
|
|
717
|
-
type === tt.comma || // After ,
|
|
718
|
-
type === tt.colon || // After :
|
|
719
|
-
type === tt.question || // After ?
|
|
720
|
-
type === tt.logicalOR || // After ||
|
|
721
|
-
type === tt.logicalAND || // After &&
|
|
722
|
-
type === tt.dot || // After . (for member expressions like obj.@prop)
|
|
723
|
-
type === tt.questionDot // After ?. (for optional chaining like obj?.@prop)
|
|
724
|
-
);
|
|
725
|
-
}
|
|
726
|
-
|
|
727
|
-
/**
|
|
728
|
-
* @param {Parse.Parser} parser
|
|
729
|
-
* @param {Parse.TokTypes} tt
|
|
730
|
-
* @returns {boolean}
|
|
731
|
-
*/
|
|
732
|
-
function inAwait(parser, tt) {
|
|
733
|
-
return currentType === tt.name &&
|
|
734
|
-
parser.value === 'await' &&
|
|
735
|
-
parser.canAwait &&
|
|
736
|
-
parser.preToken
|
|
737
|
-
? inExpression(parser.preToken, parser, tt)
|
|
738
|
-
: false;
|
|
739
|
-
}
|
|
740
|
-
|
|
741
|
-
if (inExpression(currentType, this, tt) || inAwait(this, tt)) {
|
|
742
|
-
return this.readAtIdentifier();
|
|
743
|
-
}
|
|
744
|
-
}
|
|
745
681
|
}
|
|
746
682
|
}
|
|
747
683
|
return super.getTokenFromCode(code);
|
|
748
684
|
}
|
|
749
685
|
|
|
750
|
-
/**
|
|
751
|
-
* Read an @ prefixed identifier
|
|
752
|
-
* @type {Parse.Parser['readAtIdentifier']}
|
|
753
|
-
*/
|
|
754
|
-
readAtIdentifier() {
|
|
755
|
-
const start = this.pos;
|
|
756
|
-
this.pos++; // skip '@'
|
|
757
|
-
|
|
758
|
-
// Read the identifier part manually
|
|
759
|
-
let word = '';
|
|
760
|
-
while (this.pos < this.input.length) {
|
|
761
|
-
const ch = this.input.charCodeAt(this.pos);
|
|
762
|
-
if (
|
|
763
|
-
(ch >= 65 && ch <= 90) || // A-Z
|
|
764
|
-
(ch >= 97 && ch <= 122) || // a-z
|
|
765
|
-
(ch >= 48 && ch <= 57) || // 0-9
|
|
766
|
-
ch === 95 ||
|
|
767
|
-
ch === 36
|
|
768
|
-
) {
|
|
769
|
-
// _ or $
|
|
770
|
-
word += this.input[this.pos++];
|
|
771
|
-
} else {
|
|
772
|
-
break;
|
|
773
|
-
}
|
|
774
|
-
}
|
|
775
|
-
|
|
776
|
-
if (word === '') {
|
|
777
|
-
this.raise(start, 'Invalid @ identifier');
|
|
778
|
-
}
|
|
779
|
-
|
|
780
|
-
// Return the full identifier including @
|
|
781
|
-
return this.finishToken(tt.name, '@' + word);
|
|
782
|
-
}
|
|
783
|
-
|
|
784
|
-
/**
|
|
785
|
-
* Override parseIdent to mark @ identifiers as tracked
|
|
786
|
-
* @type {Parse.Parser['parseIdent']}
|
|
787
|
-
*/
|
|
788
|
-
parseIdent(liberal) {
|
|
789
|
-
const node = /** @type {AST.Identifier &AST.NodeWithLocation} */ (
|
|
790
|
-
super.parseIdent(liberal)
|
|
791
|
-
);
|
|
792
|
-
if (node.name && node.name.startsWith('@')) {
|
|
793
|
-
set_tracked_name(node, node.name);
|
|
794
|
-
node.tracked = true;
|
|
795
|
-
}
|
|
796
|
-
return node;
|
|
797
|
-
}
|
|
798
|
-
|
|
799
686
|
/**
|
|
800
687
|
* Override parseSubscripts to handle `.@[expression]` syntax for reactive computed member access
|
|
801
688
|
* @type {Parse.Parser['parseSubscripts']}
|
|
@@ -1342,7 +1229,6 @@ function RipplePlugin(config) {
|
|
|
1342
1229
|
jsx_parseExpressionContainer() {
|
|
1343
1230
|
let node = /** @type {ESTreeJSX.JSXExpressionContainer} */ (this.startNode());
|
|
1344
1231
|
this.next();
|
|
1345
|
-
let tracked = false;
|
|
1346
1232
|
|
|
1347
1233
|
if (this.value === 'html') {
|
|
1348
1234
|
node.html = true;
|
|
@@ -1353,20 +1239,12 @@ function RipplePlugin(config) {
|
|
|
1353
1239
|
'"html" is a Ripple keyword and must be used in the form {html some_content}',
|
|
1354
1240
|
);
|
|
1355
1241
|
}
|
|
1356
|
-
if (this.type.label === '@') {
|
|
1357
|
-
this.next(); // consume @
|
|
1358
|
-
tracked = true;
|
|
1359
|
-
}
|
|
1360
1242
|
}
|
|
1361
1243
|
|
|
1362
1244
|
node.expression =
|
|
1363
1245
|
this.type === tt.braceR ? this.jsx_parseEmptyExpression() : this.parseExpression();
|
|
1364
1246
|
this.expect(tt.braceR);
|
|
1365
1247
|
|
|
1366
|
-
if (tracked && node.expression.type === 'Identifier') {
|
|
1367
|
-
node.expression.tracked = true;
|
|
1368
|
-
}
|
|
1369
|
-
|
|
1370
1248
|
return this.finishNode(node, 'JSXExpressionContainer');
|
|
1371
1249
|
}
|
|
1372
1250
|
|
|
@@ -1429,10 +1307,6 @@ function RipplePlugin(config) {
|
|
|
1429
1307
|
} else {
|
|
1430
1308
|
const id = /** @type {AST.Identifier} */ (this.parseIdentNode());
|
|
1431
1309
|
id.tracked = false;
|
|
1432
|
-
if (id.name.startsWith('@')) {
|
|
1433
|
-
set_tracked_name(id, id.name);
|
|
1434
|
-
id.tracked = true;
|
|
1435
|
-
}
|
|
1436
1310
|
this.finishNode(id, 'Identifier');
|
|
1437
1311
|
/** @type {AST.Attribute} */ (node).name = id;
|
|
1438
1312
|
/** @type {AST.Attribute} */ (node).value = id;
|
|
@@ -1484,14 +1358,6 @@ function RipplePlugin(config) {
|
|
|
1484
1358
|
// Unexpected token after @
|
|
1485
1359
|
this.unexpected();
|
|
1486
1360
|
}
|
|
1487
|
-
} else if (
|
|
1488
|
-
(this.type === tt.name || this.type === tstt.jsxName) &&
|
|
1489
|
-
this.value &&
|
|
1490
|
-
/** @type {string} */ (this.value).startsWith('@')
|
|
1491
|
-
) {
|
|
1492
|
-
set_tracked_name(node, /** @type {string} */ (this.value));
|
|
1493
|
-
node.tracked = true;
|
|
1494
|
-
this.next();
|
|
1495
1361
|
} else if (this.type === tt.name || this.type.keyword || this.type === tstt.jsxName) {
|
|
1496
1362
|
node.name = /** @type {string} */ (this.value);
|
|
1497
1363
|
node.tracked = false; // Explicitly mark as not tracked
|
|
@@ -1550,15 +1416,14 @@ function RipplePlugin(config) {
|
|
|
1550
1416
|
// Expect closing bracket
|
|
1551
1417
|
this.expect(tt.bracketR);
|
|
1552
1418
|
} else {
|
|
1553
|
-
|
|
1554
|
-
memberExpr.property = this.jsx_parseIdentifier();
|
|
1555
|
-
memberExpr.computed = false;
|
|
1419
|
+
this.unexpected();
|
|
1556
1420
|
}
|
|
1557
1421
|
} else {
|
|
1558
1422
|
// Regular dot notation
|
|
1559
1423
|
memberExpr.property = this.jsx_parseIdentifier();
|
|
1560
1424
|
memberExpr.computed = false;
|
|
1561
1425
|
}
|
|
1426
|
+
memberExpr = this.finishNode(memberExpr, 'JSXMemberExpression');
|
|
1562
1427
|
while (this.eat(tt.dot)) {
|
|
1563
1428
|
let newMemberExpr = /** @type {ESTreeJSX.JSXMemberExpression} */ (
|
|
1564
1429
|
this.startNodeAt(
|
|
@@ -1571,7 +1436,7 @@ function RipplePlugin(config) {
|
|
|
1571
1436
|
newMemberExpr.computed = false;
|
|
1572
1437
|
memberExpr = this.finishNode(newMemberExpr, 'JSXMemberExpression');
|
|
1573
1438
|
}
|
|
1574
|
-
return
|
|
1439
|
+
return memberExpr;
|
|
1575
1440
|
}
|
|
1576
1441
|
return node;
|
|
1577
1442
|
}
|
|
@@ -2355,53 +2220,6 @@ function RipplePlugin(config) {
|
|
|
2355
2220
|
this.awaitPos = 0;
|
|
2356
2221
|
return this.parseComponent({ requireName: true, declareName: true });
|
|
2357
2222
|
}
|
|
2358
|
-
if (this.type.label === '@') {
|
|
2359
|
-
// Try to parse as an expression statement first using tryParse
|
|
2360
|
-
// This allows us to handle Ripple @ syntax like @count++ without
|
|
2361
|
-
// interfering with legitimate decorator syntax
|
|
2362
|
-
this.skip_decorator = true;
|
|
2363
|
-
const expressionResult = this.tryParse(() => {
|
|
2364
|
-
const node = /** @type {AST.ExpressionStatement} */ (this.startNode());
|
|
2365
|
-
this.next();
|
|
2366
|
-
// Force expression context to ensure @ is tokenized correctly
|
|
2367
|
-
const old_expr_allowed = this.exprAllowed;
|
|
2368
|
-
this.exprAllowed = true;
|
|
2369
|
-
node.expression = this.parseExpression();
|
|
2370
|
-
|
|
2371
|
-
if (node.expression.type === 'UpdateExpression') {
|
|
2372
|
-
/** @type {AST.Expression} */
|
|
2373
|
-
let object = node.expression.argument;
|
|
2374
|
-
while (object.type === 'MemberExpression') {
|
|
2375
|
-
object = /** @type {AST.Expression} */ (object.object);
|
|
2376
|
-
}
|
|
2377
|
-
if (object.type === 'Identifier') {
|
|
2378
|
-
object.tracked = true;
|
|
2379
|
-
}
|
|
2380
|
-
} else if (node.expression.type === 'AssignmentExpression') {
|
|
2381
|
-
/** @type {AST.Expression | AST.Pattern | AST.Identifier} */
|
|
2382
|
-
let object = node.expression.left;
|
|
2383
|
-
while (object.type === 'MemberExpression') {
|
|
2384
|
-
object = /** @type {AST.Expression} */ (object.object);
|
|
2385
|
-
}
|
|
2386
|
-
if (object.type === 'Identifier') {
|
|
2387
|
-
object.tracked = true;
|
|
2388
|
-
}
|
|
2389
|
-
} else if (node.expression.type === 'Identifier') {
|
|
2390
|
-
node.expression.tracked = true;
|
|
2391
|
-
} else {
|
|
2392
|
-
// TODO?
|
|
2393
|
-
}
|
|
2394
|
-
|
|
2395
|
-
this.exprAllowed = old_expr_allowed;
|
|
2396
|
-
return this.finishNode(node, 'ExpressionStatement');
|
|
2397
|
-
});
|
|
2398
|
-
this.skip_decorator = false;
|
|
2399
|
-
|
|
2400
|
-
// If parsing as expression statement succeeded, use that result
|
|
2401
|
-
if (expressionResult.node) {
|
|
2402
|
-
return expressionResult.node;
|
|
2403
|
-
}
|
|
2404
|
-
}
|
|
2405
2223
|
|
|
2406
2224
|
if (this.type === tstt.jsxTagStart) {
|
|
2407
2225
|
this.next();
|
|
@@ -2416,6 +2234,37 @@ function RipplePlugin(config) {
|
|
|
2416
2234
|
return node;
|
|
2417
2235
|
}
|
|
2418
2236
|
|
|
2237
|
+
// &[ or &{ at statement level — lazy destructuring assignment
|
|
2238
|
+
// e.g., &[data] = track(0); or &{x, y} = obj;
|
|
2239
|
+
if (this.type === tt.bitwiseAND) {
|
|
2240
|
+
const charAfterAmp = this.input.charCodeAt(this.end);
|
|
2241
|
+
if (charAfterAmp === 123 || charAfterAmp === 91) {
|
|
2242
|
+
const node = /** @type {AST.ExpressionStatement} */ (this.startNode());
|
|
2243
|
+
const assign_node = /** @type {AST.AssignmentExpression} */ (this.startNode());
|
|
2244
|
+
this.next(); // consume &
|
|
2245
|
+
// Parse the left-hand side (array or object expression)
|
|
2246
|
+
const left = /** @type {AST.ArrayPattern | AST.ObjectPattern} */ (
|
|
2247
|
+
/** @type {unknown} */ (this.parseExprAtom())
|
|
2248
|
+
);
|
|
2249
|
+
// Convert expression to destructuring pattern
|
|
2250
|
+
this.toAssignable(left, false);
|
|
2251
|
+
left.lazy = true;
|
|
2252
|
+
// Expect = operator
|
|
2253
|
+
this.expect(tt.eq);
|
|
2254
|
+
// Parse the right-hand side
|
|
2255
|
+
assign_node.operator = '=';
|
|
2256
|
+
assign_node.left = left;
|
|
2257
|
+
assign_node.right = /** @type {AST.Expression} */ (this.parseMaybeAssign());
|
|
2258
|
+
node.expression = /** @type {AST.AssignmentExpression} */ (
|
|
2259
|
+
this.finishNode(assign_node, 'AssignmentExpression')
|
|
2260
|
+
);
|
|
2261
|
+
this.semicolon();
|
|
2262
|
+
return /** @type {AST.ExpressionStatement} */ (
|
|
2263
|
+
this.finishNode(node, 'ExpressionStatement')
|
|
2264
|
+
);
|
|
2265
|
+
}
|
|
2266
|
+
}
|
|
2267
|
+
|
|
2419
2268
|
return super.parseStatement(context, topLevel, exports);
|
|
2420
2269
|
}
|
|
2421
2270
|
|
|
@@ -2809,15 +2658,9 @@ function get_comment_handlers(source, comments, index = 0) {
|
|
|
2809
2658
|
isSwitchCaseSibling = true;
|
|
2810
2659
|
} else if (parent.type === 'SwitchCase') {
|
|
2811
2660
|
node_array = parent.consequent;
|
|
2812
|
-
} else if (
|
|
2813
|
-
parent.type === 'ArrayExpression' ||
|
|
2814
|
-
parent.type === 'RippleArrayExpression'
|
|
2815
|
-
) {
|
|
2661
|
+
} else if (parent.type === 'ArrayExpression') {
|
|
2816
2662
|
node_array = parent.elements;
|
|
2817
|
-
} else if (
|
|
2818
|
-
parent.type === 'ObjectExpression' ||
|
|
2819
|
-
parent.type === 'RippleObjectExpression'
|
|
2820
|
-
) {
|
|
2663
|
+
} else if (parent.type === 'ObjectExpression') {
|
|
2821
2664
|
node_array = parent.properties;
|
|
2822
2665
|
} else if (
|
|
2823
2666
|
parent.type === 'FunctionDeclaration' ||
|
|
@@ -328,6 +328,35 @@ function setup_lazy_array_transforms(pattern, source_id, state, writable) {
|
|
|
328
328
|
}
|
|
329
329
|
}
|
|
330
330
|
|
|
331
|
+
/**
|
|
332
|
+
* Checks if a function parameter has a Tracked<T> type annotation imported from ripple.
|
|
333
|
+
* This is used to determine if lazy array destructuring should use the track tuple fast path.
|
|
334
|
+
* @param {AST.ArrayPattern} param - The parameter pattern node
|
|
335
|
+
* @param {AnalysisContext} context - The analysis context
|
|
336
|
+
* @returns {boolean}
|
|
337
|
+
*/
|
|
338
|
+
function is_param_tracked_type(param, context) {
|
|
339
|
+
const annotation = param.typeAnnotation?.typeAnnotation;
|
|
340
|
+
|
|
341
|
+
if (
|
|
342
|
+
annotation?.type === 'TSTypeReference' &&
|
|
343
|
+
annotation.typeName?.type === 'Identifier' &&
|
|
344
|
+
annotation.typeName.name === 'Tracked'
|
|
345
|
+
) {
|
|
346
|
+
const binding = context.state.scope.get('Tracked');
|
|
347
|
+
|
|
348
|
+
return (
|
|
349
|
+
binding?.declaration_kind === 'import' &&
|
|
350
|
+
binding.initial !== null &&
|
|
351
|
+
binding.initial.type === 'ImportDeclaration' &&
|
|
352
|
+
binding.initial.source.type === 'Literal' &&
|
|
353
|
+
binding.initial.source.value === 'ripple'
|
|
354
|
+
);
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
return false;
|
|
358
|
+
}
|
|
359
|
+
|
|
331
360
|
/**
|
|
332
361
|
* @param {AST.Function} node
|
|
333
362
|
* @param {AnalysisContext} context
|
|
@@ -345,7 +374,11 @@ function visit_function(node, context) {
|
|
|
345
374
|
|
|
346
375
|
if ((param.type === 'ObjectPattern' || param.type === 'ArrayPattern') && param.lazy) {
|
|
347
376
|
const param_id = b.id(context.state.scope.generate('param'));
|
|
348
|
-
|
|
377
|
+
// For ArrayPattern params with a Tracked<T> type annotation from ripple,
|
|
378
|
+
// use the track tuple fast path (get/set instead of source[0]/source[1])
|
|
379
|
+
const is_tracked_type =
|
|
380
|
+
param.type === 'ArrayPattern' && is_param_tracked_type(param, context);
|
|
381
|
+
setup_lazy_transforms(param, param_id, context.state, true, is_tracked_type);
|
|
349
382
|
// Store the generated identifier name on the pattern for the transform phase
|
|
350
383
|
param.metadata = { ...param.metadata, lazy_id: param_id.name };
|
|
351
384
|
}
|
|
@@ -579,7 +612,7 @@ const visitors = {
|
|
|
579
612
|
}
|
|
580
613
|
|
|
581
614
|
// Lazy bindings from track() calls (read_unwraps) are inherently reactive —
|
|
582
|
-
// propagate tracking
|
|
615
|
+
// propagate tracking so that control flow (if/for/switch)
|
|
583
616
|
// and early returns create reactive blocks
|
|
584
617
|
if (
|
|
585
618
|
!node.tracked &&
|
|
@@ -673,7 +706,7 @@ const visitors = {
|
|
|
673
706
|
|
|
674
707
|
if (propertyName && internalProperties.has(propertyName)) {
|
|
675
708
|
error(
|
|
676
|
-
`Directly accessing internal property "${propertyName}" of a tracked object is not allowed. Use
|
|
709
|
+
`Directly accessing internal property "${propertyName}" of a tracked object is not allowed. Use \`${node.object.name}.value\` or \`&[]\` lazy destructuring instead.`,
|
|
677
710
|
context.state.analysis.module.filename,
|
|
678
711
|
node.property,
|
|
679
712
|
context.state.loose ? context.state.analysis.errors : undefined,
|
|
@@ -684,6 +717,8 @@ const visitors = {
|
|
|
684
717
|
|
|
685
718
|
if (
|
|
686
719
|
binding !== null &&
|
|
720
|
+
binding.kind !== 'lazy' &&
|
|
721
|
+
binding.kind !== 'lazy_fallback' &&
|
|
687
722
|
binding.initial?.type === 'CallExpression' &&
|
|
688
723
|
is_ripple_track_call(binding.initial.callee, context)
|
|
689
724
|
) {
|
|
@@ -701,7 +736,7 @@ const visitors = {
|
|
|
701
736
|
// pass through
|
|
702
737
|
} else {
|
|
703
738
|
error(
|
|
704
|
-
`Accessing a tracked object directly is not allowed, use
|
|
739
|
+
`Accessing a tracked object directly is not allowed, use \`.value\` or \`&[]\` lazy destructuring to read the value inside a tracked object - for example \`${node.object.name}.value\``,
|
|
705
740
|
context.state.analysis.module.filename,
|
|
706
741
|
node.object,
|
|
707
742
|
context.state.loose ? context.state.analysis.errors : undefined,
|
|
@@ -794,20 +829,6 @@ const visitors = {
|
|
|
794
829
|
declarator.id.metadata = { ...declarator.id.metadata, lazy_id: lazy_id.name };
|
|
795
830
|
}
|
|
796
831
|
|
|
797
|
-
const paths = extract_paths(declarator.id);
|
|
798
|
-
|
|
799
|
-
for (const path of paths) {
|
|
800
|
-
if (path.node.tracked) {
|
|
801
|
-
error(
|
|
802
|
-
'Variables cannot be reactively referenced using @',
|
|
803
|
-
state.analysis.module.filename,
|
|
804
|
-
path.node,
|
|
805
|
-
context.state.loose ? context.state.analysis.errors : undefined,
|
|
806
|
-
context.state.analysis.comments,
|
|
807
|
-
);
|
|
808
|
-
}
|
|
809
|
-
}
|
|
810
|
-
|
|
811
832
|
visit(declarator, state);
|
|
812
833
|
}
|
|
813
834
|
|
|
@@ -815,6 +836,30 @@ const visitors = {
|
|
|
815
836
|
}
|
|
816
837
|
},
|
|
817
838
|
|
|
839
|
+
ExpressionStatement(node, context) {
|
|
840
|
+
const { state, visit } = context;
|
|
841
|
+
|
|
842
|
+
// Handle standalone lazy destructuring assignment: &[data] = track(0);
|
|
843
|
+
if (
|
|
844
|
+
node.expression.type === 'AssignmentExpression' &&
|
|
845
|
+
node.expression.operator === '=' &&
|
|
846
|
+
(node.expression.left.type === 'ObjectPattern' ||
|
|
847
|
+
node.expression.left.type === 'ArrayPattern') &&
|
|
848
|
+
node.expression.left.lazy
|
|
849
|
+
) {
|
|
850
|
+
const pattern = /** @type {AST.ObjectPattern | AST.ArrayPattern} */ (node.expression.left);
|
|
851
|
+
const lazy_id = b.id(state.scope.generate('lazy'));
|
|
852
|
+
const init = /** @type {AST.Expression} */ (node.expression.right);
|
|
853
|
+
const init_is_track =
|
|
854
|
+
init?.type === 'CallExpression' && is_ripple_track_call(init.callee, context) === 'track';
|
|
855
|
+
setup_lazy_transforms(pattern, lazy_id, state, true, !!init_is_track);
|
|
856
|
+
// Store the generated identifier name on the pattern for the transform phase
|
|
857
|
+
pattern.metadata = { ...pattern.metadata, lazy_id: lazy_id.name };
|
|
858
|
+
}
|
|
859
|
+
|
|
860
|
+
context.next();
|
|
861
|
+
},
|
|
862
|
+
|
|
818
863
|
StyleIdentifier(node, context) {
|
|
819
864
|
const component = is_inside_component(context, true);
|
|
820
865
|
const parent = context.path.at(-1);
|
|
@@ -533,9 +533,6 @@ const visitors = {
|
|
|
533
533
|
if (context.state.metadata?.tracking === false) {
|
|
534
534
|
context.state.metadata.tracking = true;
|
|
535
535
|
}
|
|
536
|
-
if (node.tracked && !binding?.read_unwraps) {
|
|
537
|
-
return b.call('_$_.get', build_getter(node, context));
|
|
538
|
-
}
|
|
539
536
|
}
|
|
540
537
|
return build_getter(node, context);
|
|
541
538
|
}
|
|
@@ -919,6 +916,25 @@ const visitors = {
|
|
|
919
916
|
return context.next();
|
|
920
917
|
},
|
|
921
918
|
|
|
919
|
+
ExpressionStatement(node, context) {
|
|
920
|
+
// Handle standalone lazy destructuring: &[data] = track(0); → const lazy0 = track(0);
|
|
921
|
+
if (
|
|
922
|
+
node.expression.type === 'AssignmentExpression' &&
|
|
923
|
+
node.expression.left.lazy &&
|
|
924
|
+
node.expression.left.metadata?.lazy_id
|
|
925
|
+
) {
|
|
926
|
+
if (context.state.to_ts) {
|
|
927
|
+
// In TypeScript mode, convert to a regular assignment (drop the pattern)
|
|
928
|
+
node.expression.left.lazy = false;
|
|
929
|
+
delete node.expression.left.metadata.lazy_id;
|
|
930
|
+
return context.next();
|
|
931
|
+
}
|
|
932
|
+
const right = /** @type {AST.Expression} */ (context.visit(node.expression.right));
|
|
933
|
+
return b.const(b.id(node.expression.left.metadata.lazy_id), right);
|
|
934
|
+
}
|
|
935
|
+
return context.next();
|
|
936
|
+
},
|
|
937
|
+
|
|
922
938
|
VariableDeclaration(node, context) {
|
|
923
939
|
for (const declarator of node.declarations) {
|
|
924
940
|
if (!context.state.to_ts) {
|
|
@@ -333,29 +333,7 @@ const visitors = {
|
|
|
333
333
|
binding.node !== node &&
|
|
334
334
|
(binding.kind === 'lazy' || binding.kind === 'lazy_fallback')
|
|
335
335
|
) {
|
|
336
|
-
|
|
337
|
-
if (node.tracked && !binding.read_unwraps) {
|
|
338
|
-
const is_right_side_of_assignment =
|
|
339
|
-
parent.type === 'AssignmentExpression' && parent.right === node;
|
|
340
|
-
if (
|
|
341
|
-
(parent.type !== 'AssignmentExpression' && parent.type !== 'UpdateExpression') ||
|
|
342
|
-
is_right_side_of_assignment
|
|
343
|
-
) {
|
|
344
|
-
return b.call('_$_.get', transformed);
|
|
345
|
-
}
|
|
346
|
-
}
|
|
347
|
-
return transformed;
|
|
348
|
-
}
|
|
349
|
-
|
|
350
|
-
if (node.tracked) {
|
|
351
|
-
const is_right_side_of_assignment =
|
|
352
|
-
parent.type === 'AssignmentExpression' && parent.right === node;
|
|
353
|
-
if (
|
|
354
|
-
(parent.type !== 'AssignmentExpression' && parent.type !== 'UpdateExpression') ||
|
|
355
|
-
is_right_side_of_assignment
|
|
356
|
-
) {
|
|
357
|
-
return b.call('_$_.get', node);
|
|
358
|
-
}
|
|
336
|
+
return binding.transform.read(node);
|
|
359
337
|
}
|
|
360
338
|
|
|
361
339
|
return node;
|
|
@@ -835,6 +813,19 @@ const visitors = {
|
|
|
835
813
|
return statements.length ? b.block(statements) : b.empty;
|
|
836
814
|
},
|
|
837
815
|
|
|
816
|
+
ExpressionStatement(node, context) {
|
|
817
|
+
// Handle standalone lazy destructuring: &[data] = track(0); → const lazy0 = track(0);
|
|
818
|
+
if (
|
|
819
|
+
node.expression.type === 'AssignmentExpression' &&
|
|
820
|
+
node.expression.left.lazy &&
|
|
821
|
+
node.expression.left.metadata?.lazy_id
|
|
822
|
+
) {
|
|
823
|
+
const right = /** @type {AST.Expression} */ (context.visit(node.expression.right));
|
|
824
|
+
return b.const(b.id(node.expression.left.metadata.lazy_id), right);
|
|
825
|
+
}
|
|
826
|
+
return context.next();
|
|
827
|
+
},
|
|
828
|
+
|
|
838
829
|
VariableDeclaration(node, context) {
|
|
839
830
|
for (const declarator of node.declarations) {
|
|
840
831
|
if (!context.state.to_ts) {
|
|
@@ -1202,9 +1193,10 @@ const visitors = {
|
|
|
1202
1193
|
|
|
1203
1194
|
/** @type {AST.Statement[]} */
|
|
1204
1195
|
const init = [];
|
|
1196
|
+
const visited_id = /** @type {AST.Expression} */ (visit(node.id, state));
|
|
1205
1197
|
/** @type {AST.Statement[]} */
|
|
1206
1198
|
const statements = [
|
|
1207
|
-
b.const(comp_id,
|
|
1199
|
+
b.const(comp_id, is_element_dynamic(node) ? b.call('_$_.get', visited_id) : visited_id),
|
|
1208
1200
|
b.const(args_id, b.array(args)),
|
|
1209
1201
|
];
|
|
1210
1202
|
|
|
@@ -543,8 +543,6 @@ export namespace Parse {
|
|
|
543
543
|
*/
|
|
544
544
|
finishToken(type: TokenType, val?: string | number | RegExp | bigint): void;
|
|
545
545
|
|
|
546
|
-
readAtIdentifier(): void;
|
|
547
|
-
|
|
548
546
|
/**
|
|
549
547
|
* Read a token based on character code
|
|
550
548
|
* Called by nextToken() for each character
|
|
@@ -930,8 +928,6 @@ export namespace Parse {
|
|
|
930
928
|
| AST.ServerIdentifier
|
|
931
929
|
| AST.StyleIdentifier
|
|
932
930
|
| AST.TrackedExpression
|
|
933
|
-
| AST.RippleArrayExpression
|
|
934
|
-
| AST.RippleObjectExpression
|
|
935
931
|
| AST.Component
|
|
936
932
|
| AST.Identifier
|
|
937
933
|
| AST.Literal;
|
|
@@ -954,12 +950,8 @@ export namespace Parse {
|
|
|
954
950
|
/** Parse parenthesized expression (just the expression) */
|
|
955
951
|
parseParenExpression(): AST.Expression;
|
|
956
952
|
|
|
957
|
-
parseRippleArrayExpression(): AST.RippleArrayExpression;
|
|
958
|
-
|
|
959
953
|
parseTrackedExpression(): AST.TrackedExpression;
|
|
960
954
|
|
|
961
|
-
parseRippleObjectExpression(): AST.RippleObjectExpression;
|
|
962
|
-
|
|
963
955
|
/**
|
|
964
956
|
* Parse item in parentheses (can be overridden for flow/ts)
|
|
965
957
|
*/
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
import { branch, destroy_block, render, render_spread } from './blocks.js';
|
|
4
4
|
import { COMPOSITE_BLOCK, DEFAULT_NAMESPACE, NAMESPACE_URI } from './constants.js';
|
|
5
5
|
import { hydrate_next, hydrating } from './hydration.js';
|
|
6
|
-
import { active_block, active_namespace, with_ns } from './runtime.js';
|
|
6
|
+
import { active_block, active_namespace, get, with_ns } from './runtime.js';
|
|
7
7
|
import { top_element_to_ns } from './utils.js';
|
|
8
8
|
|
|
9
9
|
/**
|
|
@@ -29,7 +29,7 @@ export function composite(get_component, node, props) {
|
|
|
29
29
|
|
|
30
30
|
render(
|
|
31
31
|
() => {
|
|
32
|
-
var component = get_component();
|
|
32
|
+
var component = get(get_component());
|
|
33
33
|
|
|
34
34
|
if (b !== null) {
|
|
35
35
|
destroy_block(b);
|