@kudzujs/core 0.8.11 → 0.8.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/README.md +1 -1
- package/RELEASES.md +56 -0
- package/framework/README.md +5 -1
- package/framework/binding-runtime.js +7 -0
- package/framework/build.mjs +296 -2
- package/framework/core.d.ts +2 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -14,7 +14,7 @@ Kudzu compiles ordinary React-shaped TypeScript and TSX into complete static HTM
|
|
|
14
14
|
|
|
15
15
|
> Experimental `0.8.x`: the compiler API and supported TSX surface may change.
|
|
16
16
|
|
|
17
|
-
**Latest release: 0.8.
|
|
17
|
+
**Latest release: 0.8.13 - Browser capability migration.** Memos scroll spies, Excalidraw progressive sharing, and Cal.com responsive media queries compile through owned browser effects without React or general capability runtimes. Read the [release notes](./RELEASES.md#0813---browser-capability-migration) or open the [release page](https://kudzujs.cloud/releases/0.8.13).
|
|
18
18
|
|
|
19
19
|
- [Documentation](https://kudzujs.cloud/docs)
|
|
20
20
|
- [Installation guide](https://kudzujs.cloud/docs#install)
|
package/RELEASES.md
CHANGED
|
@@ -1,5 +1,61 @@
|
|
|
1
1
|
# Kudzu Releases
|
|
2
2
|
|
|
3
|
+
## 0.8.13 - Browser capability migration
|
|
4
|
+
|
|
5
|
+
Kudzu 0.8.13 proves browser-owned migration paths from Memos, Excalidraw, and Cal.com without adding React, general external-store compatibility, or browser capability runtimes.
|
|
6
|
+
|
|
7
|
+
### New in 0.8.13
|
|
8
|
+
|
|
9
|
+
- Memos-shaped scroll spies retain an effect-owned `useRef(0)` animation-frame handle with burst coalescing, exact cleanup, smooth navigation, and active-heading ARIA.
|
|
10
|
+
- Excalidraw-shaped `"share" in navigator` conditions emit false static fallback and mount Web Share DOM and handlers only in supporting browsers.
|
|
11
|
+
- Cal.com-shaped static `useSyncExternalStore` media queries emit desktop-first HTML and lower matching `change` subscriptions into owned effects.
|
|
12
|
+
- Direct Web Share and clipboard actions preserve application-owned success and failure state without package adapters.
|
|
13
|
+
- Static sibling routes remain complete zero-JavaScript HTML for every migration fixture.
|
|
14
|
+
|
|
15
|
+
### Fixed in 0.8.13
|
|
16
|
+
|
|
17
|
+
- Build-time Node `navigator` availability can no longer permanently fold browser capability UI to the wrong branch.
|
|
18
|
+
- Optimized state-select text bindings now register the same direct state commit path as optimized attributes.
|
|
19
|
+
- Animation-frame refs reject aliases, shadowed bindings, inverted guards, indirect reset/cancellation, repeated scheduling, and missing cleanup with source locations.
|
|
20
|
+
- The complete suite passes 149/149 tests.
|
|
21
|
+
|
|
22
|
+
### Boundary
|
|
23
|
+
|
|
24
|
+
This release does not add general mutable refs, external stores, media hooks, browser-expression rendering, animation scheduling, collaboration transport, QR generation, or React package execution. Dynamic capability names, `navigator.canShare()`, parameterized/imported media hooks, dynamic queries, and arbitrary store snapshots remain source migration work.
|
|
25
|
+
|
|
26
|
+
### Upgrade
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
npm install @kudzujs/core@^0.8.13
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## 0.8.12 - Static icon migration
|
|
33
|
+
|
|
34
|
+
Kudzu 0.8.12 proves a source migration path from Lucide React-shaped icon usage to accessible static SVG without a package factory or browser runtime.
|
|
35
|
+
|
|
36
|
+
### New in 0.8.12
|
|
37
|
+
|
|
38
|
+
- Used icons move into relative TSX components with direct intrinsic SVG roots and familiar size, stroke, class, fill, ARIA, and title props.
|
|
39
|
+
- Meaningful icons retain explicit roles and titles, while decorative icons remain hidden from assistive technology.
|
|
40
|
+
- React-shaped SVG presentation attributes normalize to native output and complete icon routes ship zero JavaScript.
|
|
41
|
+
- Unreachable icon modules are excluded from compilation instead of becoming deploy artifacts.
|
|
42
|
+
- Public documentation records the migration recipe and its deliberate package boundary.
|
|
43
|
+
|
|
44
|
+
### Fixed in 0.8.12
|
|
45
|
+
|
|
46
|
+
- Handler emission now follows references in the final route plan and HTML, removing evaluator modules left behind by build-folded component conditions while preserving list-expression evaluators.
|
|
47
|
+
- The complete suite passes 143/143 tests.
|
|
48
|
+
|
|
49
|
+
### Boundary
|
|
50
|
+
|
|
51
|
+
This release does not execute `lucide-react`. Dynamic icon lookup, `createLucideIcon()`, package-owned factories, and a generic icon runtime remain unsupported source migration work.
|
|
52
|
+
|
|
53
|
+
### Upgrade
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
npm install @kudzujs/core@^0.8.12
|
|
57
|
+
```
|
|
58
|
+
|
|
3
59
|
## 0.8.11 - Stale-safe data migration
|
|
4
60
|
|
|
5
61
|
Kudzu 0.8.11 proves source migration paths for TanStack Query-shaped data and React Hook Form-shaped forms while strengthening dependency-effect stale-write isolation.
|
package/framework/README.md
CHANGED
|
@@ -6,7 +6,11 @@ Migration source may retain conventional `react` imports for supported named or
|
|
|
6
6
|
|
|
7
7
|
Compilation begins from page entries and follows relative runtime imports, re-exports, and validated Worker references; unreachable TypeScript migration files are not transformed. Direct maps over imported immutable JSON-safe arrays fold to literals for zero-JavaScript static rows. Synchronous relative calculation functions may return objects whose direct static fields feed reactive JSX bindings; build rendering uses current signal values and route-specific binding ESM reevaluates the same helper after state commits. One direct array field may instead feed a keyed intrinsic map: its evaluator refreshes a compiler-owned array anchor before the existing list reconciler runs, preserving keyed DOM and SVG identity without a calculation runtime. That field must remain a JSON-safe array after every source-state commit. Package imports have a separate narrow boundary: direct references inside intrinsic JSX event callbacks are erased from build modules and bundled into route handler ESM, while render-time, effect, helper-indirect, and mixed package use fails.
|
|
8
8
|
|
|
9
|
-
Native platform work remains ordinary source. A direct async handler or directly returned relative custom-hook callback may call `navigator.clipboard.writeText()` and update application-owned success/failure state; Kudzu emits only its existing route handler ESM. Debounced synchronization uses a dependency effect that creates `setTimeout()` work and directly returns `clearTimeout()` cleanup, reusing dependency, conditional, keyed, and route ownership. One directly returned relative custom-hook callback may own one `null`-initialized private timeout ref when it directly clears the previous value, assigns a numeric-literal-delay `setTimeout()`, and an empty-dependency effect directly clears the timer on cleanup. Kudzu lowers that ref to compiler-owned state shared by existing handler and effect contexts. Multiple timers, dynamic delays, intervals, aliases, unowned delayed writes, and arbitrary timed callback graphs remain unsupported.
|
|
9
|
+
Native platform work remains ordinary source. A direct async handler or directly returned relative custom-hook callback may call `navigator.clipboard.writeText()` and update application-owned success/failure state; Kudzu emits only its existing route handler ESM. Debounced synchronization uses a dependency effect that creates `setTimeout()` work and directly returns `clearTimeout()` cleanup, reusing dependency, conditional, keyed, and route ownership. One directly returned relative custom-hook callback may own one `null`-initialized private timeout ref when it directly clears the previous value, assigns a numeric-literal-delay `setTimeout()`, and an empty-dependency effect directly clears the timer on cleanup. Kudzu lowers that ref to compiler-owned state shared by existing handler and effect contexts. An ordinary effect may instead exclusively own `useRef(0)` as one animation-frame handle when a local callback resets it, one scheduler assigns `requestAnimationFrame()`, and cleanup cancels the pending frame; Kudzu lowers it to the existing effect scope without an animation runtime. Multiple timers, dynamic delays, intervals, ref aliases, cross-effect frame handles, unowned delayed writes, and arbitrary timed callback graphs remain unsupported.
|
|
10
|
+
|
|
11
|
+
A top-level component local shaped exactly as `const supported = "literal" in navigator` may control one direct `supported && <Branch />` JSX site. Kudzu emits the branch as false static fallback, evaluates the browser capability once through an existing mount effect, and uses existing state-conditional ownership to mount its DOM and handlers only when supported. The value cannot escape or feed aliases, composed expressions, ternaries, dynamic properties, `canShare()`, or other browser globals.
|
|
12
|
+
|
|
13
|
+
A direct top-level `useSyncExternalStore()` may represent one static `window.matchMedia()` boolean when its three inline callbacks add/remove the same `change` listener, read the same literal query, and return `false` for the server snapshot. Kudzu emits false static fallback state and lowers the subscription to an existing owned effect. This is not general external-store support: imported/parameterized media hooks, dynamic queries, legacy listeners, and arbitrary snapshots remain diagnosed.
|
|
10
14
|
|
|
11
15
|
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.
|
|
12
16
|
|
|
@@ -104,6 +104,13 @@ function mountBindings(root) {
|
|
|
104
104
|
mountedBindings.add(node)
|
|
105
105
|
const registrations = []
|
|
106
106
|
bindingRegistrations.set(node, registrations)
|
|
107
|
+
if (descriptor.state) {
|
|
108
|
+
const binding = { node, target: "text", read: Object.hasOwn(descriptor, "truthy") ? () => browserState.get(descriptor.state) ? descriptor.truthy : descriptor.falsy : () => browserState.get(descriptor.state) }
|
|
109
|
+
register(bindingTargets, descriptor.state, binding)
|
|
110
|
+
registrations.push([descriptor.state, binding])
|
|
111
|
+
patchBinding(node, "text", binding.read())
|
|
112
|
+
continue
|
|
113
|
+
}
|
|
107
114
|
loadEvaluator(descriptor).then(evaluator => {
|
|
108
115
|
if (!node.isConnected) return
|
|
109
116
|
const binding = { node, target: "text", read: evaluator.read }
|
package/framework/build.mjs
CHANGED
|
@@ -145,7 +145,8 @@ export async function build({ quiet = false, minify = true } = {}) {
|
|
|
145
145
|
runtimeParams: runtimeSchema?.params,
|
|
146
146
|
...(navigable ? { navigationAsset: navigationGroup.assetPath, applicationId: navigationGroup.applicationId, layoutId: navigationGroup.layoutId, routeId: applicationRoute } : {})
|
|
147
147
|
}, props, module.layout)
|
|
148
|
-
|
|
148
|
+
const renderedOutput = `${JSON.stringify(result.plan)}\n${result.html}`
|
|
149
|
+
for (const url of result.handlerModules) if (renderedOutput.includes(JSON.stringify(url))) renderedHandlerUrls.add(url)
|
|
149
150
|
if (navigationGroup) {
|
|
150
151
|
navigationGroup.hasEffects ||= result.hasEffects
|
|
151
152
|
navigationGroup.hasParams ||= result.hasParams
|
|
@@ -2744,6 +2745,287 @@ function normalizeCustomHookTimerRefs(sourceFile, factory, context) {
|
|
|
2744
2745
|
return normalized
|
|
2745
2746
|
}
|
|
2746
2747
|
|
|
2748
|
+
function normalizeMediaQueryExternalStores(sourceFile, factory, context) {
|
|
2749
|
+
const imports = sourceFile.statements.filter(statement => ts.isImportDeclaration(statement) && !statement.importClause?.isTypeOnly && ts.isStringLiteral(statement.moduleSpecifier) && statement.moduleSpecifier.text === "react" && statement.importClause?.namedBindings && ts.isNamedImports(statement.importClause.namedBindings))
|
|
2750
|
+
const externalStoreImport = imports.flatMap(statement => statement.importClause.namedBindings.elements.map(entry => ({ entry, statement }))).find(({ entry }) => !entry.isTypeOnly && !entry.propertyName && entry.name.text === "useSyncExternalStore")
|
|
2751
|
+
if (!externalStoreImport) return sourceFile
|
|
2752
|
+
const returnedExpression = callback => {
|
|
2753
|
+
if (!(ts.isArrowFunction(callback) || ts.isFunctionExpression(callback)) || callback.parameters.length) return undefined
|
|
2754
|
+
if (!ts.isBlock(callback.body)) return unwrapExpression(callback.body)
|
|
2755
|
+
if (callback.body.statements.length !== 1 || !ts.isReturnStatement(callback.body.statements[0]) || !callback.body.statements[0].expression) return undefined
|
|
2756
|
+
return unwrapExpression(callback.body.statements[0].expression)
|
|
2757
|
+
}
|
|
2758
|
+
const matchMediaQuery = expression => {
|
|
2759
|
+
expression = unwrapExpression(expression)
|
|
2760
|
+
if (!ts.isCallExpression(expression) || expression.arguments.length !== 1 || !ts.isStringLiteral(unwrapExpression(expression.arguments[0])) || !ts.isPropertyAccessExpression(expression.expression) || !ts.isIdentifier(expression.expression.expression) || expression.expression.expression.text !== "window" || expression.expression.name.text !== "matchMedia" || !isUnshadowedGlobal(expression.expression.expression, sourceFile)) return undefined
|
|
2761
|
+
return unwrapExpression(expression.arguments[0]).text
|
|
2762
|
+
}
|
|
2763
|
+
const mediaListener = (statement, method, media, callback) => ts.isExpressionStatement(statement) && ts.isCallExpression(statement.expression) && statement.expression.arguments.length === 2 && ts.isPropertyAccessExpression(statement.expression.expression) && ts.isIdentifier(statement.expression.expression.expression) && statement.expression.expression.expression.text === media && statement.expression.expression.name.text === method && ts.isStringLiteral(unwrapExpression(statement.expression.arguments[0])) && unwrapExpression(statement.expression.arguments[0]).text === "change" && ts.isIdentifier(unwrapExpression(statement.expression.arguments[1])) && unwrapExpression(statement.expression.arguments[1]).text === callback
|
|
2764
|
+
const candidates = new Map()
|
|
2765
|
+
let index = 0
|
|
2766
|
+
const inspect = node => {
|
|
2767
|
+
if (!ts.isVariableStatement(node) || !(node.declarationList.flags & ts.NodeFlags.Const) || node.declarationList.declarations.length !== 1) {
|
|
2768
|
+
ts.forEachChild(node, inspect)
|
|
2769
|
+
return
|
|
2770
|
+
}
|
|
2771
|
+
const declaration = node.declarationList.declarations[0]
|
|
2772
|
+
const call = declaration.initializer && unwrapExpression(declaration.initializer)
|
|
2773
|
+
if (!ts.isIdentifier(declaration.name) || !call || !ts.isCallExpression(call) || !ts.isIdentifier(call.expression) || call.expression.text !== "useSyncExternalStore" || isShadowedIdentifier(call.expression, sourceFile)) {
|
|
2774
|
+
ts.forEachChild(node, inspect)
|
|
2775
|
+
return
|
|
2776
|
+
}
|
|
2777
|
+
if (call.arguments.length !== 3) throw sourceNodeError(call, sourceFile, "Media query useSyncExternalStore() requires subscribe, browser snapshot, and false server snapshot callbacks")
|
|
2778
|
+
const [subscribe, snapshot, serverSnapshot] = call.arguments.map(unwrapExpression)
|
|
2779
|
+
if (!(ts.isArrowFunction(subscribe) || ts.isFunctionExpression(subscribe)) || subscribe.parameters.length !== 1 || !ts.isIdentifier(subscribe.parameters[0].name) || !ts.isBlock(subscribe.body)) throw sourceNodeError(subscribe, sourceFile, "Media query subscriptions require one inline callback parameter and block body")
|
|
2780
|
+
if (subscribe.body.statements.length !== 3) throw sourceNodeError(subscribe, sourceFile, "Media query subscriptions must add and remove one matching change listener")
|
|
2781
|
+
const callback = subscribe.parameters[0].name.text
|
|
2782
|
+
const [mediaStatement, addStatement, returnStatement] = subscribe.body.statements
|
|
2783
|
+
const mediaDeclaration = ts.isVariableStatement(mediaStatement) && (mediaStatement.declarationList.flags & ts.NodeFlags.Const) && mediaStatement.declarationList.declarations.length === 1 ? mediaStatement.declarationList.declarations[0] : undefined
|
|
2784
|
+
const media = mediaDeclaration && ts.isIdentifier(mediaDeclaration.name) ? mediaDeclaration.name.text : undefined
|
|
2785
|
+
const query = mediaDeclaration?.initializer && matchMediaQuery(mediaDeclaration.initializer)
|
|
2786
|
+
const cleanup = ts.isReturnStatement(returnStatement) && returnStatement.expression ? unwrapExpression(returnStatement.expression) : undefined
|
|
2787
|
+
const cleanupStatement = cleanup && (ts.isArrowFunction(cleanup) || ts.isFunctionExpression(cleanup)) && !cleanup.parameters.length
|
|
2788
|
+
? ts.isBlock(cleanup.body) ? cleanup.body.statements.length === 1 ? cleanup.body.statements[0] : undefined : factory.createExpressionStatement(cleanup.body)
|
|
2789
|
+
: undefined
|
|
2790
|
+
if (!media || !query || !mediaListener(addStatement, "addEventListener", media, callback) || !cleanupStatement || !mediaListener(cleanupStatement, "removeEventListener", media, callback)) throw sourceNodeError(subscribe, sourceFile, "Media query subscriptions must add and remove one matching change listener")
|
|
2791
|
+
const snapshotValue = returnedExpression(snapshot)
|
|
2792
|
+
const snapshotQuery = snapshotValue && ts.isPropertyAccessExpression(snapshotValue) && snapshotValue.name.text === "matches" ? matchMediaQuery(snapshotValue.expression) : undefined
|
|
2793
|
+
const serverValue = returnedExpression(serverSnapshot)
|
|
2794
|
+
if (snapshotQuery !== query || !serverValue || serverValue.kind !== ts.SyntaxKind.FalseKeyword) throw sourceNodeError(call, sourceFile, "Media query external stores require matching static snapshots and a false server fallback")
|
|
2795
|
+
const owner = nearestFunction(node)
|
|
2796
|
+
const topLevelOwner = owner && (owner.parent === sourceFile || ts.isVariableDeclaration(owner.parent) && owner.parent.parent?.parent?.parent === sourceFile)
|
|
2797
|
+
if (!topLevelOwner || !owner.body || !ts.isBlock(owner.body) || node.parent !== owner.body) throw sourceNodeError(declaration, sourceFile, "Media query external stores must initialize one top-level component const")
|
|
2798
|
+
for (const name of ["useEffect", "useState"]) if (owner.parameters.some(parameter => bindingNames(parameter.name).includes(name)) || functionVarDeclaresName(owner, name) || owner.body.statements.some(statement => statementDeclaresName(statement, name))) throw sourceNodeError(declaration, sourceFile, `Media query external stores conflict with component-local ${name}`)
|
|
2799
|
+
let setter = `__kSetMediaQuery${index++}`
|
|
2800
|
+
while (sourceFile.text.includes(setter)) setter = `__kSetMediaQuery${index++}`
|
|
2801
|
+
candidates.set(node, { declaration, query, setter })
|
|
2802
|
+
}
|
|
2803
|
+
inspect(sourceFile)
|
|
2804
|
+
const references = referenceIdentifiers(sourceFile, "useSyncExternalStore")
|
|
2805
|
+
if (references.length !== candidates.size) throw sourceNodeError(references.find(reference => ![...candidates.values()].some(candidate => insideNode(reference, candidate.declaration.initializer))) ?? externalStoreImport.entry, sourceFile, "useSyncExternalStore is supported only for direct static media query declarations")
|
|
2806
|
+
const directHooks = new Set(imports.flatMap(statement => statement.importClause.namedBindings.elements.filter(entry => !entry.propertyName).map(entry => entry.name.text)))
|
|
2807
|
+
const missingHooks = ["useEffect", "useState"].filter(name => !directHooks.has(name))
|
|
2808
|
+
for (const name of missingHooks) {
|
|
2809
|
+
const collision = sourceFile.statements.some(statement => statementDeclaresName(statement, name) || ts.isImportDeclaration(statement) && importDeclarationNames(statement).includes(name))
|
|
2810
|
+
if (collision) throw sourceNodeError([...candidates.values()][0].declaration, sourceFile, `Media query external stores conflict with local ${name}`)
|
|
2811
|
+
}
|
|
2812
|
+
|
|
2813
|
+
const visitor = node => {
|
|
2814
|
+
const candidate = ts.isVariableStatement(node) ? candidates.get(node) : undefined
|
|
2815
|
+
if (candidate) {
|
|
2816
|
+
const state = factory.createVariableStatement(node.modifiers, factory.createVariableDeclarationList([
|
|
2817
|
+
factory.createVariableDeclaration(factory.createArrayBindingPattern([
|
|
2818
|
+
factory.createBindingElement(undefined, undefined, candidate.declaration.name),
|
|
2819
|
+
factory.createBindingElement(undefined, undefined, candidate.setter)
|
|
2820
|
+
]), undefined, undefined, factory.createCallExpression(factory.createIdentifier("useState"), undefined, [factory.createFalse()]))
|
|
2821
|
+
], ts.NodeFlags.Const))
|
|
2822
|
+
const media = factory.createIdentifier("media")
|
|
2823
|
+
const update = factory.createIdentifier("update")
|
|
2824
|
+
const mediaCall = factory.createCallExpression(factory.createPropertyAccessExpression(factory.createIdentifier("window"), "matchMedia"), undefined, [factory.createStringLiteral(candidate.query)])
|
|
2825
|
+
const updateCallback = factory.createArrowFunction(undefined, undefined, [], undefined, factory.createToken(ts.SyntaxKind.EqualsGreaterThanToken), factory.createCallExpression(factory.createIdentifier(candidate.setter), undefined, [factory.createPropertyAccessExpression(media, "matches")]))
|
|
2826
|
+
const listener = method => factory.createCallExpression(factory.createPropertyAccessExpression(media, method), undefined, [factory.createStringLiteral("change"), update])
|
|
2827
|
+
const cleanup = factory.createArrowFunction(undefined, undefined, [], undefined, factory.createToken(ts.SyntaxKind.EqualsGreaterThanToken), listener("removeEventListener"))
|
|
2828
|
+
const setup = factory.createArrowFunction(undefined, undefined, [], undefined, factory.createToken(ts.SyntaxKind.EqualsGreaterThanToken), factory.createBlock([
|
|
2829
|
+
factory.createVariableStatement(undefined, factory.createVariableDeclarationList([factory.createVariableDeclaration(media, undefined, undefined, mediaCall)], ts.NodeFlags.Const)),
|
|
2830
|
+
factory.createVariableStatement(undefined, factory.createVariableDeclarationList([factory.createVariableDeclaration(update, undefined, undefined, updateCallback)], ts.NodeFlags.Const)),
|
|
2831
|
+
factory.createExpressionStatement(factory.createCallExpression(update, undefined, [])),
|
|
2832
|
+
factory.createExpressionStatement(listener("addEventListener")),
|
|
2833
|
+
factory.createReturnStatement(cleanup)
|
|
2834
|
+
], true))
|
|
2835
|
+
const effectCall = factory.createCallExpression(factory.createIdentifier("useEffect"), undefined, [setup, factory.createArrayLiteralExpression()])
|
|
2836
|
+
ts.setOriginalNode(effectCall, candidate.declaration.initializer)
|
|
2837
|
+
ts.setTextRange(effectCall, candidate.declaration.initializer)
|
|
2838
|
+
return [state, factory.createExpressionStatement(effectCall)]
|
|
2839
|
+
}
|
|
2840
|
+
if (node === externalStoreImport.statement) {
|
|
2841
|
+
const clause = node.importClause
|
|
2842
|
+
const bindings = clause.namedBindings
|
|
2843
|
+
const elements = bindings.elements.filter(entry => entry !== externalStoreImport.entry)
|
|
2844
|
+
for (const name of missingHooks) elements.push(factory.createImportSpecifier(false, undefined, factory.createIdentifier(name)))
|
|
2845
|
+
return factory.updateImportDeclaration(node, node.modifiers, factory.updateImportClause(clause, false, clause.name, factory.updateNamedImports(bindings, elements)), node.moduleSpecifier, node.attributes)
|
|
2846
|
+
}
|
|
2847
|
+
return ts.visitEachChild(node, visitor, context)
|
|
2848
|
+
}
|
|
2849
|
+
return ts.visitNode(sourceFile, visitor)
|
|
2850
|
+
}
|
|
2851
|
+
|
|
2852
|
+
function insideNode(node, root) {
|
|
2853
|
+
for (let current = node; current; current = current.parent) if (current === root) return true
|
|
2854
|
+
return false
|
|
2855
|
+
}
|
|
2856
|
+
|
|
2857
|
+
function normalizeNavigatorCapabilityConditions(sourceFile, factory, context) {
|
|
2858
|
+
const candidates = new Map()
|
|
2859
|
+
let index = 0
|
|
2860
|
+
const inspect = node => {
|
|
2861
|
+
if (!ts.isVariableStatement(node) || !(node.declarationList.flags & ts.NodeFlags.Const) || node.declarationList.declarations.length !== 1) {
|
|
2862
|
+
ts.forEachChild(node, inspect)
|
|
2863
|
+
return
|
|
2864
|
+
}
|
|
2865
|
+
const declaration = node.declarationList.declarations[0]
|
|
2866
|
+
const value = declaration.initializer && unwrapExpression(declaration.initializer)
|
|
2867
|
+
if (!ts.isIdentifier(declaration.name) || !value || !ts.isBinaryExpression(value) || value.operatorToken.kind !== ts.SyntaxKind.InKeyword || !ts.isStringLiteral(unwrapExpression(value.left)) || !ts.isIdentifier(unwrapExpression(value.right)) || unwrapExpression(value.right).text !== "navigator" || !isUnshadowedGlobal(unwrapExpression(value.right), sourceFile)) {
|
|
2868
|
+
ts.forEachChild(node, inspect)
|
|
2869
|
+
return
|
|
2870
|
+
}
|
|
2871
|
+
const owner = nearestFunction(node)
|
|
2872
|
+
const topLevelOwner = owner && (owner.parent === sourceFile || ts.isVariableDeclaration(owner.parent) && owner.parent.parent?.parent?.parent === sourceFile)
|
|
2873
|
+
if (!topLevelOwner || !owner.body || !ts.isBlock(owner.body) || node.parent !== owner.body) throw sourceNodeError(declaration, sourceFile, "Navigator capability conditions must be top-level component const declarations")
|
|
2874
|
+
const references = referenceIdentifiers(owner.body, declaration.name.text)
|
|
2875
|
+
const condition = references.length === 1 ? references[0] : undefined
|
|
2876
|
+
const structural = condition && ts.isBinaryExpression(condition.parent) && condition.parent.left === condition && condition.parent.operatorToken.kind === ts.SyntaxKind.AmpersandAmpersandToken && containsJsx(condition.parent.right) && ts.isJsxExpression(condition.parent.parent) && condition.parent.parent.expression === condition.parent
|
|
2877
|
+
if (!structural) throw sourceNodeError(declaration, sourceFile, "Navigator capability values may only control one direct JSX && branch")
|
|
2878
|
+
for (const name of ["useEffect", "useState"]) if (owner.parameters.some(parameter => bindingNames(parameter.name).includes(name)) || functionVarDeclaresName(owner, name) || owner.body.statements.some(statement => statementDeclaresName(statement, name))) throw sourceNodeError(declaration, sourceFile, `Navigator capability conditions conflict with component-local ${name}`)
|
|
2879
|
+
let setter = `__kSetNavigatorCapability${index++}`
|
|
2880
|
+
while (sourceFile.text.includes(setter)) setter = `__kSetNavigatorCapability${index++}`
|
|
2881
|
+
candidates.set(node, { declaration, property: unwrapExpression(value.left).text, setter })
|
|
2882
|
+
}
|
|
2883
|
+
inspect(sourceFile)
|
|
2884
|
+
if (!candidates.size) return sourceFile
|
|
2885
|
+
|
|
2886
|
+
const visitor = node => {
|
|
2887
|
+
const candidate = ts.isVariableStatement(node) ? candidates.get(node) : undefined
|
|
2888
|
+
if (candidate) {
|
|
2889
|
+
const state = factory.createVariableStatement(node.modifiers, factory.createVariableDeclarationList([
|
|
2890
|
+
factory.createVariableDeclaration(factory.createArrayBindingPattern([
|
|
2891
|
+
factory.createBindingElement(undefined, undefined, candidate.declaration.name),
|
|
2892
|
+
factory.createBindingElement(undefined, undefined, candidate.setter)
|
|
2893
|
+
]), undefined, undefined, factory.createCallExpression(factory.createIdentifier("useState"), undefined, [factory.createFalse()]))
|
|
2894
|
+
], ts.NodeFlags.Const))
|
|
2895
|
+
const capability = factory.createBinaryExpression(factory.createStringLiteral(candidate.property), factory.createToken(ts.SyntaxKind.InKeyword), factory.createIdentifier("navigator"))
|
|
2896
|
+
const setup = factory.createArrowFunction(undefined, undefined, [], undefined, factory.createToken(ts.SyntaxKind.EqualsGreaterThanToken), factory.createBlock([
|
|
2897
|
+
factory.createExpressionStatement(factory.createCallExpression(factory.createIdentifier(candidate.setter), undefined, [capability]))
|
|
2898
|
+
], true))
|
|
2899
|
+
const effectCall = factory.createCallExpression(factory.createIdentifier("useEffect"), undefined, [setup, factory.createArrayLiteralExpression()])
|
|
2900
|
+
ts.setOriginalNode(effectCall, candidate.declaration.initializer)
|
|
2901
|
+
ts.setTextRange(effectCall, candidate.declaration.initializer)
|
|
2902
|
+
const effect = factory.createExpressionStatement(effectCall)
|
|
2903
|
+
return [state, effect]
|
|
2904
|
+
}
|
|
2905
|
+
return ts.visitEachChild(node, visitor, context)
|
|
2906
|
+
}
|
|
2907
|
+
let normalized = ts.visitNode(sourceFile, visitor)
|
|
2908
|
+
const hookImports = normalized.statements.filter(statement => ts.isImportDeclaration(statement) && !statement.importClause?.isTypeOnly && ts.isStringLiteral(statement.moduleSpecifier) && ["react", "@kudzujs/core"].includes(statement.moduleSpecifier.text) && statement.importClause?.namedBindings && ts.isNamedImports(statement.importClause.namedBindings))
|
|
2909
|
+
const hookImport = hookImports[0]
|
|
2910
|
+
const bindings = hookImport?.importClause.namedBindings
|
|
2911
|
+
const imported = new Set(hookImports.flatMap(statement => statement.importClause.namedBindings.elements.map(entry => entry.name.text)))
|
|
2912
|
+
const missing = ["useEffect", "useState"].filter(name => !imported.has(name))
|
|
2913
|
+
if (!missing.length) return normalized
|
|
2914
|
+
for (const name of missing) if (normalized.statements.some(statement => !hookImports.includes(statement) && (statementDeclaresName(statement, name) || ts.isImportDeclaration(statement) && importDeclarationNames(statement).includes(name)))) throw sourceNodeError([...candidates.values()][0].declaration, sourceFile, `Navigator capability conditions conflict with local ${name}`)
|
|
2915
|
+
if (hookImport && bindings && ts.isNamedImports(bindings)) {
|
|
2916
|
+
const statements = normalized.statements.map(statement => statement === hookImport ? factory.updateImportDeclaration(statement, statement.modifiers, factory.updateImportClause(statement.importClause, false, statement.importClause.name, factory.updateNamedImports(bindings, [
|
|
2917
|
+
...bindings.elements,
|
|
2918
|
+
...missing.map(name => factory.createImportSpecifier(false, undefined, factory.createIdentifier(name)))
|
|
2919
|
+
])), statement.moduleSpecifier, statement.attributes) : statement)
|
|
2920
|
+
return factory.updateSourceFile(normalized, statements)
|
|
2921
|
+
}
|
|
2922
|
+
const declaration = factory.createImportDeclaration(undefined, factory.createImportClause(false, undefined, factory.createNamedImports(missing.map(name => factory.createImportSpecifier(false, undefined, factory.createIdentifier(name))))), factory.createStringLiteral("@kudzujs/core"))
|
|
2923
|
+
const statements = [...normalized.statements]
|
|
2924
|
+
statements.splice(statements.findLastIndex(statement => ts.isImportDeclaration(statement)) + 1, 0, declaration)
|
|
2925
|
+
return factory.updateSourceFile(normalized, statements)
|
|
2926
|
+
}
|
|
2927
|
+
|
|
2928
|
+
function normalizeEffectAnimationFrameRefs(sourceFile, factory, context) {
|
|
2929
|
+
const frameCall = (node, name) => ts.isCallExpression(node) && (
|
|
2930
|
+
ts.isIdentifier(node.expression) && node.expression.text === name ||
|
|
2931
|
+
ts.isPropertyAccessExpression(node.expression) && ts.isIdentifier(node.expression.expression) && node.expression.expression.text === "window" && node.expression.name.text === name
|
|
2932
|
+
)
|
|
2933
|
+
const unshadowedFrameCall = (node, owner) => {
|
|
2934
|
+
const name = ts.isIdentifier(node.expression) ? node.expression : ts.isPropertyAccessExpression(node.expression) && ts.isIdentifier(node.expression.expression) ? node.expression.expression : undefined
|
|
2935
|
+
return name && !isShadowedIdentifier(name, owner) && !sourceFile.statements.some(statement => statementDeclaresName(statement, name.text) || ts.isImportDeclaration(statement) && importDeclarationNames(statement).includes(name.text))
|
|
2936
|
+
}
|
|
2937
|
+
const currentAccess = (node, name) => ts.isPropertyAccessExpression(node) && ts.isIdentifier(node.expression) && node.expression.text === name && node.name.text === "current"
|
|
2938
|
+
const inside = (node, root) => {
|
|
2939
|
+
for (let current = node; current; current = current.parent) if (current === root) return true
|
|
2940
|
+
return false
|
|
2941
|
+
}
|
|
2942
|
+
const directOrGuarded = (statement, body, name, negated) => {
|
|
2943
|
+
if (statement.parent === body) return true
|
|
2944
|
+
let branch = statement
|
|
2945
|
+
if (ts.isBlock(statement.parent) && statement.parent.statements.length === 1) branch = statement.parent
|
|
2946
|
+
const conditional = branch.parent
|
|
2947
|
+
if (!ts.isIfStatement(conditional) || conditional.thenStatement !== branch || conditional.parent !== body || conditional.elseStatement) return false
|
|
2948
|
+
let condition = unwrapExpression(conditional.expression)
|
|
2949
|
+
const isNegated = ts.isPrefixUnaryExpression(condition) && condition.operator === ts.SyntaxKind.ExclamationToken
|
|
2950
|
+
if (isNegated) condition = unwrapExpression(condition.operand)
|
|
2951
|
+
return isNegated === negated && currentAccess(condition, name)
|
|
2952
|
+
}
|
|
2953
|
+
const hasUseRefImport = sourceFile.statements.some(statement => ts.isImportDeclaration(statement) && !statement.importClause?.isTypeOnly && ts.isStringLiteral(statement.moduleSpecifier) && ["react", "@kudzujs/core"].includes(statement.moduleSpecifier.text) && statement.importClause?.namedBindings && ts.isNamedImports(statement.importClause.namedBindings) && statement.importClause.namedBindings.elements.some(entry => !entry.propertyName && entry.name.text === "useRef"))
|
|
2954
|
+
const hasUseEffectImport = sourceFile.statements.some(statement => ts.isImportDeclaration(statement) && !statement.importClause?.isTypeOnly && ts.isStringLiteral(statement.moduleSpecifier) && ["react", "@kudzujs/core"].includes(statement.moduleSpecifier.text) && statement.importClause?.namedBindings && ts.isNamedImports(statement.importClause.namedBindings) && statement.importClause.namedBindings.elements.some(entry => !entry.propertyName && entry.name.text === "useEffect"))
|
|
2955
|
+
const replacements = new Set()
|
|
2956
|
+
const inspect = node => {
|
|
2957
|
+
if (!hasUseRefImport || !ts.isVariableDeclaration(node) || !ts.isIdentifier(node.name) || !node.initializer || !ts.isCallExpression(node.initializer) || !ts.isIdentifier(node.initializer.expression) || node.initializer.expression.text !== "useRef" || isShadowedIdentifier(node.initializer.expression, sourceFile) || node.initializer.arguments.length !== 1 || !ts.isNumericLiteral(node.initializer.arguments[0]) || Number(node.initializer.arguments[0].text) !== 0) {
|
|
2958
|
+
ts.forEachChild(node, inspect)
|
|
2959
|
+
return
|
|
2960
|
+
}
|
|
2961
|
+
const owner = nearestFunction(node)
|
|
2962
|
+
if (!owner?.body || !ts.isBlock(owner.body)) return
|
|
2963
|
+
const accesses = []
|
|
2964
|
+
const collect = current => {
|
|
2965
|
+
if (currentAccess(current, node.name.text) && !isShadowedIdentifier(current.expression, owner.body)) accesses.push(current)
|
|
2966
|
+
ts.forEachChild(current, collect)
|
|
2967
|
+
}
|
|
2968
|
+
collect(owner.body)
|
|
2969
|
+
const frameAssignments = accesses.filter(access => ts.isBinaryExpression(access.parent) && unwrapExpression(access.parent.left) === access && access.parent.operatorToken.kind === ts.SyntaxKind.EqualsToken && frameCall(unwrapExpression(access.parent.right), "requestAnimationFrame") && unshadowedFrameCall(unwrapExpression(access.parent.right), owner))
|
|
2970
|
+
if (!frameAssignments.length) return
|
|
2971
|
+
const invalidReference = referenceIdentifiers(owner.body, node.name.text).find(reference => !ts.isPropertyAccessExpression(reference.parent) || reference.parent.expression !== reference || reference.parent.name.text !== "current")
|
|
2972
|
+
if (invalidReference) throw sourceNodeError(invalidReference, sourceFile, "Animation frame refs may only use direct .current reads and assignments")
|
|
2973
|
+
const statement = node.parent?.parent
|
|
2974
|
+
const topLevelOwner = owner.parent === sourceFile || ts.isVariableDeclaration(owner.parent) && owner.parent.parent?.parent?.parent === sourceFile
|
|
2975
|
+
if (!topLevelOwner || !ts.isVariableStatement(statement) || !(statement.declarationList.flags & ts.NodeFlags.Const) || statement.declarationList.declarations.length !== 1 || statement.parent !== owner.body) throw sourceNodeError(node, sourceFile, "Animation frame refs must be one top-level component const")
|
|
2976
|
+
if (frameAssignments.length !== 1) throw sourceNodeError(node, sourceFile, "Animation frame refs require one direct ref.current = requestAnimationFrame(callback) assignment")
|
|
2977
|
+
const frame = unwrapExpression(frameAssignments[0].parent.right)
|
|
2978
|
+
const frameCallback = frame.arguments.length === 1 && ts.isIdentifier(unwrapExpression(frame.arguments[0])) ? unwrapExpression(frame.arguments[0]) : undefined
|
|
2979
|
+
const effectCalls = owner.body.statements.flatMap(statement => hasUseEffectImport && ts.isExpressionStatement(statement) && ts.isCallExpression(statement.expression) && ts.isIdentifier(statement.expression.expression) && statement.expression.expression.text === "useEffect" && !isShadowedIdentifier(statement.expression.expression, sourceFile) ? [statement.expression] : [])
|
|
2980
|
+
const effects = effectCalls.filter(effect => effect.arguments[0] && inside(frameAssignments[0], effect.arguments[0]))
|
|
2981
|
+
if (effects.length !== 1) throw sourceNodeError(node, sourceFile, "Animation frame refs must belong to one inline component effect")
|
|
2982
|
+
const effect = effects[0]
|
|
2983
|
+
const callback = effect.arguments[0]
|
|
2984
|
+
if (!(ts.isArrowFunction(callback) || ts.isFunctionExpression(callback)) || !ts.isBlock(callback.body)) throw sourceNodeError(callback, sourceFile, "Animation frame refs require one inline block-bodied effect")
|
|
2985
|
+
if (accesses.some(access => !inside(access, callback))) throw sourceNodeError(node, sourceFile, "Animation frame refs may only be used inside their owning effect")
|
|
2986
|
+
const callbacks = new Map()
|
|
2987
|
+
for (const statement of callback.body.statements) {
|
|
2988
|
+
if (ts.isVariableStatement(statement)) for (const declaration of statement.declarationList.declarations) {
|
|
2989
|
+
if (ts.isIdentifier(declaration.name) && declaration.initializer && (ts.isArrowFunction(declaration.initializer) || ts.isFunctionExpression(declaration.initializer))) callbacks.set(declaration.name.text, declaration.initializer)
|
|
2990
|
+
}
|
|
2991
|
+
if (ts.isFunctionDeclaration(statement) && statement.name && statement.body) callbacks.set(statement.name.text, statement)
|
|
2992
|
+
}
|
|
2993
|
+
const frameOwner = nearestFunction(frameAssignments[0])
|
|
2994
|
+
const shadowedCallback = frameCallback && isShadowedIdentifier(frameCallback, callback.body)
|
|
2995
|
+
const update = frameCallback && !shadowedCallback && callbacks.get(frameCallback.text)
|
|
2996
|
+
if (!update) throw sourceNodeError(frame, sourceFile, "Animation frame refs require a direct local callback")
|
|
2997
|
+
const frameStatement = frameAssignments[0].parent.parent
|
|
2998
|
+
if (!frameOwner || ![...callbacks.values()].includes(frameOwner) || !ts.isBlock(frameOwner.body) || !ts.isExpressionStatement(frameStatement) || !directOrGuarded(frameStatement, frameOwner.body, node.name.text, true)) throw sourceNodeError(frameAssignments[0], sourceFile, "Animation frame requests must be assigned directly inside one local scheduler")
|
|
2999
|
+
const resetAssignments = accesses.filter(access => ts.isBinaryExpression(access.parent) && unwrapExpression(access.parent.left) === access && access.parent.operatorToken.kind === ts.SyntaxKind.EqualsToken && ts.isNumericLiteral(unwrapExpression(access.parent.right)) && Number(unwrapExpression(access.parent.right).text) === 0)
|
|
3000
|
+
const resetStatement = resetAssignments[0]?.parent.parent
|
|
3001
|
+
if (resetAssignments.length !== 1 || nearestFunction(resetAssignments[0]) !== update || !ts.isBlock(update.body) || !ts.isExpressionStatement(resetStatement) || resetStatement.parent !== update.body) throw sourceNodeError(update, sourceFile, "Animation frame callbacks must directly reset their ref to 0")
|
|
3002
|
+
const writes = accesses.filter(access => ts.isBinaryExpression(access.parent) && unwrapExpression(access.parent.left) === access && access.parent.operatorToken.kind >= ts.SyntaxKind.FirstAssignment && access.parent.operatorToken.kind <= ts.SyntaxKind.LastAssignment)
|
|
3003
|
+
if (writes.length !== 2) throw sourceNodeError(node, sourceFile, "Animation frame refs may only be assigned by their request and reset operations")
|
|
3004
|
+
const returns = effectReturns(callback)
|
|
3005
|
+
const cancellations = []
|
|
3006
|
+
const collectCancellations = current => {
|
|
3007
|
+
const argument = current.arguments?.[0] && unwrapExpression(current.arguments[0])
|
|
3008
|
+
if (frameCall(current, "cancelAnimationFrame") && unshadowedFrameCall(current, owner) && current.arguments.length === 1 && currentAccess(argument, node.name.text) && !isShadowedIdentifier(argument.expression, owner.body)) cancellations.push(current)
|
|
3009
|
+
ts.forEachChild(current, collectCancellations)
|
|
3010
|
+
}
|
|
3011
|
+
collectCancellations(callback.body)
|
|
3012
|
+
const cancellation = cancellations[0]
|
|
3013
|
+
const cleanup = cancellation && returns.cleanups.find(candidate => nearestFunction(cancellation) === candidate)
|
|
3014
|
+
const cancellationStatement = cancellation?.parent
|
|
3015
|
+
if (cancellations.length !== 1 || !cleanup || !ts.isBlock(cleanup.body) || !ts.isExpressionStatement(cancellationStatement) || !directOrGuarded(cancellationStatement, cleanup.body, node.name.text, false)) throw sourceNodeError(node, sourceFile, "Animation frame refs require direct cancellation in effect cleanup")
|
|
3016
|
+
replacements.add(node)
|
|
3017
|
+
}
|
|
3018
|
+
inspect(sourceFile)
|
|
3019
|
+
if (!replacements.size) return sourceFile
|
|
3020
|
+
const visitor = node => {
|
|
3021
|
+
if (replacements.has(node)) return factory.updateVariableDeclaration(node, node.name, node.exclamationToken, node.type, factory.createObjectLiteralExpression([
|
|
3022
|
+
factory.createPropertyAssignment("current", factory.createNumericLiteral(0))
|
|
3023
|
+
]))
|
|
3024
|
+
return ts.visitEachChild(node, visitor, context)
|
|
3025
|
+
}
|
|
3026
|
+
return ts.visitNode(sourceFile, visitor)
|
|
3027
|
+
}
|
|
3028
|
+
|
|
2747
3029
|
function createKudzuTransformer(nativeHandlers, effectHandlers, reactiveBindings, listExpressions, handlerUrl, file, sourceFiles, sourceIndex, staticFiles, importedAssets, cssModules, base, clientImports, workerReferences) {
|
|
2748
3030
|
return context => sourceFile => {
|
|
2749
3031
|
const factory = context.factory
|
|
@@ -2756,8 +3038,14 @@ function createKudzuTransformer(nativeHandlers, effectHandlers, reactiveBindings
|
|
|
2756
3038
|
ts.setParentRecursive(sourceFile, false)
|
|
2757
3039
|
sourceFile = normalizeClsxSyntax(sourceFile, factory, context)
|
|
2758
3040
|
ts.setParentRecursive(sourceFile, false)
|
|
3041
|
+
sourceFile = normalizeMediaQueryExternalStores(sourceFile, factory, context)
|
|
3042
|
+
ts.setParentRecursive(sourceFile, false)
|
|
2759
3043
|
sourceFile = normalizeReactMigrationSyntax(sourceFile, factory, context, importedCollections)
|
|
2760
3044
|
ts.setParentRecursive(sourceFile, false)
|
|
3045
|
+
sourceFile = normalizeNavigatorCapabilityConditions(sourceFile, factory, context)
|
|
3046
|
+
ts.setParentRecursive(sourceFile, false)
|
|
3047
|
+
sourceFile = normalizeEffectAnimationFrameRefs(sourceFile, factory, context)
|
|
3048
|
+
ts.setParentRecursive(sourceFile, false)
|
|
2761
3049
|
sourceFile = normalizeCustomHookTimerRefs(sourceFile, factory, context)
|
|
2762
3050
|
const customHookTimerStates = customHookTimerStatesBySource.get(sourceFile) ?? new Set()
|
|
2763
3051
|
ts.setParentRecursive(sourceFile, false)
|
|
@@ -2786,8 +3074,14 @@ function createKudzuTransformer(nativeHandlers, effectHandlers, reactiveBindings
|
|
|
2786
3074
|
ts.setParentRecursive(imported, false)
|
|
2787
3075
|
imported = normalizeClsxSyntax(imported, factory, context)
|
|
2788
3076
|
ts.setParentRecursive(imported, false)
|
|
3077
|
+
imported = normalizeMediaQueryExternalStores(imported, factory, context)
|
|
3078
|
+
ts.setParentRecursive(imported, false)
|
|
2789
3079
|
imported = normalizeReactMigrationSyntax(imported, factory, context, importedSerializableCollectionNames(imported, target, sourceFiles, sourceIndex))
|
|
2790
3080
|
ts.setParentRecursive(imported, false)
|
|
3081
|
+
imported = normalizeNavigatorCapabilityConditions(imported, factory, context)
|
|
3082
|
+
ts.setParentRecursive(imported, false)
|
|
3083
|
+
imported = normalizeEffectAnimationFrameRefs(imported, factory, context)
|
|
3084
|
+
ts.setParentRecursive(imported, false)
|
|
2791
3085
|
imported = normalizeCustomHookTimerRefs(imported, factory, context)
|
|
2792
3086
|
importedTimerStates.set(target, customHookTimerStatesBySource.get(imported) ?? new Set())
|
|
2793
3087
|
ts.setParentRecursive(imported, false)
|
|
@@ -5433,7 +5727,7 @@ function compileOptimizedEvent(expression, setters, factory) {
|
|
|
5433
5727
|
}
|
|
5434
5728
|
|
|
5435
5729
|
const nativeGlobals = new Set([
|
|
5436
|
-
"Array", "ArrayBuffer", "BigInt", "Blob", "Boolean", "Date", "Error", "Event", "FileReader", "FormData", "Infinity", "Intl", "JSON", "Map", "Math", "NaN", "Number", "Object", "Promise", "Proxy", "RangeError", "ReferenceError", "Reflect", "RegExp", "Set", "String", "Symbol", "TypeError", "URL", "URLSearchParams", "WeakMap", "WeakSet", "WebSocket", "Worker", "alert", "atob", "btoa", "clearInterval", "clearTimeout", "console", "crypto", "document", "fetch", "globalThis", "history", "isFinite", "isNaN", "localStorage", "location", "navigator", "parseFloat", "parseInt", "queueMicrotask", "requestAnimationFrame", "setInterval", "setTimeout", "structuredClone", "undefined", "window"
|
|
5730
|
+
"Array", "ArrayBuffer", "BigInt", "Blob", "Boolean", "Date", "Error", "Event", "FileReader", "FormData", "Infinity", "Intl", "JSON", "Map", "Math", "NaN", "Number", "Object", "Promise", "Proxy", "RangeError", "ReferenceError", "Reflect", "RegExp", "Set", "String", "Symbol", "TypeError", "URL", "URLSearchParams", "WeakMap", "WeakSet", "WebSocket", "Worker", "alert", "atob", "btoa", "cancelAnimationFrame", "clearInterval", "clearTimeout", "console", "crypto", "document", "fetch", "globalThis", "history", "isFinite", "isNaN", "localStorage", "location", "navigator", "parseFloat", "parseInt", "queueMicrotask", "requestAnimationFrame", "setInterval", "setTimeout", "structuredClone", "undefined", "window"
|
|
5437
5731
|
])
|
|
5438
5732
|
|
|
5439
5733
|
function rewriteEffectWorkers(callback, file, sourceFile, sourceFiles, workerReferences, factory, context) {
|
package/framework/core.d.ts
CHANGED
|
@@ -20,6 +20,8 @@ export interface RefObject<T> {
|
|
|
20
20
|
}
|
|
21
21
|
|
|
22
22
|
export function useRef<T>(initialValue: null): RefObject<T>
|
|
23
|
+
export function useRef(initialValue: 0): { current: number }
|
|
24
|
+
export function useSyncExternalStore<T>(subscribe: (callback: () => void) => () => void, getSnapshot: () => T, getServerSnapshot: () => T): T
|
|
23
25
|
|
|
24
26
|
export interface Context<T> {
|
|
25
27
|
Provider: (props: { value: T; children?: unknown }) => unknown
|