@kudzujs/core 0.6.8 → 0.6.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/README.md +13 -2
- package/framework/README.md +2 -0
- package/framework/build.mjs +85 -16
- package/framework/core.d.ts +3 -0
- package/framework/core.mjs +12 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
HTML-first TSX framework with synchronous state semantics and no virtual DOM.
|
|
8
8
|
|
|
9
|
-
Kudzu keeps the familiar function-component, props, children, event-handler, `useState`, and mount-effect shape. Static components compile to HTML. Simple interactions compile to small behavior commands, while normal sync or async JavaScript handlers and mount effects compile to external ESM.
|
|
9
|
+
Kudzu keeps the familiar function-component, props, children, event-handler, `useState`, `useReducer`, and mount-effect shape. Static components compile to HTML. Simple interactions compile to small behavior commands, while normal sync or async JavaScript handlers and mount effects compile to external ESM.
|
|
10
10
|
|
|
11
11
|
> Experimental `0.6.x`: the compiler API and supported TSX surface may change.
|
|
12
12
|
|
|
@@ -171,6 +171,17 @@ return <p>{weather.temperature}° {weather.label}</p>
|
|
|
171
171
|
|
|
172
172
|
Derived text uses comment-bounded text nodes rather than wrapper elements, so table cells, options, SVG text, layout, and element selectors keep their authored structure.
|
|
173
173
|
|
|
174
|
+
Reducer state uses the same immediate logical updates and batched DOM commit:
|
|
175
|
+
|
|
176
|
+
```tsx
|
|
177
|
+
import todoReducer from "../todoReducer"
|
|
178
|
+
|
|
179
|
+
const [todos, dispatch] = useReducer(todoReducer, [])
|
|
180
|
+
dispatch({ type: "add", title: "Ship" })
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
The reduced migration form requires `[state, dispatch]`, exactly two hook arguments, and a synchronous two-parameter reducer exported as a default or named value from a relative TypeScript module. Dispatches in compiled handlers lower to functional state updates; lazy initializers, package, namespace, local, async, and generator reducers are not supported. Reducer dispatch functions cannot be passed through props or context.
|
|
184
|
+
|
|
174
185
|
## Reactive Attributes
|
|
175
186
|
|
|
176
187
|
`className`, `disabled`, controlled `value`, and controlled `checked` accept normal state-dependent TSX expressions. The same `value` binding works for inputs and selects:
|
|
@@ -523,7 +534,7 @@ Supported:
|
|
|
523
534
|
- Runtime bracket parameters with static fallback documents and host rewrite metadata
|
|
524
535
|
- Static trusted `dangerouslySetInnerHTML`
|
|
525
536
|
- Base-path deployments, multiple CSS files, and `afterBuild`
|
|
526
|
-
-
|
|
537
|
+
- `useState` and relative-imported `useReducer` bindings
|
|
527
538
|
- Mount-only `useEffect(fn, [])` compiled to route-specific ESM
|
|
528
539
|
- Relative TypeScript module Workers owned by inline effects
|
|
529
540
|
- Conditional and keyed-row effect ownership with cleanup on DOM removal
|
package/framework/README.md
CHANGED
|
@@ -23,6 +23,8 @@ Page `metadata` can emit description, canonical, favicon, manifest, Open Graph,
|
|
|
23
23
|
|
|
24
24
|
Same-file and relative-imported components receiving a direct local-state array prop are specialized to intrinsic JSX before keyed-list analysis, so their component function is not retained in the browser. Handler modules are emitted only when a rendered descriptor references them, preventing specialized imported components from adding dead browser assets. 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, unrelated fields do nothing, and key changes remain remove plus mount. Builds without item dependencies emit no item reader or list-state subscription code.
|
|
25
25
|
|
|
26
|
+
The reduced `useReducer` form reuses ordinary state slots. A direct dispatch in a compiled handler becomes a functional `set` whose reducer is bundled from a relative TypeScript module into that handler graph. Reducer-free routes and shared runtimes are unchanged; no reducer runtime or browser component instance exists.
|
|
27
|
+
|
|
26
28
|
`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.
|
|
27
29
|
|
|
28
30
|
The current matched commerce profile emits 35,355 deploy bytes and loads 7,334 B gzip of product-route JavaScript, including 2,425 B for navigation. These sizes are unchanged because its top-level-only navigation effects retain the smaller specialized path. Validated prefetch reduced the original 128.7 ms product-to-cart navigation to 5.6 ms in the current run. Seven interleaved artifact-clean builds after warm-up measured Kudzu at 486.8 ms and React at 545.4 ms, making Kudzu 10.7% faster. Cache-disabled output is byte-for-byte identical.
|
package/framework/build.mjs
CHANGED
|
@@ -1611,6 +1611,7 @@ function createKudzuTransformer(nativeHandlers, effectHandlers, reactiveBindings
|
|
|
1611
1611
|
return imported
|
|
1612
1612
|
}
|
|
1613
1613
|
const settersByFunction = new Map()
|
|
1614
|
+
const reducersByFunction = new Map()
|
|
1614
1615
|
const functions = new Map()
|
|
1615
1616
|
const components = new Map()
|
|
1616
1617
|
const contexts = new Set()
|
|
@@ -1630,15 +1631,41 @@ function createKudzuTransformer(nativeHandlers, effectHandlers, reactiveBindings
|
|
|
1630
1631
|
let usesListItem = false
|
|
1631
1632
|
|
|
1632
1633
|
const collect = node => {
|
|
1633
|
-
if (ts.isVariableDeclaration(node) &&
|
|
1634
|
+
if (ts.isVariableDeclaration(node) && node.initializer && ts.isCallExpression(node.initializer)) {
|
|
1634
1635
|
const callName = ts.isIdentifier(node.initializer.expression) ? node.initializer.expression.text : ""
|
|
1635
|
-
|
|
1636
|
-
|
|
1636
|
+
if (callName === "useReducer") {
|
|
1637
|
+
if (!ts.isArrayBindingPattern(node.name)) throw sourceNodeError(node.name, sourceFile, "useReducer() must use [state, dispatch] identifier destructuring")
|
|
1638
|
+
const [stateElement, dispatchElement] = node.name.elements
|
|
1639
|
+
if (node.name.elements.length !== 2 || !stateElement || !dispatchElement || !ts.isBindingElement(stateElement) || !ts.isBindingElement(dispatchElement) || !ts.isIdentifier(stateElement.name) || !ts.isIdentifier(dispatchElement.name)) throw sourceNodeError(node.name, sourceFile, "useReducer() must use [state, dispatch] identifier destructuring")
|
|
1640
|
+
if (node.initializer.arguments.length !== 2) throw sourceNodeError(node.initializer, sourceFile, "useReducer() requires exactly a reducer and initial value")
|
|
1641
|
+
const reducer = node.initializer.arguments[0]
|
|
1642
|
+
if (!ts.isIdentifier(reducer) || !importBindings.has(reducer.text) || importBindings.get(reducer.text).kind === "namespace") throw sourceNodeError(reducer, sourceFile, "useReducer() reducers must be default or named imports from relative TypeScript modules")
|
|
1643
|
+
const reducerImport = importBindings.get(reducer.text)
|
|
1644
|
+
let reducerDeclaration
|
|
1645
|
+
try {
|
|
1646
|
+
reducerDeclaration = resolveComponentExport(reducerImport.target, reducerImport.kind === "default" ? "default" : reducerImport.imported, importedSource, sourceFiles)
|
|
1647
|
+
} catch {
|
|
1648
|
+
throw sourceNodeError(reducer, sourceFile, "useReducer() imports must resolve to a statically analyzable reducer function")
|
|
1649
|
+
}
|
|
1650
|
+
if (reducerDeclaration.parameters.length !== 2 || reducerDeclaration.asteriskToken || reducerDeclaration.modifiers?.some(modifier => modifier.kind === ts.SyntaxKind.AsyncKeyword)) throw sourceNodeError(reducer, sourceFile, "useReducer() reducers must be synchronous functions with exactly state and action parameters")
|
|
1637
1651
|
const owner = nearestFunction(node)
|
|
1638
|
-
if (owner)
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1652
|
+
if (!owner) throw sourceNodeError(node, sourceFile, "useReducer() cannot be used outside a Kudzu component")
|
|
1653
|
+
const setters = settersByFunction.get(owner) ?? new Map()
|
|
1654
|
+
setters.set(dispatchElement.name.text, stateElement.name.text)
|
|
1655
|
+
settersByFunction.set(owner, setters)
|
|
1656
|
+
const reducers = reducersByFunction.get(owner) ?? new Map()
|
|
1657
|
+
reducers.set(dispatchElement.name.text, { state: stateElement.name.text, reducer: reducer.text, import: reducerImport })
|
|
1658
|
+
reducersByFunction.set(owner, reducers)
|
|
1659
|
+
}
|
|
1660
|
+
if (ts.isArrayBindingPattern(node.name)) {
|
|
1661
|
+
const [stateElement, setterElement] = node.name.elements
|
|
1662
|
+
if (callName === "useState" && stateElement && setterElement && ts.isBindingElement(stateElement) && ts.isBindingElement(setterElement) && ts.isIdentifier(stateElement.name) && ts.isIdentifier(setterElement.name)) {
|
|
1663
|
+
const owner = nearestFunction(node)
|
|
1664
|
+
if (owner) {
|
|
1665
|
+
const setters = settersByFunction.get(owner) ?? new Map()
|
|
1666
|
+
setters.set(setterElement.name.text, stateElement.name.text)
|
|
1667
|
+
settersByFunction.set(owner, setters)
|
|
1668
|
+
}
|
|
1642
1669
|
}
|
|
1643
1670
|
}
|
|
1644
1671
|
}
|
|
@@ -1931,7 +1958,7 @@ function createKudzuTransformer(nativeHandlers, effectHandlers, reactiveBindings
|
|
|
1931
1958
|
} else {
|
|
1932
1959
|
compiledCallback = rewriteEffectWorkers(callback, callbackFile, callbackSource, sourceFiles, workerReferences, factory, context)
|
|
1933
1960
|
}
|
|
1934
|
-
const descriptor = compileNativeCallback(compiledCallback, setters, factory, effectHandlers, listEffect?.imports ?? importBindings, clientImports, "effect", dependencyItem, true, returns.cleanup)
|
|
1961
|
+
const descriptor = compileNativeCallback(compiledCallback, setters, reducersForNode(node, reducersByFunction), factory, effectHandlers, listEffect?.imports ?? importBindings, clientImports, "effect", dependencyItem, true, returns.cleanup)
|
|
1935
1962
|
for (const reference of workerReferences.slice(workerStart)) Object.assign(reference, { module: handlerUrl, handler: descriptor.exportName })
|
|
1936
1963
|
usesListItem ||= Boolean(itemDependencies.length && !listEffect)
|
|
1937
1964
|
usesBehavior = true
|
|
@@ -1948,7 +1975,7 @@ function createKudzuTransformer(nativeHandlers, effectHandlers, reactiveBindings
|
|
|
1948
1975
|
])
|
|
1949
1976
|
}
|
|
1950
1977
|
|
|
1951
|
-
if (ts.isVariableDeclaration(node) && ts.isArrayBindingPattern(node.name) && node.initializer && ts.isCallExpression(node.initializer) && ts.isIdentifier(node.initializer.expression) && node.initializer.expression.text === "useState" && node.initializer.arguments.length === 1) {
|
|
1978
|
+
if (ts.isVariableDeclaration(node) && ts.isArrayBindingPattern(node.name) && node.initializer && ts.isCallExpression(node.initializer) && ts.isIdentifier(node.initializer.expression) && (node.initializer.expression.text === "useState" && node.initializer.arguments.length === 1 || node.initializer.expression.text === "useReducer" && node.initializer.arguments.length === 2)) {
|
|
1952
1979
|
const stateElement = node.name.elements[0]
|
|
1953
1980
|
if (!stateElement || !ts.isBindingElement(stateElement) || !ts.isIdentifier(stateElement.name)) return node
|
|
1954
1981
|
const initializer = factory.updateCallExpression(node.initializer, node.initializer.expression, node.initializer.typeArguments, [
|
|
@@ -2030,7 +2057,7 @@ function createKudzuTransformer(nativeHandlers, effectHandlers, reactiveBindings
|
|
|
2030
2057
|
|
|
2031
2058
|
if (ts.isJsxAttribute(node) && node.initializer && ts.isJsxExpression(node.initializer) && node.initializer.expression && /^on[A-Z]/.test(node.name.text)) {
|
|
2032
2059
|
const setters = settersForNode(node, settersByFunction)
|
|
2033
|
-
const event = compileEvent(node.initializer.expression, setters, functions, factory, nativeHandlers, handlerUrl, listEventItems.get(node), importBindings, clientImports)
|
|
2060
|
+
const event = compileEvent(node.initializer.expression, setters, reducersForNode(node, reducersByFunction), functions, factory, nativeHandlers, handlerUrl, listEventItems.get(node), importBindings, clientImports)
|
|
2034
2061
|
if (event) {
|
|
2035
2062
|
usesBehavior = true
|
|
2036
2063
|
return factory.updateJsxAttribute(node, node.name, factory.createJsxExpression(undefined, event))
|
|
@@ -2656,15 +2683,15 @@ function factoryNull() {
|
|
|
2656
2683
|
return ts.factory.createNull()
|
|
2657
2684
|
}
|
|
2658
2685
|
|
|
2659
|
-
function compileEvent(expression, setters, functions, factory, nativeHandlers, handlerUrl, listItem, importBindings, clientImports) {
|
|
2686
|
+
function compileEvent(expression, setters, reducers, functions, factory, nativeHandlers, handlerUrl, listItem, importBindings, clientImports) {
|
|
2660
2687
|
if (ts.isIdentifier(expression)) expression = functions.get(expression.text)
|
|
2661
2688
|
if (!expression || (!ts.isArrowFunction(expression) && !ts.isFunctionExpression(expression) && !ts.isFunctionDeclaration(expression))) return undefined
|
|
2662
2689
|
|
|
2663
|
-
const optimized = compileOptimizedEvent(expression, setters, factory)
|
|
2690
|
+
const optimized = referencedReducerDispatches(expression.body, reducers, expression).size ? undefined : compileOptimizedEvent(expression, setters, factory)
|
|
2664
2691
|
if (optimized) return optimized
|
|
2665
2692
|
|
|
2666
2693
|
rejectWorkerConstructions(expression, expression.getSourceFile(), "Relative TypeScript Worker construction is only supported directly inside an inline useEffect() callback")
|
|
2667
|
-
const descriptor = compileNativeCallback(expression, setters, factory, nativeHandlers, importBindings, clientImports, "handler", listItem)
|
|
2694
|
+
const descriptor = compileNativeCallback(expression, setters, reducers, factory, nativeHandlers, importBindings, clientImports, "handler", listItem)
|
|
2668
2695
|
return factory.createCallExpression(factory.createIdentifier("__kNativeBehavior"), undefined, [
|
|
2669
2696
|
factory.createStringLiteral(handlerUrl),
|
|
2670
2697
|
factory.createStringLiteral(descriptor.exportName),
|
|
@@ -2673,14 +2700,16 @@ function compileEvent(expression, setters, functions, factory, nativeHandlers, h
|
|
|
2673
2700
|
])
|
|
2674
2701
|
}
|
|
2675
2702
|
|
|
2676
|
-
function compileNativeCallback(expression, setters, factory, entries, importBindings, clientImports, prefix, listItem, deferValues = false, snapshotNested = false) {
|
|
2703
|
+
function compileNativeCallback(expression, setters, reducers, factory, entries, importBindings, clientImports, prefix, listItem, deferValues = false, snapshotNested = false) {
|
|
2677
2704
|
const allCaptures = nativeCaptureNames(expression, setters)
|
|
2705
|
+
const usedReducers = referencedReducerDispatches(expression.body, reducers, expression)
|
|
2678
2706
|
const imports = [...referencedImportedBindings(expression, importBindings)].map(name => importBindings.get(name))
|
|
2707
|
+
imports.push(...[...usedReducers].map(name => reducers.get(name).import))
|
|
2679
2708
|
const captures = new Set([...allCaptures].filter(name => !importBindings.has(name)))
|
|
2680
2709
|
for (const entry of imports) clientImports.add(entry.target)
|
|
2681
2710
|
const usedStates = nativeStateNames(expression, setters)
|
|
2682
2711
|
const exportName = `${prefix}${entries.length}`
|
|
2683
|
-
entries.push({ exportName, expression, captures, imports, setters: new Map([...setters].filter(([, state]) => usedStates.has(state))), snapshotNested })
|
|
2712
|
+
entries.push({ exportName, expression, captures, imports, setters: new Map([...setters].filter(([, state]) => usedStates.has(state))), reducers: new Map([...reducers].filter(([name]) => usedReducers.has(name))), snapshotNested })
|
|
2684
2713
|
const value = name => deferValues
|
|
2685
2714
|
? factory.createArrowFunction(undefined, undefined, [], undefined, factory.createToken(ts.SyntaxKind.EqualsGreaterThanToken), factory.createIdentifier(name))
|
|
2686
2715
|
: factory.createIdentifier(name)
|
|
@@ -2697,6 +2726,16 @@ function compileNativeCallback(expression, setters, factory, entries, importBind
|
|
|
2697
2726
|
}
|
|
2698
2727
|
}
|
|
2699
2728
|
|
|
2729
|
+
function referencedReducerDispatches(root, reducers, scopeRoot = root) {
|
|
2730
|
+
const used = new Set()
|
|
2731
|
+
const visit = node => {
|
|
2732
|
+
if (ts.isIdentifier(node) && reducers.has(node.text) && isReferenceIdentifier(node) && !isShadowedIdentifier(node, scopeRoot)) used.add(node.text)
|
|
2733
|
+
ts.forEachChild(node, visit)
|
|
2734
|
+
}
|
|
2735
|
+
visit(root)
|
|
2736
|
+
return used
|
|
2737
|
+
}
|
|
2738
|
+
|
|
2700
2739
|
function nativeStateNames(expression, setters) {
|
|
2701
2740
|
return referencedStateNames(expression.body, setters, expression)
|
|
2702
2741
|
}
|
|
@@ -2945,6 +2984,15 @@ function settersForNode(node, settersByFunction) {
|
|
|
2945
2984
|
return new Map()
|
|
2946
2985
|
}
|
|
2947
2986
|
|
|
2987
|
+
function reducersForNode(node, reducersByFunction) {
|
|
2988
|
+
for (let current = node.parent; current; current = current.parent) {
|
|
2989
|
+
if (!ts.isFunctionDeclaration(current) && !ts.isFunctionExpression(current) && !ts.isArrowFunction(current)) continue
|
|
2990
|
+
const reducers = reducersByFunction.get(current)
|
|
2991
|
+
if (reducers) return reducers
|
|
2992
|
+
}
|
|
2993
|
+
return new Map()
|
|
2994
|
+
}
|
|
2995
|
+
|
|
2948
2996
|
function clientImportBindings(sourceFile, file, sourceFiles) {
|
|
2949
2997
|
const bindings = new Map()
|
|
2950
2998
|
for (const node of sourceFile.statements) {
|
|
@@ -3242,7 +3290,7 @@ function relativeModulePath(from, to) {
|
|
|
3242
3290
|
return path.startsWith(".") ? path : `./${path}`
|
|
3243
3291
|
}
|
|
3244
3292
|
|
|
3245
|
-
function printNativeHandler({ exportName, expression, captures, setters, snapshotNested }) {
|
|
3293
|
+
function printNativeHandler({ exportName, expression, captures, setters, reducers = new Map(), snapshotNested }) {
|
|
3246
3294
|
const factory = ts.factory
|
|
3247
3295
|
const stateNames = new Set(setters.values())
|
|
3248
3296
|
const snapshotNames = snapshotNested ? nestedStateNames(expression, setters) : new Set()
|
|
@@ -3251,6 +3299,16 @@ function printNativeHandler({ exportName, expression, captures, setters, snapsho
|
|
|
3251
3299
|
const captureSnapshots = new Map([...captureSnapshotNames].map(name => [name, factory.createUniqueName("__kEffectCapture")]))
|
|
3252
3300
|
const transformer = context => root => {
|
|
3253
3301
|
const visitor = node => {
|
|
3302
|
+
if (ts.isCallExpression(node) && ts.isIdentifier(node.expression) && reducers.has(node.expression.text) && !isShadowedIdentifier(node.expression, expression)) {
|
|
3303
|
+
if (node.arguments.length !== 1) throw sourceNodeError(node, expression.getSourceFile(), "Reducer dispatches require exactly one action")
|
|
3304
|
+
return reducerDispatch(factory, reducers.get(node.expression.text), ts.visitNode(node.arguments[0], visitor))
|
|
3305
|
+
}
|
|
3306
|
+
if (ts.isShorthandPropertyAssignment(node) && reducers.has(node.name.text) && !isShadowedIdentifier(node.name, expression)) {
|
|
3307
|
+
return factory.createPropertyAssignment(node.name, reducerReference(factory, reducers.get(node.name.text)))
|
|
3308
|
+
}
|
|
3309
|
+
if (ts.isIdentifier(node) && reducers.has(node.text) && isReferenceIdentifier(node) && !isShadowedIdentifier(node, expression)) {
|
|
3310
|
+
return reducerReference(factory, reducers.get(node.text))
|
|
3311
|
+
}
|
|
3254
3312
|
if (ts.isCallExpression(node) && ts.isIdentifier(node.expression) && setters.has(node.expression.text) && !isShadowedIdentifier(node.expression, expression)) {
|
|
3255
3313
|
return factory.createCallExpression(
|
|
3256
3314
|
factory.createPropertyAccessExpression(factory.createIdentifier("__k"), "set"),
|
|
@@ -3357,6 +3415,17 @@ function setterReference(factory, stateName) {
|
|
|
3357
3415
|
)
|
|
3358
3416
|
}
|
|
3359
3417
|
|
|
3418
|
+
function reducerReference(factory, reducer) {
|
|
3419
|
+
const action = factory.createUniqueName("__kAction")
|
|
3420
|
+
return factory.createArrowFunction(undefined, undefined, [factory.createParameterDeclaration(undefined, undefined, action)], undefined, factory.createToken(ts.SyntaxKind.EqualsGreaterThanToken), reducerDispatch(factory, reducer, action))
|
|
3421
|
+
}
|
|
3422
|
+
|
|
3423
|
+
function reducerDispatch(factory, reducer, action) {
|
|
3424
|
+
const previous = factory.createUniqueName("__kPrevious")
|
|
3425
|
+
const update = factory.createArrowFunction(undefined, undefined, [factory.createParameterDeclaration(undefined, undefined, previous)], undefined, factory.createToken(ts.SyntaxKind.EqualsGreaterThanToken), factory.createCallExpression(factory.createIdentifier(reducer.reducer), undefined, [previous, action]))
|
|
3426
|
+
return factory.createCallExpression(factory.createPropertyAccessExpression(factory.createIdentifier("__k"), "set"), undefined, [factory.createStringLiteral(reducer.state), update])
|
|
3427
|
+
}
|
|
3428
|
+
|
|
3360
3429
|
function printReactiveBinding({ exportName, expression, captures, states }) {
|
|
3361
3430
|
const factory = ts.factory
|
|
3362
3431
|
const transformer = context => root => {
|
package/framework/core.d.ts
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
export type StateSetter<T> = (value: T | ((previous: T) => T)) => void
|
|
2
|
+
export type Reducer<State, Action> = (state: State, action: Action) => State
|
|
3
|
+
export type Dispatch<Action> = (action: Action) => void
|
|
2
4
|
export type EffectCleanup = () => void | Promise<void>
|
|
3
5
|
export type EffectDependency = string | number | boolean | null
|
|
4
6
|
|
|
5
7
|
export function useState<T>(initialValue: T): [T, StateSetter<T>]
|
|
8
|
+
export function useReducer<State, Action>(reducer: Reducer<State, Action>, initialValue: State): [State, Dispatch<Action>]
|
|
6
9
|
export function useEffect(effect: () => void | EffectCleanup | Promise<void>, dependencies: readonly EffectDependency[]): void
|
|
7
10
|
export function useParams<Params extends Record<string, string> = Record<string, string>>(): Readonly<Params>
|
|
8
11
|
|
package/framework/core.mjs
CHANGED
|
@@ -2,6 +2,7 @@ import { serializeStyle } from "./style.js"
|
|
|
2
2
|
|
|
3
3
|
const signalMarker = Symbol("kudzu.signal")
|
|
4
4
|
const setterMarker = Symbol("kudzu.setter")
|
|
5
|
+
const reducerDispatchMarker = Symbol("kudzu.reducerDispatch")
|
|
5
6
|
const behaviorMarker = Symbol("kudzu.behavior")
|
|
6
7
|
const nativeBehaviorMarker = Symbol("kudzu.nativeBehavior")
|
|
7
8
|
const bindingMarker = Symbol("kudzu.binding")
|
|
@@ -35,6 +36,16 @@ export function useState(initialValue, name) {
|
|
|
35
36
|
return [signal, setter]
|
|
36
37
|
}
|
|
37
38
|
|
|
39
|
+
export function useReducer(reducer, initialValue, name) {
|
|
40
|
+
if (typeof reducer !== "function") throw new Error("useReducer() requires a reducer function")
|
|
41
|
+
const [state] = useState(initialValue, name)
|
|
42
|
+
const dispatch = () => {
|
|
43
|
+
throw new Error("Reducer dispatches are compiled into browser handlers")
|
|
44
|
+
}
|
|
45
|
+
Object.defineProperty(dispatch, reducerDispatchMarker, { value: state.id })
|
|
46
|
+
return [state, dispatch]
|
|
47
|
+
}
|
|
48
|
+
|
|
38
49
|
export function useParams() {
|
|
39
50
|
if (renderContext?.renderScope === "layout") throw new Error("useParams() is only supported in route scope")
|
|
40
51
|
if (!renderContext?.runtimeParamNames?.length) throw new Error("useParams() requires export const runtimeParams = true on a bracket page")
|
|
@@ -268,6 +279,7 @@ function serializeCapture(name, value, seen) {
|
|
|
268
279
|
if (value?.[listItemMarker]) return { type: "list-item" }
|
|
269
280
|
if (value?.[refMarker]) return { type: "ref", id: value.id }
|
|
270
281
|
if (value?.[signalMarker]) return { type: "state", id: value.id }
|
|
282
|
+
if (typeof value === "function" && value[reducerDispatchMarker]) throw new Error(`Native capture "${name}" cannot contain a reducer dispatch`)
|
|
271
283
|
if (typeof value === "function" && value[setterMarker]) return { type: "setter", id: value[setterMarker] }
|
|
272
284
|
if (value === null || typeof value === "string" || typeof value === "boolean") return value
|
|
273
285
|
if (typeof value === "number") {
|