@kudzujs/core 0.6.28 → 0.7.0
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 +19 -1
- package/RELEASES.md +44 -0
- package/framework/README.md +2 -0
- package/framework/build.mjs +20 -1
- package/framework/core.d.ts +4 -0
- package/framework/core.mjs +3 -0
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -8,7 +8,9 @@ HTML-first TSX framework with synchronous state semantics and no virtual DOM.
|
|
|
8
8
|
|
|
9
9
|
Kudzu is designed so ordinary common React-shaped TSX can migrate with minimal source restructuring. It keeps familiar function components, props, children, collection rendering, conditions, event handlers, `useState`, reduced `useReducer`, refs, and effects, preferring compiler specialization over imperative DOM rewrites. This is a general migration model, not compatibility for one application. Static components compile to HTML; interactions compile to direct DOM capabilities and external ESM only where used.
|
|
10
10
|
|
|
11
|
-
> Experimental `0.
|
|
11
|
+
> Experimental `0.7.x`: the compiler API and supported TSX surface may change.
|
|
12
|
+
|
|
13
|
+
**0.7.0:** React-source migration preview. Supported conventional `react` imports now compile to static HTML and capability-specific ESM without React, a VDOM, or hydration. See [release notes](./RELEASES.md#070---react-source-migration-preview).
|
|
12
14
|
|
|
13
15
|
Documentation: [kudzujs.cloud/docs](https://kudzujs.cloud/docs)
|
|
14
16
|
|
|
@@ -58,6 +60,22 @@ Configure TypeScript:
|
|
|
58
60
|
|
|
59
61
|
Make sure application TSX files are included by this `tsconfig.json`. Files outside its `include` may fall into an editor-inferred React project and incorrectly report a missing `react/jsx-runtime` or React event-type errors.
|
|
60
62
|
|
|
63
|
+
Existing React migration source may retain conventional imports while components are moved under `src`:
|
|
64
|
+
|
|
65
|
+
```tsx
|
|
66
|
+
import React, { useState } from "react"
|
|
67
|
+
|
|
68
|
+
export default function Header() {
|
|
69
|
+
const [open, setOpen] = useState(false)
|
|
70
|
+
return <React.Fragment>
|
|
71
|
+
<button onClick={() => setOpen(!open)}>{open ? "Close" : "Menu"}</button>
|
|
72
|
+
{open && <nav>Navigation</nav>}
|
|
73
|
+
</React.Fragment>
|
|
74
|
+
}
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Kudzu rewrites supported React imports to its compile-time APIs before evaluating the module; neither the React package nor a compatibility runtime enters the deploy output. Named `useState`, `useReducer`, `useEffect`, `useRef`, `createContext`, and `useContext` imports must retain those names. Default and namespace React imports currently support `Fragment`; named `Fragment` also works. Aliased hooks, `React.useState`, `memo`, `useMemo`, `useCallback`, React classes, and side-effect or dynamic React imports remain unsupported. A static route using these import forms still emits zero JavaScript.
|
|
78
|
+
|
|
61
79
|
Create `src/pages/index.tsx`:
|
|
62
80
|
|
|
63
81
|
```tsx
|
package/RELEASES.md
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# Kudzu Releases
|
|
2
|
+
|
|
3
|
+
## 0.7.0 - React-source migration preview
|
|
4
|
+
|
|
5
|
+
Kudzu 0.7.0 begins the migration track for ordinary React-shaped landing pages. Existing source may retain conventional supported imports from `react`; Kudzu rewrites those imports to compile-time APIs, pre-renders complete HTML, and emits only the route capabilities that are actually used. React, a virtual DOM, hydration, and a browser component tree are never emitted or executed.
|
|
6
|
+
|
|
7
|
+
```tsx
|
|
8
|
+
import React, { useState } from "react"
|
|
9
|
+
|
|
10
|
+
export default function Header() {
|
|
11
|
+
const [open, setOpen] = useState(false)
|
|
12
|
+
|
|
13
|
+
return <React.Fragment>
|
|
14
|
+
<button onClick={() => setOpen(!open)}>{open ? "Close" : "Menu"}</button>
|
|
15
|
+
{open && <nav>Navigation</nav>}
|
|
16
|
+
</React.Fragment>
|
|
17
|
+
}
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
### New in 0.7.0
|
|
21
|
+
|
|
22
|
+
- Conventional unaliased named imports of supported hooks from `react` compile through Kudzu without loading React.
|
|
23
|
+
- Default, namespace, and named `Fragment` imports are accepted for migration source.
|
|
24
|
+
- Relative function components, props, children, conditions, attributes, text, and event handlers keep their familiar TSX shape.
|
|
25
|
+
- Static routes using the accepted React import forms still ship zero JavaScript.
|
|
26
|
+
- Interactive routes ship direct DOM capabilities only; the landing-page acceptance fixture adds state, text, attribute, condition, and menu-handler capabilities.
|
|
27
|
+
- Emitted modules are checked for surviving runtime React references, and side-effect React imports fail with a source location.
|
|
28
|
+
- Keyed collections now support analyzable `filter`, direct-property `flatMap`, `Array.from`, positional keys, recursively deep sibling child maps, nested conditions, latest-item handlers, multiple serializable row states, effects, and `null` object refs.
|
|
29
|
+
|
|
30
|
+
### Current boundary
|
|
31
|
+
|
|
32
|
+
This is source migration support, not a React compatibility runtime. Aliased hooks, member hook calls such as `React.useState`, `memo`, `useMemo`, `useCallback`, React classes, React Router, Next-specific components, React UI packages, side-effect imports, and dynamic React imports remain unsupported. Migrate a real route, reduce the first unsupported pattern to a fixture, and extend the compiler one proven blocker at a time.
|
|
33
|
+
|
|
34
|
+
### Measured fixture
|
|
35
|
+
|
|
36
|
+
The two-route landing fixture retains React imports across relative components. Its static route has no script, while its interactive mobile-menu route emits 10,245 B raw / 5,030 B aggregate gzip JavaScript across seven capability files. Seven clean builds after one warm-up measured a 310.0 ms median on the development machine described in `MIGRATION_ROADMAP.md`.
|
|
37
|
+
|
|
38
|
+
### Upgrade
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
npm install @kudzujs/core@^0.7.0
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
New Kudzu source should continue importing APIs from `@kudzujs/core`. Retaining `react` imports is intended for migration input where minimizing source edits matters.
|
package/framework/README.md
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
Kudzu specializes ordinary common React-shaped TSX so migrations need minimal source restructuring. Declarative components, collection pipelines, conditions, hooks, and handlers should be lowered at build time rather than replaced with application-owned imperative DOM code. This principle applies across migrations and is not Stay-specific; it does not imply a React package, VDOM, hydration, or ecosystem runtime.
|
|
4
4
|
|
|
5
|
+
Migration source may retain conventional `react` imports for unaliased supported named hooks and default, namespace, or named `Fragment`. `build.mjs` rewrites those module references to `@kudzujs/core` before build-time evaluation. Static routes remain JavaScript-free and emitted modules are checked for surviving React imports. Member hook calls and aliased hooks are deliberately not inferred yet.
|
|
6
|
+
|
|
5
7
|
- `build.mjs`: TSX compilation, static, `getStaticPaths`, and runtime-fallback routes, base paths, CSS collection, post-build hooks, behavior extraction, static HTML output, and the development server.
|
|
6
8
|
- `core.mjs`: server-side JSX rendering, state slots, context providers, behavior metadata, and serializable capture validation.
|
|
7
9
|
- `jsx-runtime.mjs`: automatic JSX runtime used by TypeScript.
|
package/framework/build.mjs
CHANGED
|
@@ -1694,6 +1694,7 @@ async function compile(file, sourceFiles, sourceIndex, base, workerReferences) {
|
|
|
1694
1694
|
if (errors.length) {
|
|
1695
1695
|
throw new Error(errors.map(error => ts.flattenDiagnosticMessageText(error.messageText, "\n")).join("\n"))
|
|
1696
1696
|
}
|
|
1697
|
+
if (hasReactModuleReference(result.outputText, file)) throw new Error(`${relative(root, file)} Runtime React module references are not supported`)
|
|
1697
1698
|
|
|
1698
1699
|
const output = compiledPath(file)
|
|
1699
1700
|
await mkdir(resolve(output, ".."), { recursive: true })
|
|
@@ -1716,6 +1717,18 @@ async function compile(file, sourceFiles, sourceIndex, base, workerReferences) {
|
|
|
1716
1717
|
return { path: handlerPath, code: moduleResult.outputText, hasNativeHandlers: nativeHandlers.length > 0, hasEffects: effectHandlers.length > 0, clientImports: [...clientImports] }
|
|
1717
1718
|
}
|
|
1718
1719
|
|
|
1720
|
+
function hasReactModuleReference(source, file) {
|
|
1721
|
+
const sourceFile = ts.createSourceFile(file, source, ts.ScriptTarget.ES2022, true, ts.ScriptKind.JS)
|
|
1722
|
+
let found = false
|
|
1723
|
+
const visit = node => {
|
|
1724
|
+
if ((ts.isImportDeclaration(node) || ts.isExportDeclaration(node)) && node.moduleSpecifier && ts.isStringLiteral(node.moduleSpecifier) && node.moduleSpecifier.text === "react") found = true
|
|
1725
|
+
if (ts.isCallExpression(node) && (node.expression.kind === ts.SyntaxKind.ImportKeyword || ts.isIdentifier(node.expression) && node.expression.text === "require") && ts.isStringLiteral(node.arguments[0]) && node.arguments[0].text === "react") found = true
|
|
1726
|
+
if (!found) ts.forEachChild(node, visit)
|
|
1727
|
+
}
|
|
1728
|
+
visit(sourceFile)
|
|
1729
|
+
return found
|
|
1730
|
+
}
|
|
1731
|
+
|
|
1719
1732
|
function createKudzuTransformer(nativeHandlers, effectHandlers, reactiveBindings, listExpressions, handlerUrl, file, sourceFiles, sourceIndex, clientImports, workerReferences) {
|
|
1720
1733
|
return context => sourceFile => {
|
|
1721
1734
|
const factory = context.factory
|
|
@@ -1724,7 +1737,7 @@ function createKudzuTransformer(nativeHandlers, effectHandlers, reactiveBindings
|
|
|
1724
1737
|
ts.setParentRecursive(sourceFile, false)
|
|
1725
1738
|
rejectOrdinaryWorkerImports(sourceFile, file, sourceFiles)
|
|
1726
1739
|
const importBindings = clientImportBindings(sourceFile, file, sourceFiles)
|
|
1727
|
-
const hasUseEffectImport = sourceFile.statements.some(statement => ts.isImportDeclaration(statement) &&
|
|
1740
|
+
const hasUseEffectImport = sourceFile.statements.some(statement => ts.isImportDeclaration(statement) && ["@kudzujs/core", "react"].includes(statement.moduleSpecifier.text) && statement.importClause?.namedBindings && ts.isNamedImports(statement.importClause.namedBindings) && statement.importClause.namedBindings.elements.some(entry => !entry.propertyName && entry.name.text === "useEffect"))
|
|
1728
1741
|
const importedSources = new Map()
|
|
1729
1742
|
const importedSource = target => {
|
|
1730
1743
|
let imported = importedSources.get(target)
|
|
@@ -2208,6 +2221,12 @@ function createKudzuTransformer(nativeHandlers, effectHandlers, reactiveBindings
|
|
|
2208
2221
|
fail(node, "Stylesheets must be placed under src/ or declared in kudzu.config styles so Kudzu can emit them in <head>")
|
|
2209
2222
|
}
|
|
2210
2223
|
|
|
2224
|
+
if (ts.isImportDeclaration(node) && ts.isStringLiteral(node.moduleSpecifier) && node.moduleSpecifier.text === "react") {
|
|
2225
|
+
if (!node.importClause) fail(node, "Side-effect React imports are not supported because Kudzu does not load the React runtime")
|
|
2226
|
+
if (node.importClause.isTypeOnly) return node
|
|
2227
|
+
return factory.updateImportDeclaration(node, node.modifiers, node.importClause, factory.createStringLiteral("@kudzujs/core"), node.attributes)
|
|
2228
|
+
}
|
|
2229
|
+
|
|
2211
2230
|
if (ts.isImportDeclaration(node) && ts.isStringLiteral(node.moduleSpecifier) && node.moduleSpecifier.text.startsWith(".")) {
|
|
2212
2231
|
const target = resolveSourceImport(file, node.moduleSpecifier.text, sourceFiles)
|
|
2213
2232
|
return factory.updateImportDeclaration(node, node.modifiers, node.importClause, factory.createStringLiteral(relativeModulePath(compiledPath(file), compiledPath(target))), node.attributes)
|
package/framework/core.d.ts
CHANGED
|
@@ -3,6 +3,7 @@ export type Reducer<State, Action> = (state: State, action: Action) => State
|
|
|
3
3
|
export type Dispatch<Action> = (action: Action) => void
|
|
4
4
|
export type EffectCleanup = () => void | Promise<void>
|
|
5
5
|
export type EffectDependency = string | number | boolean | null
|
|
6
|
+
export const Fragment: unique symbol
|
|
6
7
|
|
|
7
8
|
export function useState<T>(initialValue: T): [T, StateSetter<T>]
|
|
8
9
|
export function useReducer<State, Action>(reducer: Reducer<State, Action>, initialValue: State): [State, Dispatch<Action>]
|
|
@@ -22,6 +23,9 @@ export interface Context<T> {
|
|
|
22
23
|
export function createContext<T>(defaultValue: T): Context<T>
|
|
23
24
|
export function useContext<T>(context: Context<T>): T
|
|
24
25
|
|
|
26
|
+
declare const React: { Fragment: typeof Fragment }
|
|
27
|
+
export default React
|
|
28
|
+
|
|
25
29
|
export function behavior(commands: Array<["add" | "set" | "log", unknown, unknown]>): unknown
|
|
26
30
|
export function nativeBehavior(module: string, handler: string, states: Array<[string, unknown]>, scope: Array<[string, unknown]>): unknown
|
|
27
31
|
export function binding(value: unknown, module: string, handler: string, states: Array<[string, unknown]>, scope: Array<[string, unknown]>): unknown
|
package/framework/core.mjs
CHANGED
|
@@ -20,6 +20,7 @@ const contextMarker = Symbol("kudzu.context")
|
|
|
20
20
|
const contextProviderMarker = Symbol("kudzu.contextProvider")
|
|
21
21
|
const routeScopeMarker = Symbol("kudzu.routeScope")
|
|
22
22
|
const noSelectValue = Symbol("kudzu.no-select-value")
|
|
23
|
+
export const Fragment = Symbol.for("kudzu.fragment")
|
|
23
24
|
const svgAttributeAliases = {
|
|
24
25
|
clipRule: "clip-rule",
|
|
25
26
|
colorInterpolation: "color-interpolation",
|
|
@@ -170,6 +171,8 @@ export function useContext(context) {
|
|
|
170
171
|
return context.defaultValue
|
|
171
172
|
}
|
|
172
173
|
|
|
174
|
+
export default { Fragment }
|
|
175
|
+
|
|
173
176
|
export function behavior(commands) {
|
|
174
177
|
return {
|
|
175
178
|
[behaviorMarker]: true,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kudzujs/core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"description": "HTML-first TSX framework with synchronous state semantics and no virtual DOM",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -26,6 +26,7 @@
|
|
|
26
26
|
"framework/",
|
|
27
27
|
"GOAL_A.md",
|
|
28
28
|
"GOAL_B.md",
|
|
29
|
+
"RELEASES.md",
|
|
29
30
|
"README.md",
|
|
30
31
|
"LICENSE"
|
|
31
32
|
],
|