ripple 0.3.9 → 0.3.10
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 +12 -0
- package/package.json +2 -2
- package/src/compiler/phases/1-parse/index.js +25 -15
- package/src/compiler/phases/2-analyze/index.js +35 -88
- package/src/compiler/phases/2-analyze/prune.js +13 -5
- package/src/compiler/phases/3-transform/client/index.js +188 -56
- package/src/compiler/phases/3-transform/server/index.js +62 -40
- package/src/compiler/types/index.d.ts +9 -1
- package/src/compiler/types/parse.d.ts +2 -0
- package/src/compiler/utils.js +101 -1
- package/src/runtime/element.js +39 -0
- package/src/runtime/internal/client/composite.js +10 -6
- package/src/runtime/internal/client/expression.js +218 -0
- package/src/runtime/internal/client/index.js +4 -0
- package/src/runtime/internal/client/portal.js +12 -6
- package/src/runtime/internal/server/index.js +26 -1
- package/tests/client/basic/basic.components.test.ripple +85 -87
- package/tests/client/basic/basic.errors.test.ripple +4 -8
- package/tests/client/basic/basic.rendering.test.ripple +23 -8
- package/tests/client/capture-error.js +12 -0
- package/tests/client/compiler/compiler.basic.test.ripple +76 -6
- package/tests/client/composite/composite.props.test.ripple +1 -3
- package/tests/client/composite/composite.render.test.ripple +45 -13
- package/tests/client/css/global-additional-cases.test.ripple +3 -3
- package/tests/client/svg.test.ripple +4 -4
- package/tests/hydration/basic.test.js +23 -0
- package/tests/hydration/compiled/client/basic.js +118 -66
- package/tests/hydration/compiled/client/composite.js +90 -37
- package/tests/hydration/compiled/client/events.js +18 -18
- package/tests/hydration/compiled/client/for.js +62 -62
- package/tests/hydration/compiled/client/head.js +10 -10
- package/tests/hydration/compiled/client/hmr.js +13 -10
- package/tests/hydration/compiled/client/html.js +274 -236
- package/tests/hydration/compiled/client/if-children.js +41 -35
- package/tests/hydration/compiled/client/if.js +2 -2
- package/tests/hydration/compiled/client/mixed-control-flow.js +12 -12
- package/tests/hydration/compiled/client/nested-control-flow.js +46 -46
- package/tests/hydration/compiled/client/portal.js +8 -8
- package/tests/hydration/compiled/client/reactivity.js +14 -14
- package/tests/hydration/compiled/client/return.js +2 -2
- package/tests/hydration/compiled/client/try.js +4 -4
- package/tests/hydration/compiled/server/basic.js +64 -31
- package/tests/hydration/compiled/server/composite.js +62 -29
- package/tests/hydration/compiled/server/hmr.js +24 -37
- package/tests/hydration/compiled/server/html.js +472 -611
- package/tests/hydration/compiled/server/if-children.js +77 -103
- package/tests/hydration/compiled/server/portal.js +8 -8
- package/tests/hydration/components/basic.ripple +15 -5
- package/tests/hydration/components/composite.ripple +13 -1
- package/tests/hydration/components/hmr.ripple +1 -3
- package/tests/hydration/components/html.ripple +13 -35
- package/tests/hydration/components/if-children.ripple +4 -8
- package/tests/hydration/composite.test.js +11 -0
- package/tests/server/basic.attributes.test.ripple +50 -0
- package/tests/server/basic.components.test.ripple +22 -28
- package/tests/server/basic.test.ripple +12 -0
- package/tests/server/compiler.test.ripple +25 -8
- package/tests/server/composite.props.test.ripple +1 -3
- package/tests/server/style-identifier.test.ripple +2 -4
- package/types/index.d.ts +9 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# ripple
|
|
2
2
|
|
|
3
|
+
## 0.3.10
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- [`aef1253`](https://github.com/Ripple-TS/ripple/commit/aef1253dd79c067a8358172d502dc21d8a9a9085)
|
|
8
|
+
Thanks [@trueadm](https://github.com/trueadm)! - Replace `<children />` with
|
|
9
|
+
`{children}` expression syntax for rendering component children
|
|
10
|
+
|
|
11
|
+
- Updated dependencies
|
|
12
|
+
[[`aef1253`](https://github.com/Ripple-TS/ripple/commit/aef1253dd79c067a8358172d502dc21d8a9a9085)]:
|
|
13
|
+
- ripple@0.3.10
|
|
14
|
+
|
|
3
15
|
## 0.3.9
|
|
4
16
|
|
|
5
17
|
### Patch Changes
|
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.10",
|
|
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.10"
|
|
109
109
|
}
|
|
110
110
|
}
|
|
@@ -1115,7 +1115,7 @@ function RipplePlugin(config) {
|
|
|
1115
1115
|
let node = /** @type {ESTreeJSX.JSXExpressionContainer} */ (this.startNode());
|
|
1116
1116
|
this.next();
|
|
1117
1117
|
|
|
1118
|
-
if (this.value === 'html') {
|
|
1118
|
+
if (this.type === tt.name && this.value === 'html') {
|
|
1119
1119
|
node.html = true;
|
|
1120
1120
|
this.next();
|
|
1121
1121
|
if (this.type === tt.braceR) {
|
|
@@ -1124,6 +1124,15 @@ function RipplePlugin(config) {
|
|
|
1124
1124
|
'"html" is a Ripple keyword and must be used in the form {html some_content}',
|
|
1125
1125
|
);
|
|
1126
1126
|
}
|
|
1127
|
+
} else if (this.type === tt.name && this.value === 'text') {
|
|
1128
|
+
node.text = true;
|
|
1129
|
+
this.next();
|
|
1130
|
+
if (this.type === tt.braceR) {
|
|
1131
|
+
this.raise(
|
|
1132
|
+
this.start,
|
|
1133
|
+
'"text" is a Ripple keyword and must be used in the form {text some_value}',
|
|
1134
|
+
);
|
|
1135
|
+
}
|
|
1127
1136
|
}
|
|
1128
1137
|
|
|
1129
1138
|
node.expression =
|
|
@@ -1328,16 +1337,12 @@ function RipplePlugin(config) {
|
|
|
1328
1337
|
const clause = /** @type {AST.CatchClause} */ (this.startNode());
|
|
1329
1338
|
this.next();
|
|
1330
1339
|
if (this.eat(tt.parenL)) {
|
|
1331
|
-
clause.param = this.
|
|
1340
|
+
clause.param = this.parseBindingAtom();
|
|
1341
|
+
this.expect(tt.parenR);
|
|
1332
1342
|
} else {
|
|
1333
|
-
if (this.options.ecmaVersion < 10) {
|
|
1334
|
-
this.unexpected();
|
|
1335
|
-
}
|
|
1336
1343
|
clause.param = null;
|
|
1337
|
-
this.enterScope(0);
|
|
1338
1344
|
}
|
|
1339
|
-
clause.body = this.parseBlock(
|
|
1340
|
-
this.exitScope();
|
|
1345
|
+
clause.body = this.parseBlock();
|
|
1341
1346
|
node.handler = this.finishNode(clause, 'CatchClause');
|
|
1342
1347
|
}
|
|
1343
1348
|
node.finalizer = this.eat(tt._finally) ? this.parseBlock() : null;
|
|
@@ -1909,12 +1914,13 @@ function RipplePlugin(config) {
|
|
|
1909
1914
|
if (this.type === tt.braceL) {
|
|
1910
1915
|
const node = this.jsx_parseExpressionContainer();
|
|
1911
1916
|
// Keep JSXEmptyExpression as-is (for prettier to handle comments)
|
|
1912
|
-
// but convert other expressions to Text
|
|
1917
|
+
// but convert other expressions to Html/RippleExpression/Text nodes
|
|
1913
1918
|
if (node.expression.type !== 'JSXEmptyExpression') {
|
|
1914
|
-
/** @type {AST.Html | AST.TextNode} */ (
|
|
1915
|
-
|
|
1916
|
-
|
|
1919
|
+
/** @type {AST.RippleExpression | AST.Html | AST.TextNode} */ (
|
|
1920
|
+
/** @type {unknown} */ (node)
|
|
1921
|
+
).type = node.html ? 'Html' : node.text ? 'Text' : 'RippleExpression';
|
|
1917
1922
|
delete node.html;
|
|
1923
|
+
delete node.text;
|
|
1918
1924
|
}
|
|
1919
1925
|
body.push(node);
|
|
1920
1926
|
} else if (this.type === tt.braceR) {
|
|
@@ -2050,12 +2056,16 @@ function RipplePlugin(config) {
|
|
|
2050
2056
|
this.context.some((c) => c === tstc.tc_expr)
|
|
2051
2057
|
) {
|
|
2052
2058
|
const node = this.jsx_parseExpressionContainer();
|
|
2053
|
-
// Keep JSXEmptyExpression as-is (don't convert to Text)
|
|
2059
|
+
// Keep JSXEmptyExpression as-is (don't convert to RippleExpression/Text/Html)
|
|
2054
2060
|
if (node.expression.type !== 'JSXEmptyExpression') {
|
|
2055
|
-
/** @type {AST.
|
|
2061
|
+
/** @type {AST.RippleExpression | AST.Html | AST.TextNode} */ (
|
|
2062
|
+
/** @type {unknown} */ (node)
|
|
2063
|
+
).type = node.html ? 'Html' : node.text ? 'Text' : 'RippleExpression';
|
|
2064
|
+
delete node.html;
|
|
2065
|
+
delete node.text;
|
|
2056
2066
|
}
|
|
2057
2067
|
|
|
2058
|
-
return /** @type {ESTreeJSX.JSXEmptyExpression | AST.TextNode | ESTreeJSX.JSXExpressionContainer} */ (
|
|
2068
|
+
return /** @type {ESTreeJSX.JSXEmptyExpression | AST.RippleExpression | AST.Html | AST.TextNode | ESTreeJSX.JSXExpressionContainer} */ (
|
|
2059
2069
|
/** @type {unknown} */ (node)
|
|
2060
2070
|
);
|
|
2061
2071
|
}
|
|
@@ -26,6 +26,7 @@ import {
|
|
|
26
26
|
is_inside_component,
|
|
27
27
|
is_ripple_track_call,
|
|
28
28
|
is_void_element,
|
|
29
|
+
is_children_template_expression as is_children_template_expression_in_scope,
|
|
29
30
|
normalize_children,
|
|
30
31
|
is_binding_function,
|
|
31
32
|
is_inside_try_block,
|
|
@@ -687,37 +688,6 @@ function error_return_keyword(node, context, message) {
|
|
|
687
688
|
);
|
|
688
689
|
}
|
|
689
690
|
|
|
690
|
-
/**
|
|
691
|
-
* @param {AST.Expression} expression
|
|
692
|
-
* @returns {AST.Expression}
|
|
693
|
-
*/
|
|
694
|
-
function unwrap_template_expression(expression) {
|
|
695
|
-
/** @type {AST.Expression} */
|
|
696
|
-
let node = expression;
|
|
697
|
-
|
|
698
|
-
while (true) {
|
|
699
|
-
if (
|
|
700
|
-
node.type === 'ParenthesizedExpression' ||
|
|
701
|
-
node.type === 'TSAsExpression' ||
|
|
702
|
-
node.type === 'TSSatisfiesExpression' ||
|
|
703
|
-
node.type === 'TSNonNullExpression' ||
|
|
704
|
-
node.type === 'TSInstantiationExpression'
|
|
705
|
-
) {
|
|
706
|
-
node = /** @type {AST.Expression} */ (node.expression);
|
|
707
|
-
continue;
|
|
708
|
-
}
|
|
709
|
-
|
|
710
|
-
if (node.type === 'ChainExpression') {
|
|
711
|
-
node = /** @type {AST.Expression} */ (node.expression);
|
|
712
|
-
continue;
|
|
713
|
-
}
|
|
714
|
-
|
|
715
|
-
break;
|
|
716
|
-
}
|
|
717
|
-
|
|
718
|
-
return node;
|
|
719
|
-
}
|
|
720
|
-
|
|
721
691
|
/**
|
|
722
692
|
* @param {AST.Expression} expression
|
|
723
693
|
* @param {Context<AST.Node, AnalysisState>} context
|
|
@@ -726,44 +696,7 @@ function unwrap_template_expression(expression) {
|
|
|
726
696
|
function is_children_template_expression(expression, context) {
|
|
727
697
|
const component = context.path.findLast((node) => node.type === 'Component');
|
|
728
698
|
const component_scope = component ? context.state.scopes.get(component) : null;
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
if (unwrapped.type === 'MemberExpression') {
|
|
732
|
-
let property_name = null;
|
|
733
|
-
|
|
734
|
-
if (!unwrapped.computed && unwrapped.property.type === 'Identifier') {
|
|
735
|
-
property_name = unwrapped.property.name;
|
|
736
|
-
} else if (
|
|
737
|
-
unwrapped.computed &&
|
|
738
|
-
unwrapped.property.type === 'Literal' &&
|
|
739
|
-
typeof unwrapped.property.value === 'string'
|
|
740
|
-
) {
|
|
741
|
-
property_name = unwrapped.property.value;
|
|
742
|
-
}
|
|
743
|
-
|
|
744
|
-
if (property_name === 'children') {
|
|
745
|
-
const target = unwrap_template_expression(/** @type {AST.Expression} */ (unwrapped.object));
|
|
746
|
-
|
|
747
|
-
if (target.type === 'Identifier') {
|
|
748
|
-
const binding = context.state.scope.get(target.name);
|
|
749
|
-
return binding?.declaration_kind === 'param' && binding.scope === component_scope;
|
|
750
|
-
}
|
|
751
|
-
}
|
|
752
|
-
}
|
|
753
|
-
|
|
754
|
-
if (unwrapped.type !== 'Identifier' || unwrapped.name !== 'children') {
|
|
755
|
-
return false;
|
|
756
|
-
}
|
|
757
|
-
|
|
758
|
-
const binding = context.state.scope.get(unwrapped.name);
|
|
759
|
-
return (
|
|
760
|
-
(binding?.declaration_kind === 'param' ||
|
|
761
|
-
binding?.kind === 'prop' ||
|
|
762
|
-
binding?.kind === 'prop_fallback' ||
|
|
763
|
-
binding?.kind === 'lazy' ||
|
|
764
|
-
binding?.kind === 'lazy_fallback') &&
|
|
765
|
-
binding.scope === component_scope
|
|
766
|
-
);
|
|
699
|
+
return is_children_template_expression_in_scope(expression, context.state.scope, component_scope);
|
|
767
700
|
}
|
|
768
701
|
|
|
769
702
|
/** @type {Visitors<AST.Node, AnalysisState>} */
|
|
@@ -999,7 +932,7 @@ const visitors = {
|
|
|
999
932
|
is_children_template_expression(/** @type {AST.Expression} */ (callee), context)
|
|
1000
933
|
) {
|
|
1001
934
|
error(
|
|
1002
|
-
'`children` cannot be called like a regular function.
|
|
935
|
+
'`children` cannot be called like a regular function. Render it with `{children}` or `{props.children}` instead.',
|
|
1003
936
|
context.state.analysis.module.filename,
|
|
1004
937
|
callee,
|
|
1005
938
|
context.state.loose ? context.state.analysis.errors : undefined,
|
|
@@ -1718,6 +1651,19 @@ const visitors = {
|
|
|
1718
1651
|
|
|
1719
1652
|
mark_control_flow_has_template(path);
|
|
1720
1653
|
|
|
1654
|
+
if (
|
|
1655
|
+
!is_dom_element &&
|
|
1656
|
+
is_children_template_expression(/** @type {AST.Expression} */ (node.id), context)
|
|
1657
|
+
) {
|
|
1658
|
+
error(
|
|
1659
|
+
'`children` cannot be rendered as a component. Render it with `{children}` or `{props.children}` instead.',
|
|
1660
|
+
state.analysis.module.filename,
|
|
1661
|
+
node.id,
|
|
1662
|
+
context.state.loose ? context.state.analysis.errors : undefined,
|
|
1663
|
+
context.state.analysis.comments,
|
|
1664
|
+
);
|
|
1665
|
+
}
|
|
1666
|
+
|
|
1721
1667
|
validate_nesting(node, context);
|
|
1722
1668
|
|
|
1723
1669
|
// Store capitalized name for dynamic components/elements
|
|
@@ -1771,7 +1717,10 @@ const visitors = {
|
|
|
1771
1717
|
if (/** @type {AST.Identifier} */ (node.id).name === 'title') {
|
|
1772
1718
|
const children = normalize_children(node.children, context);
|
|
1773
1719
|
|
|
1774
|
-
if (
|
|
1720
|
+
if (
|
|
1721
|
+
children.length !== 1 ||
|
|
1722
|
+
(children[0].type !== 'RippleExpression' && children[0].type !== 'Text')
|
|
1723
|
+
) {
|
|
1775
1724
|
// TODO: could transform children as something, e.g. Text Node, and avoid a fatal error
|
|
1776
1725
|
error(
|
|
1777
1726
|
'<title> must have only contain text nodes',
|
|
@@ -1916,30 +1865,22 @@ const visitors = {
|
|
|
1916
1865
|
}
|
|
1917
1866
|
/** @type {(AST.Node | AST.Expression)[]} */
|
|
1918
1867
|
let implicit_children = [];
|
|
1919
|
-
/** @type {AST.Identifier[]} */
|
|
1920
|
-
let explicit_children = [];
|
|
1921
1868
|
|
|
1922
1869
|
for (const child of node.children) {
|
|
1923
1870
|
if (child.type === 'Component') {
|
|
1924
|
-
if (child.id?.name === 'children') {
|
|
1925
|
-
explicit_children.push(child.id);
|
|
1926
|
-
}
|
|
1927
|
-
} else if (child.type !== 'EmptyStatement') {
|
|
1928
|
-
implicit_children.push(
|
|
1929
|
-
child.type === 'Text' || child.type === 'Html' ? child.expression : child,
|
|
1930
|
-
);
|
|
1931
|
-
}
|
|
1932
|
-
}
|
|
1933
|
-
|
|
1934
|
-
if (implicit_children.length > 0 && explicit_children.length > 0) {
|
|
1935
|
-
for (const item of [...explicit_children, ...implicit_children]) {
|
|
1936
1871
|
error(
|
|
1937
|
-
'
|
|
1872
|
+
'Component declarations cannot be used inside composite component children. Pass them as explicit props on the template element instead.',
|
|
1938
1873
|
state.analysis.module.filename,
|
|
1939
|
-
|
|
1874
|
+
child.id || child,
|
|
1940
1875
|
context.state.loose ? context.state.analysis.errors : undefined,
|
|
1941
1876
|
context.state.analysis.comments,
|
|
1942
1877
|
);
|
|
1878
|
+
} else if (child.type !== 'EmptyStatement') {
|
|
1879
|
+
implicit_children.push(
|
|
1880
|
+
child.type === 'RippleExpression' || child.type === 'Text' || child.type === 'Html'
|
|
1881
|
+
? child.expression
|
|
1882
|
+
: child,
|
|
1883
|
+
);
|
|
1943
1884
|
}
|
|
1944
1885
|
}
|
|
1945
1886
|
}
|
|
@@ -1966,12 +1907,18 @@ const visitors = {
|
|
|
1966
1907
|
};
|
|
1967
1908
|
},
|
|
1968
1909
|
|
|
1910
|
+
RippleExpression(node, context) {
|
|
1911
|
+
mark_control_flow_has_template(context.path);
|
|
1912
|
+
|
|
1913
|
+
context.next();
|
|
1914
|
+
},
|
|
1915
|
+
|
|
1969
1916
|
Text(node, context) {
|
|
1970
1917
|
mark_control_flow_has_template(context.path);
|
|
1971
1918
|
|
|
1972
1919
|
if (is_children_template_expression(/** @type {AST.Expression} */ (node.expression), context)) {
|
|
1973
1920
|
error(
|
|
1974
|
-
'`children` cannot be rendered using text interpolation. Use
|
|
1921
|
+
'`children` cannot be rendered using explicit text interpolation. Use `{children}` or `{props.children}` instead.',
|
|
1975
1922
|
context.state.analysis.module.filename,
|
|
1976
1923
|
node.expression,
|
|
1977
1924
|
context.state.loose ? context.state.analysis.errors : undefined,
|
|
@@ -306,12 +306,20 @@ function get_descendant_elements(node, adjacent_only) {
|
|
|
306
306
|
}
|
|
307
307
|
}
|
|
308
308
|
|
|
309
|
-
// For template nodes and
|
|
309
|
+
// For template nodes and interpolation expressions
|
|
310
310
|
if (
|
|
311
|
-
|
|
312
|
-
|
|
311
|
+
(current_node.type === 'RippleExpression' ||
|
|
312
|
+
current_node.type === 'Text' ||
|
|
313
|
+
current_node.type === 'Html') &&
|
|
314
|
+
/** @type {AST.RippleExpression | AST.Html | AST.TextNode} */ (current_node).expression &&
|
|
315
|
+
typeof (
|
|
316
|
+
/** @type {AST.RippleExpression | AST.Html | AST.TextNode} */ (current_node).expression
|
|
317
|
+
) === 'object'
|
|
313
318
|
) {
|
|
314
|
-
visit(
|
|
319
|
+
visit(
|
|
320
|
+
/** @type {AST.RippleExpression | AST.Html | AST.TextNode} */ (current_node).expression,
|
|
321
|
+
depth + 1,
|
|
322
|
+
);
|
|
315
323
|
}
|
|
316
324
|
}
|
|
317
325
|
|
|
@@ -409,7 +417,7 @@ function get_possible_element_siblings(node, direction, adjacent_only) {
|
|
|
409
417
|
// Stop at non-whitespace text nodes for adjacent selectors
|
|
410
418
|
else if (
|
|
411
419
|
adjacent_only &&
|
|
412
|
-
sibling.type === 'Text' &&
|
|
420
|
+
(sibling.type === 'RippleExpression' || sibling.type === 'Text') &&
|
|
413
421
|
sibling.expression.type === 'Literal' &&
|
|
414
422
|
typeof sibling.expression.value === 'string' &&
|
|
415
423
|
sibling.expression.value.trim()
|