@kudzujs/core 0.7.22 → 0.7.23
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/README.md +2 -1
- package/RELEASES.md +24 -0
- package/framework/README.md +2 -0
- package/framework/build.mjs +19 -7
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -10,7 +10,7 @@ Kudzu compiles ordinary React-shaped TypeScript and TSX into complete static HTM
|
|
|
10
10
|
|
|
11
11
|
> Experimental `0.7.x`: the compiler API and supported TSX surface may change.
|
|
12
12
|
|
|
13
|
-
**Latest release: 0.7.
|
|
13
|
+
**Latest release: 0.7.23 - Router-shaped runtime params.** Named or aliased React Router `useParams()` authoring now redirects to Kudzu's existing route-specific pathname reader with no router runtime. Read the [release notes](./RELEASES.md#0723---router-shaped-runtime-params) or open the [release page](https://kudzujs.cloud/releases/0.7.23).
|
|
14
14
|
|
|
15
15
|
- [Documentation](https://kudzujs.cloud/docs)
|
|
16
16
|
- [Installation guide](https://kudzujs.cloud/docs#install)
|
|
@@ -83,6 +83,7 @@ ordinary React-shaped TSX
|
|
|
83
83
|
- Build-known data and routes become complete HTML through async components and `getStaticPaths()`.
|
|
84
84
|
- Native document navigation is the default; static routes do not load a client runtime.
|
|
85
85
|
- A named or aliased React Router `Link` with a static root-relative `to` erases to a base-aware native anchor; no router package or runtime is emitted.
|
|
86
|
+
- A direct named or aliased React Router `useParams()` call on a `runtimeParams` bracket route reuses Kudzu's route-specific pathname reader.
|
|
86
87
|
- Unsupported nearby patterns fail during the build with a source location and actionable boundary.
|
|
87
88
|
|
|
88
89
|
Migration input may retain supported imports from `react`; Kudzu erases those references and never emits or executes React. New Kudzu source should import framework APIs from `@kudzujs/core`.
|
package/RELEASES.md
CHANGED
|
@@ -1,5 +1,29 @@
|
|
|
1
1
|
# Kudzu Releases
|
|
2
2
|
|
|
3
|
+
## 0.7.23 - Router-shaped runtime params
|
|
4
|
+
|
|
5
|
+
Kudzu 0.7.23 accepts the conventional React Router `useParams()` source shape on runtime bracket routes and redirects it to the existing capability-specific pathname reader without shipping React Router.
|
|
6
|
+
|
|
7
|
+
### New in 0.7.23
|
|
8
|
+
|
|
9
|
+
- Named or aliased `useParams` imports from `react-router-dom` may be called directly on bracket pages exporting `runtimeParams = true`.
|
|
10
|
+
- One optional TypeScript type argument is preserved through normalization while the runtime call remains argument-free.
|
|
11
|
+
- The React Router binding is redirected to `@kudzujs/core`; no `react-router-dom` module reference survives emitted code.
|
|
12
|
+
- Mixed `Link` and `useParams` imports lower together: Link becomes a base-aware native anchor and params reuse the pathname reader.
|
|
13
|
+
- Runtime parameter values retain existing secure decoding, direct DOM bindings, effects, handlers, and navigation-group lifecycle behavior.
|
|
14
|
+
- Indirect references, runtime arguments, default/namespace imports, and unsupported router hooks fail with source diagnostics.
|
|
15
|
+
- The complete suite passes 112/112 tests with browser coverage for multi-parameter paths and mixed Link/params migration input.
|
|
16
|
+
|
|
17
|
+
### Boundary
|
|
18
|
+
|
|
19
|
+
React Router `useParams()` lowering requires a direct named or aliased zero-argument call on a bracket route exporting `runtimeParams = true`. Build-known `getStaticPaths()` routes continue to receive params through page props. Catch-all routes, optional segments, indirect calls, runtime arguments, and other router hooks remain unsupported. No SPA router or shared router runtime is added.
|
|
20
|
+
|
|
21
|
+
### Upgrade
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
npm install @kudzujs/core@^0.7.23
|
|
25
|
+
```
|
|
26
|
+
|
|
3
27
|
## 0.7.22 - SVG structures and native links
|
|
4
28
|
|
|
5
29
|
Kudzu 0.7.22 extends ordinary React-shaped structural authoring into SVG and erases the common React Router `Link` form to native navigation without adding an SVG renderer or router runtime.
|
package/framework/README.md
CHANGED
|
@@ -6,6 +6,8 @@ Migration source may retain conventional `react` imports for supported named or
|
|
|
6
6
|
|
|
7
7
|
A named or aliased `Link` import from `react-router-dom` may render directly with one static root-relative `to` plus native anchor props. The compiler prefixes the configured `base`, changes the element to `<a href>`, and erases the import, so native navigation remains the default and configured navigation groups see an ordinary eligible anchor. Dynamic or relative destinations, `NavLink`, router-only props, spreads, default/namespace imports, and non-JSX uses fail with source diagnostics. No React Router package code or router runtime is emitted.
|
|
8
8
|
|
|
9
|
+
A named or aliased React Router `useParams` import may also be called directly without runtime arguments on a bracket route exporting `runtimeParams = true`. The compiler redirects that local binding to `@kudzujs/core`, preserving one optional TypeScript type argument, and reuses the existing route-specific pathname matcher. Indirect calls and other router hooks remain unsupported. Build-known `getStaticPaths()` routes use page props instead because no browser pathname capability is needed.
|
|
10
|
+
|
|
9
11
|
Direct `clsx` calls over literal strings, numbers, arrays, object conditions, and conditional expressions are similarly lowered to ordinary concatenation and conditional expressions. The package import is erased, and dynamic classes continue through the existing binding compiler without serializing or shipping the `clsx` function.
|
|
10
12
|
|
|
11
13
|
Repeated ordinary same-file and relative-imported child components execute independently at build time, so each `useState` call receives a distinct concrete state ID while shared native handler modules retain per-element state maps and captures. A direct JSON-safe primitive parent state passed to a destructured child prop remains the same signal for child DOM bindings and effect dependencies; repeated calls own independent effect records, and conditional removal cleans up before remount recreates the effect. Multiple direct primitive dependencies share the existing commit batching path: every value is compared with `Object.is`, and one or more same-turn changes cause one cleanup and rerun. A top-level immutable local derived through a supported pure primitive expression from direct state may also be an effect dependency: source state commits schedule evaluation, the derived result is compared with `Object.is`, and the expression is substituted into setup and cleanup handlers. Effect setup and directly returned cleanup callbacks may each resolve one top-level simple `const` function in the same component; those functions are substituted into the existing handler graph rather than retained in a browser registry. Reactive conditional descriptors own state created by their direct branch: initial visible output reuses the rendered template IDs, removal deletes those slots, and remount recreates them from serialized initial values. Static sibling routes and branches without local state add no ownership metadata, component function, hook dispatcher, or rerender loop.
|
package/framework/build.mjs
CHANGED
|
@@ -1776,25 +1776,27 @@ function emittedPackageReference(source, file, packages) {
|
|
|
1776
1776
|
|
|
1777
1777
|
function normalizeReactRouterSyntax(sourceFile, factory, context, base) {
|
|
1778
1778
|
const links = new Set()
|
|
1779
|
+
const params = new Set()
|
|
1779
1780
|
for (const statement of sourceFile.statements) {
|
|
1780
1781
|
if ((ts.isExportDeclaration(statement) || ts.isImportDeclaration(statement)) && ts.isStringLiteral(statement.moduleSpecifier) && statement.moduleSpecifier.text === "react-router-dom") {
|
|
1781
1782
|
if (ts.isExportDeclaration(statement)) throw sourceNodeError(statement, sourceFile, "React Router exports are not supported; import Link directly where it renders")
|
|
1782
1783
|
const clause = statement.importClause
|
|
1783
1784
|
if (clause?.isTypeOnly) continue
|
|
1784
1785
|
if (!clause) throw sourceNodeError(statement, sourceFile, "Side-effect React Router imports are not supported")
|
|
1785
|
-
if (clause.name) throw sourceNodeError(clause.name, sourceFile, "React Router default imports are not supported; use
|
|
1786
|
+
if (clause.name) throw sourceNodeError(clause.name, sourceFile, "React Router default imports are not supported; use named Link or useParams imports")
|
|
1786
1787
|
const bindings = clause.namedBindings
|
|
1787
|
-
if (!bindings || ts.isNamespaceImport(bindings)) throw sourceNodeError(bindings ?? statement, sourceFile, "React Router namespace imports are not supported; use
|
|
1788
|
+
if (!bindings || ts.isNamespaceImport(bindings)) throw sourceNodeError(bindings ?? statement, sourceFile, "React Router namespace imports are not supported; use named Link or useParams imports")
|
|
1788
1789
|
for (const entry of bindings.elements) {
|
|
1789
1790
|
if (entry.isTypeOnly) continue
|
|
1790
1791
|
const imported = (entry.propertyName ?? entry.name).text
|
|
1791
1792
|
if (imported === "NavLink") throw sourceNodeError(entry, sourceFile, "React Router NavLink active-route semantics cannot be erased to a native anchor")
|
|
1792
|
-
if (imported
|
|
1793
|
-
|
|
1793
|
+
if (imported === "Link") links.add(entry.name.text)
|
|
1794
|
+
else if (imported === "useParams") params.add(entry.name.text)
|
|
1795
|
+
else throw sourceNodeError(entry, sourceFile, `React Router ${imported} is not supported; only named Link and useParams imports can be lowered`)
|
|
1794
1796
|
}
|
|
1795
1797
|
}
|
|
1796
1798
|
}
|
|
1797
|
-
if (!links.size) return sourceFile
|
|
1799
|
+
if (!links.size && !params.size) return sourceFile
|
|
1798
1800
|
|
|
1799
1801
|
const routerProps = new Set(["discover", "end", "prefetch", "preventScrollReset", "relative", "reloadDocument", "replace", "state", "viewTransition"])
|
|
1800
1802
|
const attributes = attributesNode => {
|
|
@@ -1829,19 +1831,29 @@ function normalizeReactRouterSyntax(sourceFile, factory, context, base) {
|
|
|
1829
1831
|
return factory.updateJsxElement(node, opening, ts.visitNodes(node.children, visitor), closing)
|
|
1830
1832
|
}
|
|
1831
1833
|
if (ts.isJsxSelfClosingElement(node) && importedLink(node.tagName)) return factory.updateJsxSelfClosingElement(node, factory.createIdentifier("a"), node.typeArguments, attributes(node.attributes))
|
|
1834
|
+
if (ts.isCallExpression(node) && ts.isIdentifier(node.expression) && params.has(node.expression.text) && !isShadowedIdentifier(node.expression, sourceFile)) {
|
|
1835
|
+
if (node.questionDotToken || node.arguments.length || (node.typeArguments?.length ?? 0) > 1) throw sourceNodeError(node, sourceFile, "React Router useParams must be called directly without runtime arguments and with at most one type argument")
|
|
1836
|
+
return node
|
|
1837
|
+
}
|
|
1832
1838
|
if (ts.isIdentifier(node) && links.has(node.text) && isReferenceIdentifier(node) && !isShadowedIdentifier(node, sourceFile)) throw sourceNodeError(node, sourceFile, "React Router Link imports may only be used as direct JSX elements")
|
|
1839
|
+
if (ts.isIdentifier(node) && params.has(node.text) && isReferenceIdentifier(node) && !isShadowedIdentifier(node, sourceFile)) throw sourceNodeError(node, sourceFile, "React Router useParams imports may only be called directly")
|
|
1833
1840
|
if (ts.isImportDeclaration(node) && ts.isStringLiteral(node.moduleSpecifier) && node.moduleSpecifier.text === "react-router-dom") {
|
|
1834
1841
|
const clause = node.importClause
|
|
1835
1842
|
if (!clause || clause.isTypeOnly) return node
|
|
1836
1843
|
const bindings = clause.namedBindings
|
|
1837
1844
|
if (!bindings || !ts.isNamedImports(bindings)) return node
|
|
1838
|
-
const elements = bindings.elements.filter(entry => entry.isTypeOnly || (entry.propertyName ?? entry.name).text
|
|
1845
|
+
const elements = bindings.elements.filter(entry => entry.isTypeOnly || !["Link", "useParams"].includes((entry.propertyName ?? entry.name).text))
|
|
1839
1846
|
if (!elements.length) return undefined
|
|
1840
1847
|
return factory.updateImportDeclaration(node, node.modifiers, factory.updateImportClause(clause, clause.isTypeOnly, undefined, factory.updateNamedImports(bindings, elements)), node.moduleSpecifier, node.attributes)
|
|
1841
1848
|
}
|
|
1842
1849
|
return ts.visitEachChild(node, visitor, context)
|
|
1843
1850
|
}
|
|
1844
|
-
|
|
1851
|
+
const normalized = ts.visitNode(sourceFile, visitor)
|
|
1852
|
+
if (!params.size) return normalized
|
|
1853
|
+
const declaration = factory.createImportDeclaration(undefined, factory.createImportClause(false, undefined, factory.createNamedImports([...params].map(name => factory.createImportSpecifier(false, name === "useParams" ? undefined : factory.createIdentifier("useParams"), factory.createIdentifier(name))))), factory.createStringLiteral("@kudzujs/core"))
|
|
1854
|
+
const statements = [...normalized.statements]
|
|
1855
|
+
statements.splice(statements.findLastIndex(statement => ts.isImportDeclaration(statement)) + 1, 0, declaration)
|
|
1856
|
+
return factory.updateSourceFile(normalized, statements)
|
|
1845
1857
|
}
|
|
1846
1858
|
|
|
1847
1859
|
function normalizeClsxSyntax(sourceFile, factory, context) {
|