ripple 0.3.10 → 0.3.12
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 +38 -0
- package/package.json +2 -2
- package/src/compiler/errors.js +1 -1
- package/src/compiler/index.d.ts +3 -1
- package/src/compiler/phases/1-parse/index.js +170 -8
- package/src/compiler/phases/2-analyze/index.js +231 -20
- package/src/compiler/phases/3-transform/client/index.js +169 -77
- package/src/compiler/phases/3-transform/server/index.js +46 -3
- package/src/compiler/types/index.d.ts +19 -2
- package/src/compiler/types/parse.d.ts +1 -1
- package/src/compiler/utils.js +174 -0
- package/src/runtime/index-client.js +14 -4
- package/src/runtime/internal/client/composite.js +2 -2
- package/src/runtime/internal/client/expression.js +64 -2
- package/src/runtime/internal/client/portal.js +1 -1
- package/src/utils/builders.js +30 -0
- package/tests/client/basic/__snapshots__/basic.rendering.test.ripple.snap +1 -0
- package/tests/client/basic/basic.rendering.test.ripple +4 -2
- package/tests/client/composite/composite.render.test.ripple +46 -0
- package/tests/client/return.test.ripple +101 -0
- package/tests/client/tsx.test.ripple +486 -0
- package/tests/hydration/compiled/client/basic.js +8 -24
- package/tests/hydration/compiled/client/composite.js +6 -24
- package/tests/hydration/compiled/client/events.js +9 -54
- package/tests/hydration/compiled/client/for.js +59 -152
- package/tests/hydration/compiled/client/head.js +5 -20
- package/tests/hydration/compiled/client/hmr.js +2 -8
- package/tests/hydration/compiled/client/html.js +59 -226
- package/tests/hydration/compiled/client/if-children.js +6 -22
- package/tests/hydration/compiled/client/mixed-control-flow.js +18 -66
- package/tests/hydration/compiled/client/nested-control-flow.js +92 -368
- package/tests/hydration/compiled/client/portal.js +4 -16
- package/tests/hydration/compiled/client/reactivity.js +7 -40
- package/tests/hydration/compiled/client/return.js +1 -4
- package/tests/hydration/compiled/client/try.js +2 -2
- package/tests/utils/compiler-compat-config.test.js +38 -0
- package/tests/utils/vite-plugin-config.test.js +113 -0
- package/tsconfig.json +2 -0
- package/tsconfig.typecheck.json +2 -1
- package/types/index.d.ts +2 -12
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,43 @@
|
|
|
1
1
|
# ripple
|
|
2
2
|
|
|
3
|
+
## 0.3.12
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Updated dependencies []:
|
|
8
|
+
- ripple@0.3.12
|
|
9
|
+
|
|
10
|
+
## 0.3.11
|
|
11
|
+
|
|
12
|
+
### Patch Changes
|
|
13
|
+
|
|
14
|
+
- [#853](https://github.com/Ripple-TS/ripple/pull/853)
|
|
15
|
+
[`6792c70`](https://github.com/Ripple-TS/ripple/commit/6792c700db30ec0c25077bf8892753f18eddc5cc)
|
|
16
|
+
Thanks [@RazinShafayet2007](https://github.com/RazinShafayet2007)! -
|
|
17
|
+
fix(compiler): add `throw` statement support in `if` blocks
|
|
18
|
+
|
|
19
|
+
- [#858](https://github.com/Ripple-TS/ripple/pull/858)
|
|
20
|
+
[`f2624a6`](https://github.com/Ripple-TS/ripple/commit/f2624a6596479480c47317ea3030863214a6e2b3)
|
|
21
|
+
Thanks [@RazinShafayet2007](https://github.com/RazinShafayet2007)! - fix: scoped
|
|
22
|
+
styles apply correctly when child content is rendered through a parent component
|
|
23
|
+
|
|
24
|
+
- [#840](https://github.com/Ripple-TS/ripple/pull/840)
|
|
25
|
+
[`13323dd`](https://github.com/Ripple-TS/ripple/commit/13323dddbcb68e1e8e373142884a7c54fbb76cd7)
|
|
26
|
+
Thanks [@trueadm](https://github.com/trueadm)! - Remove the `compat` option from
|
|
27
|
+
`mount()` and `hydrate()`, and stop exporting the old public compat types from
|
|
28
|
+
`ripple`. Compat integrations are now expected to be provided by the Vite plugin
|
|
29
|
+
via `ripple.config.ts`, while direct runtime tests can seed the generated global
|
|
30
|
+
compat registry.
|
|
31
|
+
|
|
32
|
+
Also add the `reactCompat()` config-facing helper from `@ripple-ts/compat-react`
|
|
33
|
+
for use in `ripple.config.ts`.
|
|
34
|
+
|
|
35
|
+
- Updated dependencies
|
|
36
|
+
[[`6792c70`](https://github.com/Ripple-TS/ripple/commit/6792c700db30ec0c25077bf8892753f18eddc5cc),
|
|
37
|
+
[`f2624a6`](https://github.com/Ripple-TS/ripple/commit/f2624a6596479480c47317ea3030863214a6e2b3),
|
|
38
|
+
[`13323dd`](https://github.com/Ripple-TS/ripple/commit/13323dddbcb68e1e8e373142884a7c54fbb76cd7)]:
|
|
39
|
+
- ripple@0.3.11
|
|
40
|
+
|
|
3
41
|
## 0.3.10
|
|
4
42
|
|
|
5
43
|
### 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.12",
|
|
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.12"
|
|
109
109
|
}
|
|
110
110
|
}
|
package/src/compiler/errors.js
CHANGED
package/src/compiler/index.d.ts
CHANGED
|
@@ -98,6 +98,7 @@ interface SharedCompileOptions {
|
|
|
98
98
|
export interface CompileOptions extends SharedCompileOptions {
|
|
99
99
|
mode?: 'client' | 'server';
|
|
100
100
|
hmr?: boolean;
|
|
101
|
+
compat_kinds?: string[];
|
|
101
102
|
}
|
|
102
103
|
|
|
103
104
|
export interface ParseOptions {
|
|
@@ -106,7 +107,8 @@ export interface ParseOptions {
|
|
|
106
107
|
comments?: AST.CommentWithLocation[];
|
|
107
108
|
}
|
|
108
109
|
|
|
109
|
-
export interface AnalyzeOptions
|
|
110
|
+
export interface AnalyzeOptions
|
|
111
|
+
extends ParseOptions, Pick<CompileOptions, 'mode' | 'compat_kinds'> {
|
|
110
112
|
errors?: RippleCompileError[];
|
|
111
113
|
to_ts?: boolean;
|
|
112
114
|
}
|
|
@@ -1358,7 +1358,9 @@ function RipplePlugin(config) {
|
|
|
1358
1358
|
|
|
1359
1359
|
/** @type {Parse.Parser['jsx_readToken']} */
|
|
1360
1360
|
jsx_readToken() {
|
|
1361
|
-
const inside_tsx_compat = this.#path.findLast(
|
|
1361
|
+
const inside_tsx_compat = this.#path.findLast(
|
|
1362
|
+
(n) => n.type === 'TsxCompat' || n.type === 'Tsx',
|
|
1363
|
+
);
|
|
1362
1364
|
if (inside_tsx_compat) {
|
|
1363
1365
|
return super.jsx_readToken();
|
|
1364
1366
|
}
|
|
@@ -1509,6 +1511,48 @@ function RipplePlugin(config) {
|
|
|
1509
1511
|
}
|
|
1510
1512
|
}
|
|
1511
1513
|
|
|
1514
|
+
/**
|
|
1515
|
+
* Override jsx_parseElement to intercept expression-level JSX.
|
|
1516
|
+
* This is called by acorn-jsx's parseExprAtom when it encounters <
|
|
1517
|
+
* in expression position. Only <tsx> and <tsx:*> are allowed.
|
|
1518
|
+
* @type {Parse.Parser['jsx_parseElement']}
|
|
1519
|
+
*/
|
|
1520
|
+
jsx_parseElement() {
|
|
1521
|
+
const inside_tsx = this.#path.findLast((n) => n.type === 'TsxCompat' || n.type === 'Tsx');
|
|
1522
|
+
if (inside_tsx) {
|
|
1523
|
+
// Inside tsx/tsx:*, let acorn-jsx handle it normally
|
|
1524
|
+
return super.jsx_parseElement();
|
|
1525
|
+
}
|
|
1526
|
+
|
|
1527
|
+
// Check if the element being parsed IS a <tsx> or <tsx:*> tag
|
|
1528
|
+
// Current token is jsxTagStart, this.end is position after '<'
|
|
1529
|
+
const tag_name_start = this.end;
|
|
1530
|
+
const char_after_tsx = this.input.charCodeAt(tag_name_start + 3);
|
|
1531
|
+
const is_tsx_tag =
|
|
1532
|
+
this.input.startsWith('tsx', tag_name_start) &&
|
|
1533
|
+
(tag_name_start + 3 >= this.input.length ||
|
|
1534
|
+
char_after_tsx === 62 || // >
|
|
1535
|
+
char_after_tsx === 47 || // / (self-closing)
|
|
1536
|
+
char_after_tsx === 32 || // space
|
|
1537
|
+
char_after_tsx === 9 || // tab
|
|
1538
|
+
char_after_tsx === 10 || // newline
|
|
1539
|
+
char_after_tsx === 13 || // carriage return
|
|
1540
|
+
char_after_tsx === 58); // : (tsx:react)
|
|
1541
|
+
|
|
1542
|
+
if (is_tsx_tag) {
|
|
1543
|
+
// Use Ripple's parseElement to create a Tsx/TsxCompat node
|
|
1544
|
+
this.next();
|
|
1545
|
+
return /** @type {import('estree-jsx').JSXElement} */ (
|
|
1546
|
+
/** @type {unknown} */ (this.parseElement())
|
|
1547
|
+
);
|
|
1548
|
+
}
|
|
1549
|
+
|
|
1550
|
+
this.raise(
|
|
1551
|
+
this.start,
|
|
1552
|
+
'JSX elements cannot be used as expressions. Wrap with `<tsx>...</tsx>` or use elements as statements within a component.',
|
|
1553
|
+
);
|
|
1554
|
+
}
|
|
1555
|
+
|
|
1512
1556
|
/**
|
|
1513
1557
|
* @type {Parse.Parser['parseElement']}
|
|
1514
1558
|
*/
|
|
@@ -1520,7 +1564,7 @@ function RipplePlugin(config) {
|
|
|
1520
1564
|
const start = this.start - 1;
|
|
1521
1565
|
const position = new acorn.Position(this.curLine, start - this.lineStart);
|
|
1522
1566
|
|
|
1523
|
-
const element = /** @type {AST.Element | AST.TsxCompat} */ (this.startNode());
|
|
1567
|
+
const element = /** @type {AST.Element | AST.Tsx | AST.TsxCompat} */ (this.startNode());
|
|
1524
1568
|
element.start = start;
|
|
1525
1569
|
/** @type {AST.NodeWithLocation} */ (element).loc.start = position;
|
|
1526
1570
|
element.metadata = { path: [] };
|
|
@@ -1535,6 +1579,8 @@ function RipplePlugin(config) {
|
|
|
1535
1579
|
|
|
1536
1580
|
// Check if this is a namespaced element (tsx:react)
|
|
1537
1581
|
const is_tsx_compat = open.name.type === 'JSXNamespacedName';
|
|
1582
|
+
const is_tsx =
|
|
1583
|
+
!is_tsx_compat && open.name.type === 'JSXIdentifier' && open.name.name === 'tsx';
|
|
1538
1584
|
|
|
1539
1585
|
if (is_tsx_compat) {
|
|
1540
1586
|
const namespace_node = /** @type {ESTreeJSX.JSXNamespacedName} */ (open.name);
|
|
@@ -1548,6 +1594,15 @@ function RipplePlugin(config) {
|
|
|
1548
1594
|
`TSX compatibility elements cannot be self-closing. '<${tagName} />' must have a closing tag '</${tagName}>'.`,
|
|
1549
1595
|
);
|
|
1550
1596
|
}
|
|
1597
|
+
} else if (is_tsx) {
|
|
1598
|
+
/** @type {AST.Tsx} */ (element).type = 'Tsx';
|
|
1599
|
+
|
|
1600
|
+
if (open.selfClosing) {
|
|
1601
|
+
this.raise(
|
|
1602
|
+
open.start,
|
|
1603
|
+
`TSX elements cannot be self-closing. '<tsx />' must have a closing tag '</tsx>'.`,
|
|
1604
|
+
);
|
|
1605
|
+
}
|
|
1551
1606
|
} else {
|
|
1552
1607
|
element.type = 'Element';
|
|
1553
1608
|
}
|
|
@@ -1575,7 +1630,7 @@ function RipplePlugin(config) {
|
|
|
1575
1630
|
}
|
|
1576
1631
|
}
|
|
1577
1632
|
|
|
1578
|
-
if (!is_tsx_compat) {
|
|
1633
|
+
if (!is_tsx_compat && !is_tsx) {
|
|
1579
1634
|
/** @type {AST.Element} */ (element).id = /** @type {AST.Identifier} */ (
|
|
1580
1635
|
convert_from_jsx(/** @type {ESTreeJSX.JSXIdentifier} */ (open.name))
|
|
1581
1636
|
);
|
|
@@ -1745,6 +1800,7 @@ function RipplePlugin(config) {
|
|
|
1745
1800
|
const insideTemplate =
|
|
1746
1801
|
parent?.type === 'Component' ||
|
|
1747
1802
|
parent?.type === 'Element' ||
|
|
1803
|
+
parent?.type === 'Tsx' ||
|
|
1748
1804
|
parent?.type === 'TsxCompat';
|
|
1749
1805
|
|
|
1750
1806
|
if (curContext === tstc.tc_expr && !insideTemplate) {
|
|
@@ -1757,7 +1813,30 @@ function RipplePlugin(config) {
|
|
|
1757
1813
|
this.parseTemplateBody(/** @type {AST.Element} */ (element).children);
|
|
1758
1814
|
this.exitScope();
|
|
1759
1815
|
|
|
1760
|
-
if (element.type === '
|
|
1816
|
+
if (element.type === 'Tsx') {
|
|
1817
|
+
this.#path.pop();
|
|
1818
|
+
|
|
1819
|
+
if (!element.unclosed) {
|
|
1820
|
+
const raise_error = () => {
|
|
1821
|
+
this.raise(this.start, `Expected closing tag '</tsx>'`);
|
|
1822
|
+
};
|
|
1823
|
+
|
|
1824
|
+
this.next();
|
|
1825
|
+
// we should expect to see </tsx>
|
|
1826
|
+
if (this.value !== '/') {
|
|
1827
|
+
raise_error();
|
|
1828
|
+
}
|
|
1829
|
+
this.next();
|
|
1830
|
+
if (this.value !== 'tsx') {
|
|
1831
|
+
raise_error();
|
|
1832
|
+
}
|
|
1833
|
+
this.next();
|
|
1834
|
+
if (this.type !== tstt.jsxTagEnd) {
|
|
1835
|
+
raise_error();
|
|
1836
|
+
}
|
|
1837
|
+
this.next();
|
|
1838
|
+
}
|
|
1839
|
+
} else if (element.type === 'TsxCompat') {
|
|
1761
1840
|
this.#path.pop();
|
|
1762
1841
|
|
|
1763
1842
|
if (!element.unclosed) {
|
|
@@ -1813,6 +1892,7 @@ function RipplePlugin(config) {
|
|
|
1813
1892
|
const insideTemplate =
|
|
1814
1893
|
parent?.type === 'Component' ||
|
|
1815
1894
|
parent?.type === 'Element' ||
|
|
1895
|
+
parent?.type === 'Tsx' ||
|
|
1816
1896
|
parent?.type === 'TsxCompat';
|
|
1817
1897
|
|
|
1818
1898
|
if (curContext === tstc.tc_expr && !insideTemplate) {
|
|
@@ -1820,7 +1900,7 @@ function RipplePlugin(config) {
|
|
|
1820
1900
|
}
|
|
1821
1901
|
}
|
|
1822
1902
|
|
|
1823
|
-
if (element.closingElement && !is_tsx_compat) {
|
|
1903
|
+
if (element.closingElement && !is_tsx_compat && !is_tsx) {
|
|
1824
1904
|
/** @type {unknown} */ (element.closingElement.name) = convert_from_jsx(
|
|
1825
1905
|
element.closingElement.name,
|
|
1826
1906
|
);
|
|
@@ -1836,6 +1916,7 @@ function RipplePlugin(config) {
|
|
|
1836
1916
|
parseTemplateBody(body) {
|
|
1837
1917
|
const inside_func =
|
|
1838
1918
|
this.context.some((n) => n.token === 'function') || this.scopeStack.length > 1;
|
|
1919
|
+
const inside_tsx = this.#path.findLast((n) => n.type === 'Tsx');
|
|
1839
1920
|
const inside_tsx_compat = this.#path.findLast((n) => n.type === 'TsxCompat');
|
|
1840
1921
|
|
|
1841
1922
|
if (!inside_func) {
|
|
@@ -1847,6 +1928,75 @@ function RipplePlugin(config) {
|
|
|
1847
1928
|
}
|
|
1848
1929
|
}
|
|
1849
1930
|
|
|
1931
|
+
if (inside_tsx) {
|
|
1932
|
+
this.exprAllowed = true;
|
|
1933
|
+
|
|
1934
|
+
while (true) {
|
|
1935
|
+
if (this.type === tt.eof || this.pos >= this.input.length || this.type === tt.braceR) {
|
|
1936
|
+
if (!this.#loose) {
|
|
1937
|
+
this.raise(
|
|
1938
|
+
this.start,
|
|
1939
|
+
`Unclosed tag '<tsx>'. Expected '</tsx>' before end of component.`,
|
|
1940
|
+
);
|
|
1941
|
+
} else {
|
|
1942
|
+
inside_tsx.unclosed = true;
|
|
1943
|
+
/** @type {AST.NodeWithLocation} */ (inside_tsx).loc.end = {
|
|
1944
|
+
.../** @type {AST.SourceLocation} */ (inside_tsx.openingElement.loc).end,
|
|
1945
|
+
};
|
|
1946
|
+
inside_tsx.end = inside_tsx.openingElement.end;
|
|
1947
|
+
}
|
|
1948
|
+
return;
|
|
1949
|
+
}
|
|
1950
|
+
|
|
1951
|
+
if (this.input.slice(this.pos, this.pos + 4) === '/tsx') {
|
|
1952
|
+
const after = this.input.charCodeAt(this.pos + 4);
|
|
1953
|
+
// Make sure it's </tsx> and not </tsx:...>
|
|
1954
|
+
if (after === 62 /* > */) {
|
|
1955
|
+
return;
|
|
1956
|
+
}
|
|
1957
|
+
}
|
|
1958
|
+
|
|
1959
|
+
if (this.type === tt.braceL) {
|
|
1960
|
+
const node = this.jsx_parseExpressionContainer();
|
|
1961
|
+
body.push(node);
|
|
1962
|
+
} else if (this.type === tstt.jsxTagStart) {
|
|
1963
|
+
// Parse JSX element
|
|
1964
|
+
const node = super.parseExpression();
|
|
1965
|
+
body.push(node);
|
|
1966
|
+
} else {
|
|
1967
|
+
const start = this.start;
|
|
1968
|
+
this.pos = start;
|
|
1969
|
+
let text = '';
|
|
1970
|
+
|
|
1971
|
+
while (this.pos < this.input.length) {
|
|
1972
|
+
const ch = this.input.charCodeAt(this.pos);
|
|
1973
|
+
|
|
1974
|
+
// Stop at opening tag, expression, or the component-closing brace
|
|
1975
|
+
if (ch === 60 || ch === 123 || ch === 125) {
|
|
1976
|
+
// < or { or }
|
|
1977
|
+
break;
|
|
1978
|
+
}
|
|
1979
|
+
|
|
1980
|
+
text += this.input[this.pos];
|
|
1981
|
+
this.pos++;
|
|
1982
|
+
}
|
|
1983
|
+
|
|
1984
|
+
if (text) {
|
|
1985
|
+
const node = /** @type {ESTreeJSX.JSXText} */ ({
|
|
1986
|
+
type: 'JSXText',
|
|
1987
|
+
value: text,
|
|
1988
|
+
raw: text,
|
|
1989
|
+
start,
|
|
1990
|
+
end: this.pos,
|
|
1991
|
+
});
|
|
1992
|
+
body.push(node);
|
|
1993
|
+
}
|
|
1994
|
+
|
|
1995
|
+
// Always call next() to ensure parser makes progress
|
|
1996
|
+
this.next();
|
|
1997
|
+
}
|
|
1998
|
+
}
|
|
1999
|
+
}
|
|
1850
2000
|
if (inside_tsx_compat) {
|
|
1851
2001
|
this.exprAllowed = true;
|
|
1852
2002
|
|
|
@@ -1950,7 +2100,9 @@ function RipplePlugin(config) {
|
|
|
1950
2100
|
const currentElement = this.#path[this.#path.length - 1];
|
|
1951
2101
|
if (
|
|
1952
2102
|
!currentElement ||
|
|
1953
|
-
(currentElement.type !== 'Element' &&
|
|
2103
|
+
(currentElement.type !== 'Element' &&
|
|
2104
|
+
currentElement.type !== 'Tsx' &&
|
|
2105
|
+
currentElement.type !== 'TsxCompat')
|
|
1954
2106
|
) {
|
|
1955
2107
|
this.raise(this.start, 'Unexpected closing tag');
|
|
1956
2108
|
}
|
|
@@ -1966,6 +2118,12 @@ function RipplePlugin(config) {
|
|
|
1966
2118
|
closingElement.name?.type === 'JSXNamespacedName'
|
|
1967
2119
|
? closingElement.name.namespace.name + ':' + closingElement.name.name.name
|
|
1968
2120
|
: this.getElementName(closingElement.name);
|
|
2121
|
+
} else if (currentElement.type === 'Tsx') {
|
|
2122
|
+
openingTagName = 'tsx';
|
|
2123
|
+
closingTagName =
|
|
2124
|
+
closingElement.name?.type === 'JSXNamespacedName'
|
|
2125
|
+
? closingElement.name.namespace.name + ':' + closingElement.name.name.name
|
|
2126
|
+
: this.getElementName(closingElement.name);
|
|
1969
2127
|
} else {
|
|
1970
2128
|
// Regular Element node
|
|
1971
2129
|
openingTagName = this.getElementName(currentElement.id);
|
|
@@ -1987,12 +2145,16 @@ function RipplePlugin(config) {
|
|
|
1987
2145
|
const elem = this.#path[this.#path.length - 1];
|
|
1988
2146
|
|
|
1989
2147
|
// Stop at non-Element boundaries (Component, etc.)
|
|
1990
|
-
if (elem.type !== 'Element' && elem.type !== 'TsxCompat') {
|
|
2148
|
+
if (elem.type !== 'Element' && elem.type !== 'Tsx' && elem.type !== 'TsxCompat') {
|
|
1991
2149
|
break;
|
|
1992
2150
|
}
|
|
1993
2151
|
|
|
1994
2152
|
const elemName =
|
|
1995
|
-
elem.type === 'TsxCompat'
|
|
2153
|
+
elem.type === 'TsxCompat'
|
|
2154
|
+
? 'tsx:' + elem.kind
|
|
2155
|
+
: elem.type === 'Tsx'
|
|
2156
|
+
? 'tsx'
|
|
2157
|
+
: this.getElementName(elem.id);
|
|
1996
2158
|
|
|
1997
2159
|
// Found matching opening tag
|
|
1998
2160
|
if (elemName === closingTagName) {
|