@kudzujs/core 0.7.11 → 0.7.13
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/GOAL_B.md +6 -0
- package/README.md +3 -2
- package/RELEASES.md +48 -1
- package/framework/README.md +3 -7
- package/framework/build.mjs +23 -2
- package/framework/core.d.ts +1 -0
- package/framework/core.mjs +8 -2
- package/package.json +2 -1
package/GOAL_B.md
CHANGED
|
@@ -102,6 +102,12 @@ The focused fixture emitted a 907 B raw / 477 B gzip Worker graph and an 11,388
|
|
|
102
102
|
|
|
103
103
|
These numbers are dated conformance evidence. They do not define a dashboard product target.
|
|
104
104
|
|
|
105
|
+
## Reproducible 0.7.12 Rerun
|
|
106
|
+
|
|
107
|
+
At commit `05e5cc2` on Apple M3 / macOS 26.5.2 / Node 25.6.1, `npm run benchmark` measured clean build times of 404.2, 401.3, 408.2, 404.4, 399.9, 408.8, and 402.0 ms after one warm-up, for a 404.2 ms median. The current Worker graph is 907 B raw / 475 B gzip; the complete dashboard window graph is 11,960 B raw / 5,365 B aggregate gzip.
|
|
108
|
+
|
|
109
|
+
Chrome 150.0.7871.187 passed the tracked throughput, cadence, stale-write, bounded-history, and 30-cycle start/termination checks through the focused Worker browser test. These current values differ from the historical completion snapshot because shared window capabilities and the local toolchain changed; the historical values above remain release provenance, not current artifact claims.
|
|
110
|
+
|
|
105
111
|
## Non-Goals
|
|
106
112
|
|
|
107
113
|
- Device, alarm, tenant, transport, chart, map, or widget product features.
|
package/README.md
CHANGED
|
@@ -10,13 +10,14 @@ 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.13 - Deterministic useId.** Ordinary React-shaped components can retain top-level `useId()` calls while Kudzu emits stable static HTML IDs and no browser capability. Read the [release notes](./RELEASES.md#0713---deterministic-useid) or open the [release page](https://kudzujs.cloud/releases/0.7.13).
|
|
14
14
|
|
|
15
15
|
- [Documentation](https://kudzujs.cloud/docs)
|
|
16
16
|
- [Installation guide](https://kudzujs.cloud/docs#install)
|
|
17
17
|
- [Components and migration support](https://kudzujs.cloud/docs#components)
|
|
18
18
|
- [Current limits](https://kudzujs.cloud/docs#limits)
|
|
19
19
|
- [Benchmarks](https://kudzujs.cloud/docs#benchmarks)
|
|
20
|
+
- [Raw performance records](./PERFORMANCE.md)
|
|
20
21
|
- [React migration roadmap](./MIGRATION_ROADMAP.md)
|
|
21
22
|
- [Release history](./RELEASES.md)
|
|
22
23
|
|
|
@@ -77,7 +78,7 @@ ordinary React-shaped TSX
|
|
|
77
78
|
```
|
|
78
79
|
|
|
79
80
|
- Function components execute at build time and do not survive as browser components.
|
|
80
|
-
- `useState` and reduced `useReducer` compile to synchronous logical state and batched direct DOM writes.
|
|
81
|
+
- `useState` and reduced `useReducer` compile to synchronous logical state and batched direct DOM writes; top-level `useId` becomes deterministic static HTML without browser JavaScript.
|
|
81
82
|
- Conditions, keyed collections, attributes, events, refs, effects, and supported component boundaries compile to route-specific capabilities.
|
|
82
83
|
- Build-known data and routes become complete HTML through async components and `getStaticPaths()`.
|
|
83
84
|
- Native document navigation is the default; static routes do not load a client runtime.
|
package/RELEASES.md
CHANGED
|
@@ -1,5 +1,52 @@
|
|
|
1
1
|
# Kudzu Releases
|
|
2
2
|
|
|
3
|
+
## 0.7.13 - Deterministic useId
|
|
4
|
+
|
|
5
|
+
Kudzu 0.7.13 preserves conventional top-level React `useId()` authoring while emitting deterministic static HTML IDs with no browser runtime.
|
|
6
|
+
|
|
7
|
+
### New in 0.7.13
|
|
8
|
+
|
|
9
|
+
- `useId` may be imported directly or with an alias from `react`, or called as a direct default/namespace React member.
|
|
10
|
+
- Each top-level `const id = useId()` receives a stable build-time ID that can be reused by `id`, `htmlFor`, and ARIA ID-reference attributes.
|
|
11
|
+
- Repeated component calls receive distinct IDs, while unchanged clean builds reproduce the same output.
|
|
12
|
+
- Shared layouts and route content use separate ID namespaces during complete-document and enhanced navigation builds.
|
|
13
|
+
- Static routes remain JavaScript-free; no hook dispatcher, hydration metadata, or browser component function is emitted.
|
|
14
|
+
- Calls with arguments, non-top-level forms, and keyed-row ownership fail with source-located diagnostics.
|
|
15
|
+
|
|
16
|
+
### Boundary
|
|
17
|
+
|
|
18
|
+
`useId()` accepts no arguments and must initialize one top-level `const` identifier in an ordinary component. Keyed rows remain unsupported because cloned row templates require key-scoped rewriting of `id`, `for`, ARIA IDREF, and fragment-reference attributes; Kudzu rejects the unsafe shape instead of emitting duplicate IDs.
|
|
19
|
+
|
|
20
|
+
### Upgrade
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
npm install @kudzujs/core@^0.7.13
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## 0.7.12 - Exported row reuse
|
|
27
|
+
|
|
28
|
+
Kudzu 0.7.12 lets directly exported same-file row components remain ordinary reusable source while every supported call still specializes to intrinsic DOM.
|
|
29
|
+
|
|
30
|
+
### New in 0.7.12
|
|
31
|
+
|
|
32
|
+
- Same-file keyed rows may use direct `export function Row` declarations.
|
|
33
|
+
- Function-valued `export const Row = ...` declarations receive the same specialization.
|
|
34
|
+
- One exported row may be reused across multiple keyed maps and ordinary static JSX sites.
|
|
35
|
+
- Every discovered JSX call specializes independently; the exported declaration remains build-only and no component function or name enters browser assets.
|
|
36
|
+
- Existing keyed identity, event handlers, conditions, styles, static callbacks, reorder, and removal behavior remain unchanged.
|
|
37
|
+
- Non-JSX references and dynamic component aliases continue to fail with a source-located `may only be referenced as JSX` diagnostic.
|
|
38
|
+
- Export-list and default-export aliases, exported state-backed wrappers, and exported reducer-dispatch components remain outside this direct-row fixture.
|
|
39
|
+
|
|
40
|
+
### Boundary
|
|
41
|
+
|
|
42
|
+
This release supports direct export modifiers on same-file keyed row function declarations and function-valued constants. Every local reference must remain a JSX call, and effectful row components may still only run at keyed sites. Imported rows retain their existing default, named, aliased, and direct named re-export support.
|
|
43
|
+
|
|
44
|
+
### Upgrade
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
npm install @kudzujs/core@^0.7.12
|
|
48
|
+
```
|
|
49
|
+
|
|
3
50
|
## 0.7.11 - Serializable defaults and rest props
|
|
4
51
|
|
|
5
52
|
Kudzu 0.7.11 preserves common non-primitive defaults and direct rest forwarding across existing compiler-specialized component boundaries.
|
|
@@ -295,7 +342,7 @@ This is source migration support, not a React compatibility runtime. Aliased hoo
|
|
|
295
342
|
|
|
296
343
|
### Measured fixture
|
|
297
344
|
|
|
298
|
-
The two-route landing fixture retains React imports across relative components. Its static route has no script, while its interactive mobile-menu route
|
|
345
|
+
The two-route landing fixture retains React imports across relative components. Its static route has no script, while its interactive mobile-menu route emitted 10,245 B raw / 5,030 B aggregate gzip JavaScript across seven capability files in the 0.7.0 release snapshot. Seven clean builds after one warm-up measured a 310.0 ms median in that historical development environment; the original runner and raw array are not tracked in the current repository.
|
|
299
346
|
|
|
300
347
|
### Upgrade
|
|
301
348
|
|
package/framework/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
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 supported named or aliased hooks, direct members such as `React.useState`, same-file `memo`, inline `useCallback`, direct-state expression or analyzable collection-pipeline `useMemo`, and default, namespace, or named `Fragment`. Collection memos may start from local array state or a named relative import of an exported JSON-safe `const` array and may read direct local state declared in their dependency array. `build.mjs` canonicalizes those forms and rewrites module references to `@kudzujs/core` before build-time evaluation. Memo wrappers are erased or inlined into existing bindings and keyed-list selectors because no browser component rerender or memo cache exists. Static routes remain JavaScript-free and emitted modules are checked for surviving React imports.
|
|
5
|
+
Migration source may retain conventional `react` imports for supported named or aliased hooks, direct members such as `React.useState`, same-file `memo`, inline `useCallback`, direct-state expression or analyzable collection-pipeline `useMemo`, top-level `const` identifiers initialized by `useId()`, and default, namespace, or named `Fragment`. `useId()` becomes a deterministic build-time HTML ID and emits no browser capability; keyed rows reject it because cloned row templates cannot safely duplicate HTML IDs. Collection memos may start from local array state or a named relative import of an exported JSON-safe `const` array and may read direct local state declared in their dependency array. `build.mjs` canonicalizes those forms and rewrites module references to `@kudzujs/core` before build-time evaluation. Memo wrappers are erased or inlined into existing bindings and keyed-list selectors because no browser component rerender or memo cache exists. Static routes remain JavaScript-free and emitted modules are checked for surviving React imports.
|
|
6
6
|
|
|
7
7
|
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.
|
|
8
8
|
|
|
@@ -31,7 +31,7 @@ Page `metadata` can emit description, canonical, favicon, manifest, Open Graph,
|
|
|
31
31
|
|
|
32
32
|
Inline SVG rendering normalizes an explicit set of common React presentation aliases before static serialization and binding descriptor creation. Reactive aliases therefore use the existing generic `setAttribute` path; static SVG adds no JavaScript and reactive SVG adds no SVG-specific runtime.
|
|
33
33
|
|
|
34
|
-
Same-file and relative-imported component chains receiving a direct local-state array or keyed item are recursively specialized to intrinsic JSX before keyed-list analysis, so their component functions are not retained in the browser. Missing destructured props use directly serializable primitive, plain-object, or array literal defaults during specialization. One final identifier rest binding may be expanded exactly once at the direct intrinsic root. Rows may own multiple direct-property child maps recursively, nested conditions, latest-item handlers, multiple directly serializable state slots, effects, and `null`-initialized object refs. Structural list sites and ancestor key paths scope hooks across updates and reorder and release them on removal. Handler modules are emitted only when a rendered descriptor references them. Direct JSON-safe primitive keyed-item dependencies subscribe each row record to its owning list commit and compare selected fields after `list-runtime.js` synchronously refreshes the row marker. Only changed rows rerun with the complete latest item; reorder compares equal and unrelated fields do nothing. Builds without item dependencies emit no item reader or list-state subscription code.
|
|
34
|
+
Same-file, directly exported same-file, and relative-imported component chains receiving a direct local-state array or keyed item are recursively specialized to intrinsic JSX before keyed-list analysis, so their component functions are not retained in the browser. A directly exported row may be reused across static and keyed JSX sites; export-list/default aliases and non-JSX references remain rejected. Missing destructured props use directly serializable primitive, plain-object, or array literal defaults during specialization. One final identifier rest binding may be expanded exactly once at the direct intrinsic root. Rows may own multiple direct-property child maps recursively, nested conditions, latest-item handlers, multiple directly serializable state slots, effects, and `null`-initialized object refs. Structural list sites and ancestor key paths scope hooks across updates and reorder and release them on removal. Handler modules are emitted only when a rendered descriptor references them. Direct JSON-safe primitive keyed-item dependencies subscribe each row record to its owning list commit and compare selected fields after `list-runtime.js` synchronously refreshes the row marker. Only changed rows rerun with the complete latest item; reorder compares equal and unrelated fields do nothing. Builds without item dependencies emit no item reader or list-state subscription code.
|
|
35
35
|
|
|
36
36
|
Rendered collection selectors compile one-use aliases and inline `(item)` or `(item, index)` pipelines over local array state or supported static named imports. Supported selectors are pure `filter` with direct local-state reads, direct-property `flatMap`, and `Array.from` before a final keyed `map`; dependency commits re-evaluate the selector against the immutable build-time collection while field keys retain item identity and `key={index}` retains positional identity. Compiler-owned static filters over structural keyed rows validate source references and keys once, retain removed rows as detached prototypes, clone fresh restoration nodes, and insert only new runs without moving retained DOM. Specialized collection wrappers and keyed rows inline direct object-literal or calling-component `const` object prop spreads in source order and forward JSX children into intrinsic output. This route-specific path is compiled out elsewhere. Compiler-owned collection state is excluded from development snapshot restoration. Dynamic/computed prop spreads, arbitrary callbacks, mutation, asynchronous selectors, imported callback functions, prototype-sensitive reads, lazy/dynamic row state initializers, non-`null` or callback refs, and recursive component cycles fail during compilation.
|
|
37
37
|
|
|
@@ -39,8 +39,4 @@ The reduced `useReducer` form reuses ordinary state slots and React's pure reduc
|
|
|
39
39
|
|
|
40
40
|
`kudzu.config` may opt one emitted shared-layout group into same-document navigation with legacy `navigation: { routes: ["/product", "/items/[id]"] }`, or multiple groups with `navigation: { groups: [{ routes: [...] }, { routes: [...] }] }`. The forms are mutually exclusive. Identities are globally unique emitted exact paths or `runtimeParams` patterns; each group uses one page-exported layout function identity. Runtime records securely match concrete pathnames under `base`, and their cache-safe parameter initializer runs before route DOM/effects mount on every transition. Each group receives a deterministic route-hashed asset specialized to only its records, pattern decoder, and effect/parameter lifecycle needs. Cross-group and ungrouped anchors remain native and are not prefetched; overlapping path domains across groups fail the build. Route effect entries export cache-safe layout and route mount functions: layout effects, including conditional/keyed DOM-owned effects, persist for the group session; route effects receive a fresh owner registry after each route insertion; and non-persisted page disposal cleans route before layout. Direct primitive state, runtime parameter, and keyed-item property dependencies and cleanup are supported. Fragment payloads and coordinated View Transitions are not implemented.
|
|
41
41
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
In matched state-only and item-property keyed-row builds, the minified route effect entry changes from 3,829 B raw/1,667 B gzip to 4,392 B raw/1,823 B gzip, the shared runtime from 1,291 B raw/671 B gzip to 1,503 B raw/751 B gzip, and the list runtime from 6,606 B raw/2,474 B gzip to 6,652 B raw/2,493 B gzip. The complete targeted-notification capability costs +821 B raw/+255 B gzip and remains absent from builds without item dependencies. Seven clean builds of the expanded three-route fixture measured 420-440 ms with a 430 ms median; this records current build cost rather than claiming a cross-version speed change.
|
|
45
|
-
|
|
46
|
-
The matched 1,000-row cross-framework effect fixture measured Kudzu at 8,264 B initial JavaScript gzip, 426 ms build, 3.6 ms selected-row cleanup/update/setup, 2.4 ms unrelated-field update, and 8.8 ms reorder. React CSR measured 60,921 B, 1,198 ms, 9.8 ms, 5.9 ms, and 16.8 ms respectively; Vue measured 5.8, 2.4, and 10.5 ms for the browser operations, and Svelte measured 5.7, 4.1, and 58.1 ms. Browser operations begin only after all targets have 1,000 rows and effects ready. Kudzu emits those rows in HTML while the framework CSR fixtures begin from empty shells, so JavaScript, output, and build values are not architecture-equivalent comparisons. Targeted changed-root notification avoids an extra O(n) effect-record scan; list validation, serialization, and reconciliation remain O(n).
|
|
42
|
+
Cross-framework performance tables are historical snapshots from an excluded local benchmark workspace; the current checkout does not contain their competitor fixtures, runners, or raw arrays. The maintained provenance warning and tables live in the web docs. `npm run benchmark` is the tracked reproducible Worker fixture measurement, while the focused Chrome Worker test verifies throughput, cadence, bounded history, stale-write isolation, and route lifecycle behavior.
|
package/framework/build.mjs
CHANGED
|
@@ -1882,7 +1882,7 @@ function normalizeZustandMigrationSyntax(sourceFile, factory, context) {
|
|
|
1882
1882
|
}
|
|
1883
1883
|
|
|
1884
1884
|
function normalizeReactMigrationSyntax(sourceFile, factory, context, importedCollections = new Set()) {
|
|
1885
|
-
const supported = new Set(["createContext", "useContext", "useEffect", "useReducer", "useRef", "useState"])
|
|
1885
|
+
const supported = new Set(["createContext", "useContext", "useEffect", "useId", "useReducer", "useRef", "useState"])
|
|
1886
1886
|
const erased = new Set(["memo", "useCallback", "useMemo"])
|
|
1887
1887
|
const aliases = new Map()
|
|
1888
1888
|
const reactObjects = new Set()
|
|
@@ -2074,6 +2074,24 @@ function normalizeReactMigrationSyntax(sourceFile, factory, context, importedCol
|
|
|
2074
2074
|
return normalized
|
|
2075
2075
|
}
|
|
2076
2076
|
|
|
2077
|
+
function validateUseIdSyntax(sourceFile) {
|
|
2078
|
+
const imported = 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 === "useId"))
|
|
2079
|
+
if (!imported) return
|
|
2080
|
+
const visit = node => {
|
|
2081
|
+
if (ts.isCallExpression(node) && ts.isIdentifier(node.expression) && node.expression.text === "useId" && !isShadowedIdentifier(node.expression, sourceFile)) {
|
|
2082
|
+
if (node.arguments.length) throw sourceNodeError(node, sourceFile, "useId() does not accept arguments")
|
|
2083
|
+
const declaration = node.parent
|
|
2084
|
+
const statement = declaration?.parent?.parent
|
|
2085
|
+
const owner = nearestFunction(node)
|
|
2086
|
+
if (!ts.isVariableDeclaration(declaration) || declaration.initializer !== node || !ts.isIdentifier(declaration.name) || !statement || !ts.isVariableStatement(statement) || (statement.declarationList.flags & ts.NodeFlags.Const) === 0 || !owner || !ts.isBlock(owner.body) || statement.parent !== owner.body) {
|
|
2087
|
+
throw sourceNodeError(node, sourceFile, "useId() must be assigned to one top-level const identifier in a component")
|
|
2088
|
+
}
|
|
2089
|
+
}
|
|
2090
|
+
ts.forEachChild(node, visit)
|
|
2091
|
+
}
|
|
2092
|
+
visit(sourceFile)
|
|
2093
|
+
}
|
|
2094
|
+
|
|
2077
2095
|
function importDeclarationNames(statement) {
|
|
2078
2096
|
const names = []
|
|
2079
2097
|
if (statement.importClause?.name) names.push(statement.importClause.name.text)
|
|
@@ -2158,6 +2176,7 @@ function createKudzuTransformer(nativeHandlers, effectHandlers, reactiveBindings
|
|
|
2158
2176
|
ts.setParentRecursive(sourceFile, false)
|
|
2159
2177
|
sourceFile = normalizeReactMigrationSyntax(sourceFile, factory, context, importedCollections)
|
|
2160
2178
|
ts.setParentRecursive(sourceFile, false)
|
|
2179
|
+
validateUseIdSyntax(sourceFile)
|
|
2161
2180
|
sourceFile = normalizeZustandMigrationSyntax(sourceFile, factory, context)
|
|
2162
2181
|
ts.setParentRecursive(sourceFile, false)
|
|
2163
2182
|
sourceFile = normalizeRenderControlFlow(sourceFile, factory, context)
|
|
@@ -2173,6 +2192,7 @@ function createKudzuTransformer(nativeHandlers, effectHandlers, reactiveBindings
|
|
|
2173
2192
|
ts.setParentRecursive(imported, false)
|
|
2174
2193
|
imported = normalizeReactMigrationSyntax(imported, factory, context, importedSerializableCollectionNames(imported, target, sourceFiles, sourceIndex))
|
|
2175
2194
|
ts.setParentRecursive(imported, false)
|
|
2195
|
+
validateUseIdSyntax(imported)
|
|
2176
2196
|
imported = normalizeZustandMigrationSyntax(imported, factory, context)
|
|
2177
2197
|
ts.setParentRecursive(imported, false)
|
|
2178
2198
|
imported = normalizeRenderControlFlow(imported, factory, context)
|
|
@@ -2546,7 +2566,6 @@ function createKudzuTransformer(nativeHandlers, effectHandlers, reactiveBindings
|
|
|
2546
2566
|
const imported = binding.kind === "default" ? "default" : binding.imported
|
|
2547
2567
|
component = { function: resolveComponentExport(binding.target, imported, importedSource, sourceFiles), declaration: undefined }
|
|
2548
2568
|
}
|
|
2549
|
-
if (local && isExportedDeclaration(component.declaration)) fail(component.declaration, `Keyed list component ${name} cannot be exported`)
|
|
2550
2569
|
const declaredCalls = jsxTagUses(sourceFile, name)
|
|
2551
2570
|
if (local && identifierReferenceCount(sourceFile, name) !== declaredCalls.length) fail(component.declaration, `Keyed list component ${name} may only be referenced as JSX`)
|
|
2552
2571
|
const calls = [...new Set([
|
|
@@ -3282,6 +3301,7 @@ function validateKeyedList(parts, sourceFile, listValues, listEventItems, listCo
|
|
|
3282
3301
|
if (!ts.isIdentifier(tag) || tag.text[0] !== tag.text[0].toLowerCase()) fail(node, "Keyed list items must use intrinsic JSX elements")
|
|
3283
3302
|
}
|
|
3284
3303
|
const visit = node => {
|
|
3304
|
+
if (ts.isCallExpression(node) && ts.isIdentifier(node.expression) && node.expression.text === "useId") fail(node, "useId() is not supported in keyed rows")
|
|
3285
3305
|
if (ts.isJsxFragment(node)) fail(node, "Fragments are not supported in keyed lists")
|
|
3286
3306
|
if (ts.isJsxElement(node) || ts.isJsxSelfClosingElement(node)) validateElement(node)
|
|
3287
3307
|
if (node !== root && ts.isCallExpression(node) && ts.isPropertyAccessExpression(node.expression) && node.expression.name.text === "map" && containsJsx(node)) fail(node, nestedDiagnostic)
|
|
@@ -3587,6 +3607,7 @@ function specializeComponentCall(call, component, sourceFile, factory, context,
|
|
|
3587
3607
|
rowRefs.push({ name })
|
|
3588
3608
|
continue
|
|
3589
3609
|
}
|
|
3610
|
+
if (declaration.initializer && ts.isCallExpression(declaration.initializer) && ts.isIdentifier(declaration.initializer.expression) && declaration.initializer.expression.text === "useId") throw sourceNodeError(declaration.initializer, component.getSourceFile(), "useId() is not supported in keyed row components")
|
|
3590
3611
|
if (!ts.isIdentifier(declaration.name) || !declaration.initializer) fail(declaration, `${label} component locals must be initialized identifiers`)
|
|
3591
3612
|
const calculation = substituteClone(declaration.initializer, substitutions, factory, context)
|
|
3592
3613
|
calculations.push({ name: declaration.name.text, expression: calculation })
|
package/framework/core.d.ts
CHANGED
|
@@ -5,6 +5,7 @@ export type EffectCleanup = () => void | Promise<void>
|
|
|
5
5
|
export type EffectDependency = string | number | boolean | null
|
|
6
6
|
export const Fragment: unique symbol
|
|
7
7
|
|
|
8
|
+
export function useId(): string
|
|
8
9
|
export function useState<T>(initialValue: T): [T, StateSetter<T>]
|
|
9
10
|
export function useReducer<State, Action>(reducer: Reducer<State, Action>, initialValue: State): [State, Dispatch<Action>]
|
|
10
11
|
export function useEffect(effect: () => void | EffectCleanup | Promise<void>, dependencies: readonly EffectDependency[]): void
|
package/framework/core.mjs
CHANGED
|
@@ -47,6 +47,12 @@ const svgAttributeAliases = {
|
|
|
47
47
|
|
|
48
48
|
let renderContext
|
|
49
49
|
|
|
50
|
+
export function useId() {
|
|
51
|
+
if (!renderContext) throw new Error("useId() can only run while rendering a Kudzu component")
|
|
52
|
+
if (renderContext.listRoot || renderContext.listRowRoot || renderContext.listTemplate) throw new Error("useId() is not supported in keyed rows")
|
|
53
|
+
return `k-${nextRenderId("i")}`
|
|
54
|
+
}
|
|
55
|
+
|
|
50
56
|
export function useState(initialValue, name) {
|
|
51
57
|
if (!renderContext) {
|
|
52
58
|
throw new Error("useState() can only run while rendering a Kudzu component")
|
|
@@ -406,7 +412,7 @@ function serializeCapture(name, value, seen) {
|
|
|
406
412
|
}
|
|
407
413
|
|
|
408
414
|
export async function renderPage(component, metadata = {}, props = {}, layout) {
|
|
409
|
-
renderContext = { scoped: Boolean(layout), renderScope: layout ? "layout" : "route", counters: { layout: { s: 0, r: 0, c: 0, l: 0, e: 0, p: 0 }, route: { s: 0, r: 0, c: 0, l: 0, e: 0, p: 0 } }, nextState: 0, nextRef: 0, nextCondition: 0, nextList: 0, nextEffect: 0, nextParam: 0, conditionDepth: 0, listDepth: 0, listRoot: undefined, listRowRoot: undefined, listTemplate: false, listInitialMarkers: false, listConditionalBranch: false, listFields: undefined, listEffectOwners: [], listRowStates: [], listRowRefs: [], listRowConditions: [], listRowLists: [], effectOwners: [], contexts: [], stores: new Map(), states: {}, textStates: new Set(), conditionStates: new Set(), events: [], effects: [], bindings: [], textBindings: [], conditions: [], lists: [], handlerModules: new Set(), runtimeParamNames: metadata.runtimeParams, paramEntries: [], params: undefined, hasBehaviors: false, hasNativeBehaviors: false, hasEffects: false, hasParams: false, hasBindings: false, hasLists: false, hasListStyles: false }
|
|
415
|
+
renderContext = { scoped: Boolean(layout), renderScope: layout ? "layout" : "route", counters: { layout: { s: 0, r: 0, c: 0, l: 0, e: 0, p: 0, i: 0 }, route: { s: 0, r: 0, c: 0, l: 0, e: 0, p: 0, i: 0 } }, nextState: 0, nextRef: 0, nextCondition: 0, nextList: 0, nextEffect: 0, nextParam: 0, nextId: 0, conditionDepth: 0, listDepth: 0, listRoot: undefined, listRowRoot: undefined, listTemplate: false, listInitialMarkers: false, listConditionalBranch: false, listFields: undefined, listEffectOwners: [], listRowStates: [], listRowRefs: [], listRowConditions: [], listRowLists: [], effectOwners: [], contexts: [], stores: new Map(), states: {}, textStates: new Set(), conditionStates: new Set(), events: [], effects: [], bindings: [], textBindings: [], conditions: [], lists: [], handlerModules: new Set(), runtimeParamNames: metadata.runtimeParams, paramEntries: [], params: undefined, hasBehaviors: false, hasNativeBehaviors: false, hasEffects: false, hasParams: false, hasBindings: false, hasLists: false, hasListStyles: false }
|
|
410
416
|
|
|
411
417
|
try {
|
|
412
418
|
const page = { [routeScopeMarker]: true, component, props }
|
|
@@ -910,7 +916,7 @@ function nextRowList() {
|
|
|
910
916
|
|
|
911
917
|
function nextRenderId(kind) {
|
|
912
918
|
if (renderContext.scoped) return `${renderContext.renderScope === "layout" ? "l" : "r"}${kind}${renderContext.counters[renderContext.renderScope][kind]++}`
|
|
913
|
-
const counters = { s: "nextState", r: "nextRef", c: "nextCondition", l: "nextList", e: "nextEffect", p: "nextParam" }
|
|
919
|
+
const counters = { s: "nextState", r: "nextRef", c: "nextCondition", l: "nextList", e: "nextEffect", p: "nextParam", i: "nextId" }
|
|
914
920
|
return `${kind}${renderContext[counters[kind]]++}`
|
|
915
921
|
}
|
|
916
922
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kudzujs/core",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.13",
|
|
4
4
|
"description": "HTML-first TSX framework with synchronous state semantics and no virtual DOM",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -51,6 +51,7 @@
|
|
|
51
51
|
"dev": "node ./bin/kudzu.mjs dev",
|
|
52
52
|
"check": "tsc --noEmit && tsc -p test/fixtures/tsconfig.json --noEmit && node ./bin/kudzu.mjs build",
|
|
53
53
|
"test": "node --test test/*.test.mjs",
|
|
54
|
+
"benchmark": "node test/performance.mjs",
|
|
54
55
|
"prepublishOnly": "npm run check && npm test",
|
|
55
56
|
"deploy": "wrangler deploy",
|
|
56
57
|
"preview": "wrangler dev"
|