@kudzujs/core 0.8.62 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/MIGRATION_ROADMAP.md +36 -1
- package/PERFORMANCE.md +79 -1
- package/README.md +2 -2
- package/RELEASES.md +29 -0
- package/bin/kudzu.mjs +10 -1
- package/docs/next-architecture/0.9-baseline.md +1199 -0
- package/docs/next-architecture/0.9-benchmark-contracts.md +507 -0
- package/docs/next-architecture/0.9-component-property-contract.md +89 -0
- package/docs/next-architecture/0.9-compression-ledger.md +227 -0
- package/docs/next-architecture/0.9-final-proof-audit.md +176 -0
- package/docs/next-architecture/0.9-implementation-plan.md +1819 -0
- package/docs/next-architecture/0.9-resource-lifecycle.md +118 -0
- package/docs/next-architecture/0.9-semantic-compression.md +384 -0
- package/docs/next-architecture/README.md +16 -12
- package/docs/next-architecture/compiler-current-architecture.md +7 -7
- package/docs/next-architecture/large-application-ai-native-roadmap.md +5 -3
- package/docs/next-architecture/versioning.md +1 -1
- package/framework/README.md +2 -0
- package/framework/binding-runtime.js +4 -4
- package/framework/build.mjs +135 -30
- package/framework/compiler/ast-helpers.mjs +5 -0
- package/framework/compiler/browser-signal-passes.mjs +2 -7
- package/framework/compiler/collection-analysis.mjs +4 -0
- package/framework/compiler/descriptor-session.mjs +36 -12
- package/framework/compiler/effect-analysis.mjs +28 -8
- package/framework/compiler/effect-codegen.mjs +79 -36
- package/framework/compiler/effect-private-ref-pass.mjs +4 -8
- package/framework/compiler/handler-lowering.mjs +12 -7
- package/framework/compiler/ir/module-ir.mjs +26 -4
- package/framework/compiler/list-runtime-codegen.mjs +4 -2
- package/framework/compiler/optimize/command-specialization.mjs +4 -7
- package/framework/compiler/route-artifact-report.mjs +4 -3
- package/framework/compiler/route-build-record.mjs +12 -0
- package/framework/compiler/route-capability-planner.mjs +3 -3
- package/framework/compiler/route-ir.mjs +27 -11
- package/framework/compiler/runtime-codegen.mjs +2 -2
- package/framework/compiler/source-compiler.mjs +359 -78
- package/framework/core.d.ts +1 -0
- package/framework/core.mjs +18 -5
- package/framework/dependency-runtime.js +1 -1
- package/framework/effect-runtime.js +2 -2
- package/framework/list-runtime.js +67 -24
- package/framework/native-runtime.js +12 -9
- package/framework/runtime.js +1 -1
- package/framework/serialization.js +13 -6
- package/framework/shared-runtime.js +14 -12
- package/package.json +1 -1
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { readFile, realpath, stat } from "node:fs/promises"
|
|
2
2
|
import { dirname, extname, isAbsolute, join, relative, resolve, sep } from "node:path"
|
|
3
|
+
import { transform, transformSync } from "esbuild"
|
|
3
4
|
import ts from "typescript"
|
|
4
5
|
import { createBindingIndex } from "./analysis/binding-index.mjs"
|
|
5
6
|
import { createComponentAnalysisSession } from "./analysis/component-analysis.mjs"
|
|
@@ -28,16 +29,24 @@ const { root, sourceDirectory, pagesDirectory, workDirectory, workerCompiler, mo
|
|
|
28
29
|
const buildDirectory = project.buildDirectory ?? workDirectory
|
|
29
30
|
const { ordinaryRuntimeDependencies, resolveSourceImport, runtimeModuleReference } = project.graph
|
|
30
31
|
const parseSourceFile = (file, source) => modules.read(file, source).sourceFile
|
|
32
|
+
const importFreeModules = new Map()
|
|
31
33
|
const staticAssetExtensions = new Set([".avif", ".gif", ".ico", ".jpeg", ".jpg", ".otf", ".png", ".svg", ".ttf", ".webp", ".woff", ".woff2"])
|
|
32
34
|
|
|
33
|
-
function
|
|
35
|
+
async function compileSourceAsync(file, sourceFiles, sourceIndex, staticFiles, cssModules, base) {
|
|
36
|
+
const source = sourceIndex.get(file)
|
|
37
|
+
const output = importFreeTypeScriptModule(file, source) ? (await transform(source, { loader: "ts", format: "esm", target: "es2022", sourcefile: file })).code : undefined
|
|
38
|
+
return compileSource(file, sourceFiles, sourceIndex, staticFiles, cssModules, base, output)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function compileSource(file, sourceFiles, sourceIndex, staticFiles, cssModules, base, importFreeOutput) {
|
|
34
42
|
const importedAssets = new Set()
|
|
35
43
|
const source = sourceIndex.get(file)
|
|
36
44
|
const semantic = createSemanticArtifact(relative(root, file).replaceAll(sep, "/"))
|
|
37
45
|
const handlerPath = `handlers/${relative(sourceDirectory, file).replaceAll(sep, "/").replace(/\.(?:ts|tsx)$/, ".js")}`
|
|
38
|
-
const
|
|
46
|
+
const importFreePlain = importFreeTypeScriptModule(file, source)
|
|
47
|
+
const plain = importFreePlain || plainTypeScriptModule(file, source, sourceFiles)
|
|
39
48
|
if (plain && counters) counters.plainModules = (counters.plainModules ?? 0) + 1
|
|
40
|
-
const result = ts.transpileModule(source, {
|
|
49
|
+
const result = importFreePlain ? { outputText: importFreeOutput ?? transformSync(source, { loader: "ts", format: "esm", target: "es2022", sourcefile: file }).code } : ts.transpileModule(source, {
|
|
41
50
|
fileName: file,
|
|
42
51
|
compilerOptions: {
|
|
43
52
|
target: ts.ScriptTarget.ES2022,
|
|
@@ -87,6 +96,15 @@ function plainTypeScriptModule(file, source, sourceFiles) {
|
|
|
87
96
|
return true
|
|
88
97
|
}
|
|
89
98
|
|
|
99
|
+
function importFreeTypeScriptModule(file, source) {
|
|
100
|
+
if (!file.endsWith(".ts")) return false
|
|
101
|
+
const cached = importFreeModules.get(file)
|
|
102
|
+
if (cached?.source === source) return cached.result
|
|
103
|
+
const result = !source.includes(".worker.ts") && ts.preProcessFile(source, true, true).importedFiles.length === 0
|
|
104
|
+
importFreeModules.set(file, { source, result })
|
|
105
|
+
return result
|
|
106
|
+
}
|
|
107
|
+
|
|
90
108
|
function createPlainModuleTransformer(file, sourceFiles) {
|
|
91
109
|
return context => sourceFile => context.factory.updateSourceFile(sourceFile, sourceFile.statements.map(statement => {
|
|
92
110
|
if ((!ts.isImportDeclaration(statement) && !ts.isExportDeclaration(statement)) || !runtimeModuleReference(statement) || !statement.moduleSpecifier || !ts.isStringLiteral(statement.moduleSpecifier) || !statement.moduleSpecifier.text.startsWith(".")) return statement
|
|
@@ -126,7 +144,9 @@ function reachableSourceFiles(entries, sourceFiles, sourceIndex) {
|
|
|
126
144
|
if (visited.has(file)) continue
|
|
127
145
|
visited.add(file)
|
|
128
146
|
reachable.add(file)
|
|
129
|
-
const
|
|
147
|
+
const source = sourceIndex.get(file)
|
|
148
|
+
if (importFreeTypeScriptModule(file, source)) continue
|
|
149
|
+
const sourceFile = parseSourceFile(file, source)
|
|
130
150
|
if (owner === "ordinary") for (const target of ordinaryRuntimeDependencies(file, sourceFile, sourceFiles, isStaticImport)) queue.push({ file: target, owner: target.endsWith(".worker.ts") ? "worker" : owner })
|
|
131
151
|
const visit = node => {
|
|
132
152
|
if (owner === "worker") {
|
|
@@ -332,7 +352,7 @@ function createKudzuTransformer({ semantic, handlerUrl, file, sourceFiles, sourc
|
|
|
332
352
|
const { moduleIR } = semantic
|
|
333
353
|
return context => sourceFile => {
|
|
334
354
|
const hasLinkElements = /<link/i.test(sourceFile.text)
|
|
335
|
-
const importedStaticCollections = importedSerializableCollections(sourceFile, file, sourceFiles, sourceIndex)
|
|
355
|
+
const importedStaticCollections = importedSerializableCollections(sourceFile, file, sourceFiles, sourceIndex, true)
|
|
336
356
|
const importedCollections = new Set(importedStaticCollections.keys())
|
|
337
357
|
const normalized = normalizeCompilerSource(sourceFile, { base, context, file, importedCollections, importedStaticCollections, sourceFiles, sourceIndex })
|
|
338
358
|
sourceFile = normalized.sourceFile
|
|
@@ -395,8 +415,16 @@ function createKudzuTransformer({ semantic, handlerUrl, file, sourceFiles, sourc
|
|
|
395
415
|
}
|
|
396
416
|
const importedCollectionTransforms = new Map()
|
|
397
417
|
const importedCalculationFunctions = new Map()
|
|
418
|
+
const validatedEffectCalculations = new WeakSet()
|
|
419
|
+
const transformImports = new Set()
|
|
420
|
+
const collectTransformImports = node => {
|
|
421
|
+
if (ts.isCallExpression(node) && ts.isIdentifier(node.expression)) transformImports.add(node.expression.text)
|
|
422
|
+
if ((ts.isJsxOpeningElement(node) || ts.isJsxSelfClosingElement(node)) && ts.isIdentifier(node.tagName)) transformImports.add(node.tagName.text)
|
|
423
|
+
ts.forEachChild(node, collectTransformImports)
|
|
424
|
+
}
|
|
425
|
+
collectTransformImports(sourceFile)
|
|
398
426
|
for (const [name, binding] of importBindings) {
|
|
399
|
-
if (binding.kind === "namespace") continue
|
|
427
|
+
if (binding.kind === "namespace" || !transformImports.has(name)) continue
|
|
400
428
|
try {
|
|
401
429
|
importedCollectionTransforms.set(name, resolveComponentExport(binding.target, binding.kind === "default" ? "default" : binding.imported, target => parseSourceFile(target, sourceIndex.get(target)), sourceFiles))
|
|
402
430
|
} catch {}
|
|
@@ -642,7 +670,8 @@ function createKudzuTransformer({ semantic, handlerUrl, file, sourceFiles, sourc
|
|
|
642
670
|
const delay = node.initializer.arguments[1] && unwrapExpression(node.initializer.arguments[1])
|
|
643
671
|
const setters = owner ? settersByFunction.get(owner) ?? new Map() : new Map()
|
|
644
672
|
if (!owner) throw sourceNodeError(node, sourceFile, "Parameterized debounce hooks cannot be used outside a Kudzu component")
|
|
645
|
-
|
|
673
|
+
const stateInitializer = ts.isIdentifier(value) ? directStateInitializer(owner, value.text) : undefined
|
|
674
|
+
if (node.initializer.arguments.length !== 2 || !ts.isIdentifier(value) || !new Set(setters.values()).has(value.text) || !stateInitializer || !isPrimitiveDefaultLiteral(stateInitializer)) throw sourceNodeError(node.initializer, sourceFile, "Parameterized debounce hooks require one direct primitive state argument")
|
|
646
675
|
if (!ts.isNumericLiteral(delay)) throw sourceNodeError(node.initializer.arguments[1] ?? node.initializer, sourceFile, "Parameterized debounce hook delays must be numeric literals")
|
|
647
676
|
const syntheticSetter = `__kSetDebounced_${Math.max(0, node.pos)}`
|
|
648
677
|
setters.set(syntheticSetter, node.name.text)
|
|
@@ -670,6 +699,7 @@ function createKudzuTransformer({ semantic, handlerUrl, file, sourceFiles, sourc
|
|
|
670
699
|
}
|
|
671
700
|
}
|
|
672
701
|
const contextSubstitutions = new Map()
|
|
702
|
+
const contextSharedStates = new Map()
|
|
673
703
|
for (const [setter, state] of hook.states) {
|
|
674
704
|
if (hook.context) {
|
|
675
705
|
if (names.has(setter) && !names.has(state)) throw sourceNodeError(node.name, sourceFile, `Relative Context setter ${JSON.stringify(setter)} requires state ${JSON.stringify(state)} to be destructured`)
|
|
@@ -689,6 +719,9 @@ function createKudzuTransformer({ semantic, handlerUrl, file, sourceFiles, sourc
|
|
|
689
719
|
const localSetter = localName(setter)
|
|
690
720
|
setters.set(localSetter, localState)
|
|
691
721
|
registerState(owner, localState, localSetter, "context", node, { owner: hook.stateOwner, state: hook.stateSymbols.get(state) })
|
|
722
|
+
const sharedState = registerSharedState(moduleIR, { identity: hook.stateOwner.symbol.id, field: state })
|
|
723
|
+
contextSharedStates.set(state, sharedState)
|
|
724
|
+
stateOwnersByFunction.get(owner).set(localState, { kind: "shared-state", sharedState: sharedState.slot })
|
|
692
725
|
if (requiredContextStates.has(state)) {
|
|
693
726
|
for (const [field, local] of [[state, localState], [setter, localSetter]]) {
|
|
694
727
|
if (names.has(field) || privateFields.some(entry => (typeof entry === "string" ? entry : entry.property) === field)) continue
|
|
@@ -723,7 +756,16 @@ function createKudzuTransformer({ semantic, handlerUrl, file, sourceFiles, sourc
|
|
|
723
756
|
if (hook.context) {
|
|
724
757
|
const reducers = reducersByFunction.get(owner) ?? new Map()
|
|
725
758
|
const states = new Map([...hook.states].map(([setter, state]) => [contextSubstitutions.get(setter)?.text ?? setter, contextSubstitutions.get(state)?.text ?? state]))
|
|
726
|
-
|
|
759
|
+
const referenced = referencedStateNames(hook.callbacks.get(name).body, hook.states, hook.callbacks.get(name))
|
|
760
|
+
const anchor = [...hook.states.values()].find(state => referenced.has(state))
|
|
761
|
+
const sharedState = contextSharedStates.get(anchor)
|
|
762
|
+
if (!sharedState) {
|
|
763
|
+
reducers.set(name, { sourceKind: "Context", directImplementation: callback, states })
|
|
764
|
+
reducersByFunction.set(owner, reducers)
|
|
765
|
+
continue
|
|
766
|
+
}
|
|
767
|
+
const action = registerSharedAction(moduleIR, { state: sharedState.slot, name })
|
|
768
|
+
reducers.set(name, { state: contextSubstitutions.get(anchor)?.text ?? anchor, sourceKind: "Context", sharedAction: { ...action, directImplementation: callback, states } })
|
|
727
769
|
reducersByFunction.set(owner, reducers)
|
|
728
770
|
}
|
|
729
771
|
}
|
|
@@ -960,6 +1002,78 @@ function createKudzuTransformer({ semantic, handlerUrl, file, sourceFiles, sourc
|
|
|
960
1002
|
if (!returns.length || returns.some(returned => !returned || !ts.isObjectLiteralExpression(returned))) fail(call.expression, "Reactive imported calculations must return a plain object")
|
|
961
1003
|
const fieldExists = returns.every(returned => returned.properties.some(property => ts.isSpreadAssignment(property) || !ts.isComputedPropertyName(property.name) && property.name.text === field))
|
|
962
1004
|
if (!fieldExists) fail(call.parent, `Reactive imported calculation does not return field ${JSON.stringify(field)}`)
|
|
1005
|
+
return { calculation, returns }
|
|
1006
|
+
}
|
|
1007
|
+
const validateSelectedEffectCalculation = (call, field) => {
|
|
1008
|
+
const { calculation, returns } = validateImportedCalculation(call, field)
|
|
1009
|
+
const calculationSource = calculation.getSourceFile()
|
|
1010
|
+
const reject = (target, message) => { throw sourceNodeError(target, calculationSource, message) }
|
|
1011
|
+
if (calculation.asteriskToken || calculation.modifiers?.some(modifier => modifier.kind === ts.SyntaxKind.AsyncKeyword)) reject(calculation, "Imported calculations used by useEffect() must be synchronous; move async work into the owned effect")
|
|
1012
|
+
if (calculation.parameters.some(parameter => !ts.isIdentifier(parameter.name) || parameter.dotDotDotToken || parameter.initializer || parameter.questionToken)) reject(calculation, "Imported calculations used by useEffect() require ordinary identifier parameters without defaults or rest")
|
|
1013
|
+
if (call.arguments.some(argument => !ts.isIdentifier(unwrapExpression(argument)))) fail(call, "useEffect() selected calculation arguments must be direct primitive state identifiers; aliases, property paths, and composed arguments are not supported")
|
|
1014
|
+
const shapes = returns.map(returned => returned.properties.map(property => {
|
|
1015
|
+
if (!ts.isPropertyAssignment(property) && !ts.isShorthandPropertyAssignment(property) || ts.isComputedPropertyName(property.name) || ["__proto__", "constructor", "prototype"].includes(property.name.text)) reject(property, "Imported calculations used by useEffect() must return direct safe plain-object fields without spreads, methods, or computed names")
|
|
1016
|
+
return property.name.text
|
|
1017
|
+
}).sort())
|
|
1018
|
+
const expected = JSON.stringify(shapes[0])
|
|
1019
|
+
if (shapes.some(shape => JSON.stringify(shape) !== expected)) reject(calculation, `Imported calculations used by useEffect() must return the same direct plain-object fields on every path; expected ${expected}`)
|
|
1020
|
+
if (validatedEffectCalculations.has(calculation)) return
|
|
1021
|
+
const staticImports = importedSerializableCollections(calculationSource, calculationSource.fileName, sourceFiles, sourceIndex)
|
|
1022
|
+
const imported = new Map()
|
|
1023
|
+
for (const statement of calculationSource.statements) if (ts.isImportDeclaration(statement) && statement.importClause && !statement.importClause.isTypeOnly && ts.isStringLiteral(statement.moduleSpecifier)) {
|
|
1024
|
+
const names = []
|
|
1025
|
+
if (statement.importClause.name) names.push(statement.importClause.name.text)
|
|
1026
|
+
const bindings = statement.importClause.namedBindings
|
|
1027
|
+
if (bindings && ts.isNamespaceImport(bindings)) names.push(bindings.name.text)
|
|
1028
|
+
if (bindings && ts.isNamedImports(bindings)) names.push(...bindings.elements.filter(entry => !entry.isTypeOnly).map(entry => entry.name.text))
|
|
1029
|
+
for (const name of names) imported.set(name, statement.moduleSpecifier.text)
|
|
1030
|
+
}
|
|
1031
|
+
for (const [name, target] of imported) if (!target.startsWith(".") && referencesIdentifier(calculation.body, name)) reject(calculation.body, `Imported calculations used by useEffect() cannot reference package import ${JSON.stringify(name)} from ${JSON.stringify(target)}`)
|
|
1032
|
+
const localNames = new Set(calculation.parameters.flatMap(parameter => bindingNames(parameter.name)))
|
|
1033
|
+
if (ts.isBlock(calculation.body)) for (const statement of calculation.body.statements) {
|
|
1034
|
+
if (ts.isVariableStatement(statement)) for (const declaration of statement.declarationList.declarations) for (const name of bindingNames(declaration.name)) localNames.add(name)
|
|
1035
|
+
if (ts.isFunctionDeclaration(statement) && statement.name) localNames.add(statement.name.text)
|
|
1036
|
+
}
|
|
1037
|
+
const localDeclarations = new Map()
|
|
1038
|
+
if (ts.isBlock(calculation.body)) for (const statement of calculation.body.statements) if (ts.isVariableStatement(statement)) for (const declaration of statement.declarationList.declarations) if (ts.isIdentifier(declaration.name) && declaration.initializer) localDeclarations.set(declaration.name.text, declaration.initializer)
|
|
1039
|
+
const visiting = []
|
|
1040
|
+
const visited = new Set()
|
|
1041
|
+
const visitLocal = name => {
|
|
1042
|
+
if (visited.has(name)) return
|
|
1043
|
+
const cycle = visiting.indexOf(name)
|
|
1044
|
+
if (cycle >= 0) reject(localDeclarations.get(name), `Imported calculation derived-local cycle: ${[...visiting.slice(cycle), name].join(" -> ")}`)
|
|
1045
|
+
visiting.push(name)
|
|
1046
|
+
const initializer = localDeclarations.get(name)
|
|
1047
|
+
if (initializer) for (const candidate of localDeclarations.keys()) if (referencesIdentifier(initializer, candidate)) visitLocal(candidate)
|
|
1048
|
+
visiting.pop()
|
|
1049
|
+
visited.add(name)
|
|
1050
|
+
}
|
|
1051
|
+
for (const name of localDeclarations.keys()) visitLocal(name)
|
|
1052
|
+
const visit = node => {
|
|
1053
|
+
if (ts.isTypeNode(node)) return
|
|
1054
|
+
if (ts.isBinaryExpression(node) && assignmentOperators.has(node.operatorToken.kind) || ts.isPostfixUnaryExpression(node) || ts.isPrefixUnaryExpression(node) && [ts.SyntaxKind.PlusPlusToken, ts.SyntaxKind.MinusMinusToken].includes(node.operator) || ts.isDeleteExpression(node)) reject(node, "Imported calculations used by useEffect() must be pure; assignments, updates, delete, and mutation are not supported")
|
|
1055
|
+
if (ts.isAwaitExpression(node) || ts.isYieldExpression(node) || ts.isNewExpression(node)) reject(node, "Imported calculations used by useEffect() must be deterministic and synchronous; await, yield, and construction are not supported")
|
|
1056
|
+
if (ts.isCallExpression(node)) {
|
|
1057
|
+
if (ts.isIdentifier(node.expression) && ["Boolean", "Number", "String"].includes(node.expression.text)) {
|
|
1058
|
+
// Primitive conversions are deterministic.
|
|
1059
|
+
} else if (ts.isPropertyAccessExpression(node.expression)) {
|
|
1060
|
+
const method = node.expression.name.text
|
|
1061
|
+
const receiver = unwrapExpression(node.expression.expression)
|
|
1062
|
+
if (mutatingListMethods.has(method)) reject(node, `Imported calculations used by useEffect() must be pure; mutating method ${JSON.stringify(method)} is not supported`)
|
|
1063
|
+
const math = ts.isIdentifier(receiver) && receiver.text === "Math"
|
|
1064
|
+
const find = method === "find" && node.arguments.length === 1 && ts.isArrowFunction(node.arguments[0]) && !ts.isBlock(node.arguments[0].body) && !node.arguments[0].modifiers?.some(modifier => modifier.kind === ts.SyntaxKind.AsyncKeyword)
|
|
1065
|
+
if (!find && !(math && pureMathMethods.has(method)) && !pureListMethods.has(method)) reject(node, `Imported calculations used by useEffect() must be deterministic; call ${JSON.stringify(node.expression.getText(calculationSource))} is not supported`)
|
|
1066
|
+
} else reject(node, "Imported calculations used by useEffect() cannot call arbitrary functions")
|
|
1067
|
+
}
|
|
1068
|
+
if (ts.isIdentifier(node) && isReferenceIdentifier(node)) {
|
|
1069
|
+
const target = imported.get(node.text)
|
|
1070
|
+
if (target && !target.startsWith(".")) reject(node, `Imported calculations used by useEffect() cannot reference package import ${JSON.stringify(node.text)} from ${JSON.stringify(target)}`)
|
|
1071
|
+
if (target?.startsWith(".") && !staticImports.has(node.text)) reject(node, `Imported calculations used by useEffect() may capture only relative exported JSON-safe const arrays; capture ${JSON.stringify(node.text)} is opaque or nonserializable`)
|
|
1072
|
+
}
|
|
1073
|
+
ts.forEachChild(node, visit)
|
|
1074
|
+
}
|
|
1075
|
+
visit(calculation.body)
|
|
1076
|
+
validatedEffectCalculations.add(calculation)
|
|
963
1077
|
}
|
|
964
1078
|
const validateReactiveJsxExpression = (expression, allowedNames) => {
|
|
965
1079
|
const value = unwrapExpression(expression)
|
|
@@ -1033,6 +1147,7 @@ function createKudzuTransformer({ semantic, handlerUrl, file, sourceFiles, sourc
|
|
|
1033
1147
|
}
|
|
1034
1148
|
const componentSpecializations = new WeakMap()
|
|
1035
1149
|
const specializedEffectStateOwners = new WeakMap()
|
|
1150
|
+
const calculationDerivedByOwner = new WeakMap()
|
|
1036
1151
|
const setterHookHelpers = new WeakMap()
|
|
1037
1152
|
const expandedRowSpecializations = new WeakMap()
|
|
1038
1153
|
const nestedRowSpecializations = new Map()
|
|
@@ -1063,7 +1178,9 @@ function createKudzuTransformer({ semantic, handlerUrl, file, sourceFiles, sourc
|
|
|
1063
1178
|
props: result.props.map(prop => {
|
|
1064
1179
|
const expression = result.propExpressions.get(prop.name)
|
|
1065
1180
|
const signals = expression ? propSignals(expression) : []
|
|
1066
|
-
|
|
1181
|
+
const uses = result.propertyUses.get(prop.local) ?? []
|
|
1182
|
+
const properties = signals.length === 1 ? uses.map(use => ({ signal: signals[0], path: use.path, consumers: use.consumers, equality: "object-is" })) : []
|
|
1183
|
+
return { ...prop, ...(signals.length ? { signals } : {}), ...(properties.length ? { properties } : {}) }
|
|
1067
1184
|
}),
|
|
1068
1185
|
states: [
|
|
1069
1186
|
...result.rowStates.map(({ state, setter, source }) => ({ name: state, setter, kind: "row", ...(analysisSite(source, "hook") ? { site: analysisSite(source, "hook") } : {}), ...(analysisSource(source) ? { source: analysisSource(source) } : {}) })),
|
|
@@ -1141,6 +1258,44 @@ function createKudzuTransformer({ semantic, handlerUrl, file, sourceFiles, sourc
|
|
|
1141
1258
|
merged.parent = root.parent
|
|
1142
1259
|
return merged
|
|
1143
1260
|
}
|
|
1261
|
+
const materializeComponentHelper = (call, specialization, prefix, prepend) => {
|
|
1262
|
+
const owner = nearestFunction(call)
|
|
1263
|
+
const name = `${prefix}${Math.max(0, call.pos)}`
|
|
1264
|
+
const effectStatements = specialization.effects.map(entry => {
|
|
1265
|
+
const effectCall = factory.updateCallExpression(entry.call, factory.createIdentifier("__kComponentUseEffect"), entry.call.typeArguments, entry.call.arguments)
|
|
1266
|
+
synthesizeTree(effectCall)
|
|
1267
|
+
ts.setOriginalNode(effectCall, entry.source)
|
|
1268
|
+
specializedEffectStateOwners.set(effectCall, { owner: { kind: "specialization", slot: specialization.analysis.slot }, references: specialization.propStateOwners })
|
|
1269
|
+
return factory.createExpressionStatement(effectCall)
|
|
1270
|
+
})
|
|
1271
|
+
const rendered = prepend ? factory.createNull() : specialization.root
|
|
1272
|
+
const helper = factory.createFunctionDeclaration(undefined, undefined, name, undefined, [], undefined, factory.createBlock([...specialization.hookDeclarations, ...effectStatements, factory.createReturnStatement(rendered)], true))
|
|
1273
|
+
ts.setParentRecursive(helper, false)
|
|
1274
|
+
helper.parent = owner.body
|
|
1275
|
+
const helpers = setterHookHelpers.get(owner.body) ?? []
|
|
1276
|
+
helpers.push(helper)
|
|
1277
|
+
setterHookHelpers.set(owner.body, helpers)
|
|
1278
|
+
const setters = new Map(settersForNode(call, settersByFunction))
|
|
1279
|
+
for (const state of specialization.ordinaryStates) setters.set(state.setter, state.state)
|
|
1280
|
+
settersByFunction.set(helper, setters)
|
|
1281
|
+
const stateOwners = new Map([...stateOwnersForNode(call), ...specialization.propStateOwners])
|
|
1282
|
+
for (const state of specialization.ordinaryStates) stateOwners.set(state.state, state.analysisReference)
|
|
1283
|
+
stateOwnersByFunction.set(helper, stateOwners)
|
|
1284
|
+
usesComponentState ||= specialization.ordinaryStates.length > 0
|
|
1285
|
+
usesComponentId ||= specialization.usesComponentId
|
|
1286
|
+
usesComponentRef ||= specialization.ordinaryRefs.length > 0
|
|
1287
|
+
usesComponentEffects ||= specialization.effects.length > 0
|
|
1288
|
+
const invocation = factory.createJsxSelfClosingElement(factory.createIdentifier(name), undefined, factory.createJsxAttributes([]))
|
|
1289
|
+
specialization.root = prepend ? prependJsxChild(specialization.root, invocation, factory) : invocation
|
|
1290
|
+
ts.setParentRecursive(specialization.root, false)
|
|
1291
|
+
specialization.root.parent = call.parent
|
|
1292
|
+
}
|
|
1293
|
+
const attachStateBackedEffects = (call, specialization, componentSource, imported) => {
|
|
1294
|
+
if (imported) synthesizeTree(specialization.root = mergeSpecializedImports(specialization.root, componentSource, call, specialization.effects))
|
|
1295
|
+
if (!specialization.effects.length) return
|
|
1296
|
+
if (specialization.hookDeclarations.length) fail(call, "State-backed object property components cannot declare state, refs, or IDs")
|
|
1297
|
+
materializeComponentHelper(call, specialization, "KPropertyEffect", true)
|
|
1298
|
+
}
|
|
1144
1299
|
const expandReducerCallbacks = (root, componentSource, call, ownership) => {
|
|
1145
1300
|
const componentImports = clientImportBindings(componentSource, componentSource.fileName, sourceFiles)
|
|
1146
1301
|
const replacements = new WeakMap()
|
|
@@ -1324,8 +1479,10 @@ function createKudzuTransformer({ semantic, handlerUrl, file, sourceFiles, sourc
|
|
|
1324
1479
|
if (identifierReferenceCount(sourceFile, name) !== calls.length) fail(component.declaration, `State-backed list component ${name} may only be referenced as JSX`)
|
|
1325
1480
|
if (stateBackedCalls.length !== calls.length) fail(component.declaration, `State-backed list component ${name} must receive its mapped prop from local state at every call`)
|
|
1326
1481
|
for (const call of stateBackedCalls) {
|
|
1327
|
-
const
|
|
1328
|
-
|
|
1482
|
+
const objectProperty = isStateBackedListComponentCall(call, component.function, settersByFunction.get(nearestFunction(call)) ?? new Map(), true)
|
|
1483
|
+
const specialization = specialize(call, component.function, objectProperty ? "Object-property component" : "Keyed list")
|
|
1484
|
+
if (specialization.effects.length && !objectProperty) fail(call, "State-backed list components cannot declare effects")
|
|
1485
|
+
if (objectProperty) attachStateBackedEffects(call, specialization, component.function.getSourceFile(), false)
|
|
1329
1486
|
componentSpecializations.set(call, specialization)
|
|
1330
1487
|
stateBackedComponentRoots.push(specialization.root)
|
|
1331
1488
|
}
|
|
@@ -1346,8 +1503,10 @@ function createKudzuTransformer({ semantic, handlerUrl, file, sourceFiles, sourc
|
|
|
1346
1503
|
}
|
|
1347
1504
|
const stateBackedCalls = calls.filter(call => isStateBackedListComponentCall(call, component, settersByFunction.get(nearestFunction(call)) ?? new Map()))
|
|
1348
1505
|
for (const call of stateBackedCalls) {
|
|
1349
|
-
const
|
|
1350
|
-
|
|
1506
|
+
const objectProperty = isStateBackedListComponentCall(call, component, settersByFunction.get(nearestFunction(call)) ?? new Map(), true)
|
|
1507
|
+
const specialization = specialize(call, component, objectProperty ? "Object-property component" : "Keyed list")
|
|
1508
|
+
if (specialization.effects.length && !objectProperty) fail(call, "State-backed list components cannot declare effects")
|
|
1509
|
+
if (objectProperty) attachStateBackedEffects(call, specialization, component.getSourceFile(), true)
|
|
1351
1510
|
componentSpecializations.set(call, specialization)
|
|
1352
1511
|
stateBackedComponentRoots.push(specialization.root)
|
|
1353
1512
|
}
|
|
@@ -1364,7 +1523,8 @@ function createKudzuTransformer({ semantic, handlerUrl, file, sourceFiles, sourc
|
|
|
1364
1523
|
const setterAttribute = attributes.properties.find(entry => ts.isJsxAttribute(entry) && entry.name.text === prop)
|
|
1365
1524
|
const stateValue = stateAttribute?.initializer && ts.isJsxExpression(stateAttribute.initializer) ? unwrapExpression(stateAttribute.initializer.expression) : undefined
|
|
1366
1525
|
const setterValue = setterAttribute?.initializer && ts.isJsxExpression(setterAttribute.initializer) ? unwrapExpression(setterAttribute.initializer.expression) : undefined
|
|
1367
|
-
|
|
1526
|
+
const stateInitializer = ts.isIdentifier(stateValue) ? directStateInitializer(nearestFunction(call), stateValue.text) : undefined
|
|
1527
|
+
if (!ts.isIdentifier(stateValue) || !ts.isIdentifier(setterValue) || setters.get(setterValue.text) !== stateValue.text || !stateInitializer || !ts.isArrayLiteralExpression(stateInitializer)) fail(setterAttribute ?? call, `Setter-callback effect prop ${JSON.stringify(prop)} must target the same direct array state passed through ${JSON.stringify(effect.stateProp)}`)
|
|
1368
1528
|
}
|
|
1369
1529
|
const specialization = specialize(call, component, "Setter-callback", true, true, new Set(settersForNode(call, settersByFunction).values()))
|
|
1370
1530
|
if (specialization.hookDeclarations.length || specialization.effects.length) {
|
|
@@ -1382,44 +1542,7 @@ function createKudzuTransformer({ semantic, handlerUrl, file, sourceFiles, sourc
|
|
|
1382
1542
|
}
|
|
1383
1543
|
specialization.root = expandSetterComponents(specialization.root, component.getSourceFile(), [component], specialization, settersForNode(call, settersByFunction), stateOwnersForNode(call))
|
|
1384
1544
|
if (imported) synthesizeTree(specialization.root = mergeSpecializedImports(specialization.root, component.getSourceFile(), call, specialization.effects))
|
|
1385
|
-
if (specialization.hookDeclarations.length || specialization.effects.length)
|
|
1386
|
-
const owner = nearestFunction(call)
|
|
1387
|
-
const name = `KSetterComponent${Math.max(0, call.pos)}`
|
|
1388
|
-
const effectStatements = specialization.effects.map(entry => {
|
|
1389
|
-
const effectCall = factory.updateCallExpression(entry.call, factory.createIdentifier("__kComponentUseEffect"), entry.call.typeArguments, entry.call.arguments)
|
|
1390
|
-
synthesizeTree(effectCall)
|
|
1391
|
-
ts.setOriginalNode(effectCall, entry.source)
|
|
1392
|
-
specializedEffectStateOwners.set(effectCall, { owner: { kind: "specialization", slot: specialization.analysis.slot }, references: specialization.propStateOwners })
|
|
1393
|
-
return factory.createExpressionStatement(effectCall)
|
|
1394
|
-
})
|
|
1395
|
-
const helper = factory.createFunctionDeclaration(
|
|
1396
|
-
undefined,
|
|
1397
|
-
undefined,
|
|
1398
|
-
name,
|
|
1399
|
-
undefined,
|
|
1400
|
-
[],
|
|
1401
|
-
undefined,
|
|
1402
|
-
factory.createBlock([...specialization.hookDeclarations, ...effectStatements, factory.createReturnStatement(specialization.root)], true)
|
|
1403
|
-
)
|
|
1404
|
-
ts.setParentRecursive(helper, false)
|
|
1405
|
-
helper.parent = owner.body
|
|
1406
|
-
const helpers = setterHookHelpers.get(owner.body) ?? []
|
|
1407
|
-
helpers.push(helper)
|
|
1408
|
-
setterHookHelpers.set(owner.body, helpers)
|
|
1409
|
-
const setters = new Map(settersForNode(call, settersByFunction))
|
|
1410
|
-
for (const state of specialization.ordinaryStates) setters.set(state.setter, state.state)
|
|
1411
|
-
settersByFunction.set(helper, setters)
|
|
1412
|
-
const stateOwners = new Map([...stateOwnersForNode(call), ...specialization.propStateOwners])
|
|
1413
|
-
for (const state of specialization.ordinaryStates) stateOwners.set(state.state, state.analysisReference)
|
|
1414
|
-
stateOwnersByFunction.set(helper, stateOwners)
|
|
1415
|
-
usesComponentState ||= specialization.ordinaryStates.length > 0
|
|
1416
|
-
usesComponentId ||= specialization.usesComponentId
|
|
1417
|
-
usesComponentRef ||= specialization.ordinaryRefs.length > 0
|
|
1418
|
-
usesComponentEffects ||= specialization.effects.length > 0
|
|
1419
|
-
specialization.root = factory.createJsxSelfClosingElement(factory.createIdentifier(name), undefined, factory.createJsxAttributes([]))
|
|
1420
|
-
ts.setParentRecursive(specialization.root, false)
|
|
1421
|
-
specialization.root.parent = call.parent
|
|
1422
|
-
}
|
|
1545
|
+
if (specialization.hookDeclarations.length || specialization.effects.length) materializeComponentHelper(call, specialization, "KSetterComponent", false)
|
|
1423
1546
|
componentSpecializations.set(call, specialization)
|
|
1424
1547
|
}
|
|
1425
1548
|
for (const [name, component] of components) {
|
|
@@ -1726,7 +1849,7 @@ function createKudzuTransformer({ semantic, handlerUrl, file, sourceFiles, sourc
|
|
|
1726
1849
|
: collectionSymbol !== undefined ? { kind: "symbol", symbol: collectionSymbol } : { kind: "static" }
|
|
1727
1850
|
if (listParts.calculation) {
|
|
1728
1851
|
usesBinding = true
|
|
1729
|
-
const compiled = descriptors.compileReactiveBinding(listParts.calculation, { setters: settersForNode(node, settersByFunction), importBindings, keyedBlock: blockSlot })
|
|
1852
|
+
const compiled = descriptors.compileReactiveBinding(listParts.calculation, { setters: settersForNode(node, settersByFunction), importBindings, keyedBlock: blockSlot, derived: calculationBinding(node, nearestFunction(node)) })
|
|
1730
1853
|
listSource = compiled.node
|
|
1731
1854
|
collection = { kind: "binding", binding: compiled.binding }
|
|
1732
1855
|
}
|
|
@@ -1851,6 +1974,26 @@ function createKudzuTransformer({ semantic, handlerUrl, file, sourceFiles, sourc
|
|
|
1851
1974
|
if (callback.parameters.length) effectFail(callback, "useEffect() callback cannot declare parameters")
|
|
1852
1975
|
if (!ts.isArrayLiteralExpression(dependencies)) effectFail(dependencies, "useEffect() dependencies must be a literal array")
|
|
1853
1976
|
const setters = settersForNode(node, settersByFunction)
|
|
1977
|
+
const resolveCalculation = dependency => {
|
|
1978
|
+
if (specializedEffect) return undefined
|
|
1979
|
+
const value = unwrapExpression(dependency)
|
|
1980
|
+
const result = ts.isIdentifier(value) ? value : ts.isPropertyAccessExpression(value) || ts.isElementAccessExpression(value) ? unwrapExpression(value.expression) : undefined
|
|
1981
|
+
if (!result || !ts.isIdentifier(result)) return undefined
|
|
1982
|
+
const entries = jsxLocalDeclarations.get(effectOwner)?.get(result.text)
|
|
1983
|
+
const initializer = entries?.length === 1 && entries[0].node.parent?.parent?.parent === effectOwner?.body ? unwrapExpression(entries[0].initializer) : undefined
|
|
1984
|
+
if (!initializer || !ts.isCallExpression(initializer) || !ts.isIdentifier(initializer.expression) || !importBindings.has(initializer.expression.text)) return undefined
|
|
1985
|
+
if (ts.isIdentifier(value)) effectFail(value, `useEffect() cannot depend on the whole imported calculation result ${JSON.stringify(value.text)}; select one JSON-safe primitive field such as ${value.text}.id`)
|
|
1986
|
+
if (ts.isElementAccessExpression(value)) effectFail(value, `useEffect() imported calculation dependencies require one direct static result field such as ${result.text}.id; computed result properties are not supported`)
|
|
1987
|
+
if (!ts.isPropertyAccessExpression(value) || ["__proto__", "constructor", "prototype"].includes(value.name.text)) effectFail(value, "useEffect() imported calculation dependency field must not be __proto__, prototype, or constructor")
|
|
1988
|
+
validateSelectedEffectCalculation(initializer, value.name.text)
|
|
1989
|
+
const expanded = unwrapExpression(resolveReactiveJsxExpression(dependency, effectOwner, setters))
|
|
1990
|
+
if (!ts.isPropertyAccessExpression(expanded) || expanded.name.text !== value.name.text) return undefined
|
|
1991
|
+
const call = unwrapExpression(expanded.expression)
|
|
1992
|
+
if (!ts.isCallExpression(call) || !ts.isIdentifier(call.expression) || !importBindings.has(call.expression.text)) return undefined
|
|
1993
|
+
const states = referencedStateNames(call, setters, call, bindingIndex)
|
|
1994
|
+
if (states.size < 2 || call.arguments.some(argument => !ts.isIdentifier(unwrapExpression(argument)) || !states.has(unwrapExpression(argument).text))) effectFail(call, "useEffect() selected calculations require at least two direct primitive state arguments")
|
|
1995
|
+
return { name: value.expression.text, field: value.name.text, call, states, source: dependency }
|
|
1996
|
+
}
|
|
1854
1997
|
const dependencyAnalysis = analyzeEffectDependencies({
|
|
1855
1998
|
dependencies,
|
|
1856
1999
|
node,
|
|
@@ -1860,9 +2003,12 @@ function createKudzuTransformer({ semantic, handlerUrl, file, sourceFiles, sourc
|
|
|
1860
2003
|
localDeclarations: jsxLocalDeclarations.get(nearestFunction(node)),
|
|
1861
2004
|
factory,
|
|
1862
2005
|
fail: effectFail,
|
|
1863
|
-
bindingIndex
|
|
2006
|
+
bindingIndex,
|
|
2007
|
+
resolveCalculation
|
|
1864
2008
|
})
|
|
1865
2009
|
const { dependencyItem, itemDependencies, ordinaryDependencies, entries: dependencyEntries, dependencyStates, substitutions: dependencySubstitutions, subscriptions: subscriptionDependencies, hasDerived: hasDerivedDependency } = dependencyAnalysis
|
|
2010
|
+
const calculationEntries = dependencyEntries.filter(entry => entry.kind === "calculation")
|
|
2011
|
+
if (calculationEntries.length && (calculationEntries.length !== 1 || dependencyEntries.length !== 1)) effectFail(dependencies, "useEffect() selected calculation fields must be the only dependency")
|
|
1866
2012
|
if (!effectOwner) fail(node, "useEffect() cannot be used outside a Kudzu component")
|
|
1867
2013
|
if (!ts.isBlock(callback.body)) effectFail(callback, "useEffect() callback must use a block body")
|
|
1868
2014
|
const cleanupSubstitutions = new Map()
|
|
@@ -1886,6 +2032,7 @@ function createKudzuTransformer({ semantic, handlerUrl, file, sourceFiles, sourc
|
|
|
1886
2032
|
if (invalidCleanup) effectFail(invalidCleanup, "useEffect() cleanup functions cannot declare parameters or be generators")
|
|
1887
2033
|
if (returns.cleanup && callback.modifiers?.some(modifier => modifier.kind === ts.SyntaxKind.AsyncKeyword)) effectFail(callback, "useEffect() async callbacks cannot return cleanup functions")
|
|
1888
2034
|
validateEffectOwnedBrowserResources(callback, returns, effectFail, bindingIndex)
|
|
2035
|
+
const calculationEvaluators = dependencyEntries.map(entry => entry.kind === "calculation" ? descriptors.compileDerivedEvaluator(entry.call, { setters, importBindings }) : undefined)
|
|
1889
2036
|
const callbackSource = specializedEffect?.sourceFile ?? sourceFile
|
|
1890
2037
|
const callbackFile = callbackSource.fileName
|
|
1891
2038
|
let compiledCallback = dependencySubstitutions.size ? substituteClone(callback, dependencySubstitutions, factory, context) : callback
|
|
@@ -1915,7 +2062,16 @@ function createKudzuTransformer({ semantic, handlerUrl, file, sourceFiles, sourc
|
|
|
1915
2062
|
})
|
|
1916
2063
|
usesListItem ||= Boolean(itemDependencies.length && !listEffect)
|
|
1917
2064
|
usesBehavior = true
|
|
1918
|
-
const derivedDependencies = hasDerivedDependency ? dependencyEntries.map(entry => entry.kind === "derived"
|
|
2065
|
+
const derivedDependencies = hasDerivedDependency ? dependencyEntries.map((entry, index) => entry.kind === "derived"
|
|
2066
|
+
? descriptors.registerDerived("expression", entry.expression, entry.states, entry.source)
|
|
2067
|
+
: entry.kind === "calculation" ? descriptors.registerDerived("calculation", { binding: calculationEvaluators[index].binding, fields: [entry.field] }, entry.states, entry.source) : undefined) : []
|
|
2068
|
+
if (calculationEntries.length) {
|
|
2069
|
+
const calculations = calculationDerivedByOwner.get(effectOwner) ?? new Map()
|
|
2070
|
+
dependencyEntries.forEach((entry, index) => {
|
|
2071
|
+
if (entry.kind === "calculation") calculations.set(entry.name, derivedDependencies[index])
|
|
2072
|
+
})
|
|
2073
|
+
calculationDerivedByOwner.set(effectOwner, calculations)
|
|
2074
|
+
}
|
|
1919
2075
|
const effectSource = specializedEffect?.source ?? node
|
|
1920
2076
|
const lexicalOwner = nearestFunction(effectSource)
|
|
1921
2077
|
const effectStateOwners = new Map([...stateOwnersForNode(callbackArgument), ...stateOwnersForNode(node), ...(specializedEffectStateOwners.get(node)?.references ?? []), ...(activeStateOwners ?? [])])
|
|
@@ -1924,7 +2080,9 @@ function createKudzuTransformer({ semantic, handlerUrl, file, sourceFiles, sourc
|
|
|
1924
2080
|
const dependencyStateNames = [...dependencyStates.keys()]
|
|
1925
2081
|
const effect = descriptors.registerEffect(descriptor, {
|
|
1926
2082
|
cleanup: returns.cleanup,
|
|
1927
|
-
dependencies: hasDerivedDependency ? dependencyEntries.map((entry, index) => entry.kind === "derived"
|
|
2083
|
+
dependencies: hasDerivedDependency ? dependencyEntries.map((entry, index) => entry.kind === "derived"
|
|
2084
|
+
? { kind: "derived", derived: derivedDependencies[index].slot, sources: [...entry.states].map(signalFor) }
|
|
2085
|
+
: entry.kind === "calculation" ? { kind: "derived", derived: derivedDependencies[index].slot, sources: [...entry.states].map(signalFor), field: entry.field, evaluator: calculationEvaluators[index].binding } : { kind: "signal", signal: signalFor(entry.name) }) : ordinaryDependencies.map(dependency => ({ kind: "signal", signal: signalFor(dependency.text) })),
|
|
1928
2086
|
subscriptions: subscriptionNames.map(signalFor),
|
|
1929
2087
|
dependencySignals: dependencyStateNames.map(signalFor),
|
|
1930
2088
|
itemDependencies,
|
|
@@ -1938,7 +2096,12 @@ function createKudzuTransformer({ semantic, handlerUrl, file, sourceFiles, sourc
|
|
|
1938
2096
|
...(analysisSite(effectSource, "hook") ? { site: analysisSite(effectSource, "hook") } : {}),
|
|
1939
2097
|
...(analysisSource(effectSource) ? { source: analysisSource(effectSource) } : {})
|
|
1940
2098
|
})
|
|
1941
|
-
const
|
|
2099
|
+
const hasExpressionDependency = dependencyEntries.some(entry => entry.kind === "derived")
|
|
2100
|
+
const dependencyExpressions = hasExpressionDependency ? effect.dependencies.map((dependency, index) => dependency.kind === "derived" ? moduleIR.derived[dependency.derived].expression : ["state", dependencyEntries[index]?.name ?? ordinaryDependencies[index].text]) : []
|
|
2101
|
+
const dependencyEvaluators = calculationEntries.length ? calculationEvaluators.map((evaluator, index) => evaluator ? factory.createObjectLiteralExpression([
|
|
2102
|
+
factory.createSpreadAssignment(evaluator.descriptor),
|
|
2103
|
+
factory.createPropertyAssignment("field", factory.createStringLiteral(dependencyEntries[index].field))
|
|
2104
|
+
]) : factory.createNull()) : []
|
|
1942
2105
|
const buildCallback = [...packageBindings].some(([name]) => referenceIdentifiers(callback, name).length)
|
|
1943
2106
|
? factory.createArrowFunction(undefined, undefined, [], undefined, factory.createToken(ts.SyntaxKind.EqualsGreaterThanToken), factory.createBlock([], false))
|
|
1944
2107
|
: callback
|
|
@@ -1952,8 +2115,9 @@ function createKudzuTransformer({ semantic, handlerUrl, file, sourceFiles, sourc
|
|
|
1952
2115
|
factory.createStringLiteral(specializedEffect ? sourceLocation(specializedEffect.source, specializedEffect.sourceFile) : sourceLocation(node, sourceFile)),
|
|
1953
2116
|
effect.cleanup ? factory.createTrue() : factory.createFalse(),
|
|
1954
2117
|
factory.createArrayLiteralExpression(effect.itemDependencies.map(field => factory.createStringLiteral(field))),
|
|
1955
|
-
|
|
1956
|
-
factory.createArrayLiteralExpression(dependencyStateNames.map(name => factory.createArrayLiteralExpression([factory.createStringLiteral(name), factory.createIdentifier(name)])))
|
|
2118
|
+
dependencyExpressions.length ? jsonExpression(dependencyExpressions, factory) : factory.createArrayLiteralExpression(),
|
|
2119
|
+
factory.createArrayLiteralExpression(dependencyStateNames.map(name => factory.createArrayLiteralExpression([factory.createStringLiteral(name), factory.createIdentifier(name)]))),
|
|
2120
|
+
factory.createArrayLiteralExpression(dependencyEvaluators)
|
|
1957
2121
|
])
|
|
1958
2122
|
}
|
|
1959
2123
|
|
|
@@ -2030,7 +2194,7 @@ function createKudzuTransformer({ semantic, handlerUrl, file, sourceFiles, sourc
|
|
|
2030
2194
|
if ((usedStates.size || captures.size) && !ts.isIdentifier(expression) && !containsJsx(expression)) {
|
|
2031
2195
|
usesBehavior = true
|
|
2032
2196
|
usesBinding = true
|
|
2033
|
-
return factory.updateJsxExpression(node, descriptors.compileReactiveBinding(expression, { setters, importBindings }).node)
|
|
2197
|
+
return factory.updateJsxExpression(node, descriptors.compileReactiveBinding(expression, { setters, importBindings, derived: calculationBinding(node.expression, nearestFunction(node)) }).node)
|
|
2034
2198
|
}
|
|
2035
2199
|
}
|
|
2036
2200
|
|
|
@@ -2043,7 +2207,7 @@ function createKudzuTransformer({ semantic, handlerUrl, file, sourceFiles, sourc
|
|
|
2043
2207
|
if ((usedStates.size || captures.size) && !ts.isIdentifier(expression)) {
|
|
2044
2208
|
usesBehavior = true
|
|
2045
2209
|
usesBinding = true
|
|
2046
|
-
const compiled = descriptors.compileReactiveBinding(expression, { setters, importBindings })
|
|
2210
|
+
const compiled = descriptors.compileReactiveBinding(expression, { setters, importBindings, derived: calculationBinding(sourceExpression, nearestFunction(node)) })
|
|
2047
2211
|
return factory.updateJsxAttribute(node, node.name, factory.createJsxExpression(undefined, compiled.node))
|
|
2048
2212
|
}
|
|
2049
2213
|
}
|
|
@@ -2072,6 +2236,27 @@ function createKudzuTransformer({ semantic, handlerUrl, file, sourceFiles, sourc
|
|
|
2072
2236
|
return ts.visitEachChild(node, visitor, context)
|
|
2073
2237
|
}
|
|
2074
2238
|
|
|
2239
|
+
function calculationBinding(expression, owner) {
|
|
2240
|
+
const calculations = calculationDerivedByOwner.get(owner)
|
|
2241
|
+
if (!calculations?.size) return undefined
|
|
2242
|
+
const fields = new Map()
|
|
2243
|
+
const visit = node => {
|
|
2244
|
+
const value = unwrapExpression(node)
|
|
2245
|
+
if (ts.isPropertyAccessExpression(value) && ts.isIdentifier(value.expression) && calculations.has(value.expression.text)) {
|
|
2246
|
+
const derived = calculations.get(value.expression.text)
|
|
2247
|
+
const entry = fields.get(derived.slot) ?? { derived, fields: new Set() }
|
|
2248
|
+
entry.fields.add(value.name.text)
|
|
2249
|
+
fields.set(derived.slot, entry)
|
|
2250
|
+
}
|
|
2251
|
+
ts.forEachChild(node, visit)
|
|
2252
|
+
}
|
|
2253
|
+
visit(expression)
|
|
2254
|
+
if (fields.size !== 1) return undefined
|
|
2255
|
+
const [{ derived, fields: selected }] = fields.values()
|
|
2256
|
+
for (const field of selected) if (!derived.calculation.fields.includes(field)) derived.calculation.fields.push(field)
|
|
2257
|
+
return { derived: derived.slot, fields: [...selected] }
|
|
2258
|
+
}
|
|
2259
|
+
|
|
2075
2260
|
const transformed = ts.visitNode(sourceFile, visitor)
|
|
2076
2261
|
descriptors.finalize()
|
|
2077
2262
|
if (!usesBehavior) return transformed
|
|
@@ -2207,7 +2392,7 @@ function jsonExpression(value, factory) {
|
|
|
2207
2392
|
return factory.createCallExpression(factory.createPropertyAccessExpression(factory.createIdentifier("JSON"), "parse"), undefined, [factory.createStringLiteral(JSON.stringify(value))])
|
|
2208
2393
|
}
|
|
2209
2394
|
|
|
2210
|
-
function isStateBackedListComponentCall(call, component, setters) {
|
|
2395
|
+
function isStateBackedListComponentCall(call, component, setters, propertyOnly = false) {
|
|
2211
2396
|
if (component.parameters.length !== 1 || !ts.isObjectBindingPattern(component.parameters[0].name)) return false
|
|
2212
2397
|
const attributes = ts.isJsxElement(call) ? call.openingElement.attributes : call.attributes
|
|
2213
2398
|
const stateNames = new Set(setters.values())
|
|
@@ -2227,7 +2412,9 @@ function isStateBackedListComponentCall(call, component, setters) {
|
|
|
2227
2412
|
let found = false
|
|
2228
2413
|
const visit = node => {
|
|
2229
2414
|
if (found || node !== returned && isFunctionLike(node)) return
|
|
2230
|
-
|
|
2415
|
+
const collection = ts.isCallExpression(node) && ts.isPropertyAccessExpression(node.expression) && node.expression.name.text === "map" ? unwrapExpression(node.expression.expression) : undefined
|
|
2416
|
+
const root = collection && ts.isPropertyAccessExpression(collection) ? unwrapExpression(collection.expression) : collection
|
|
2417
|
+
if (root && ts.isIdentifier(root) && mappedProps.has(root.text) && (!propertyOnly || ts.isPropertyAccessExpression(collection))) {
|
|
2231
2418
|
found = true
|
|
2232
2419
|
return
|
|
2233
2420
|
}
|
|
@@ -2237,6 +2424,75 @@ function isStateBackedListComponentCall(call, component, setters) {
|
|
|
2237
2424
|
return found
|
|
2238
2425
|
}
|
|
2239
2426
|
|
|
2427
|
+
function referencesComponentProperty(expression, locals) {
|
|
2428
|
+
let found = false
|
|
2429
|
+
const visit = node => {
|
|
2430
|
+
if (found) return
|
|
2431
|
+
if (ts.isPropertyAccessExpression(node) && ts.isIdentifier(unwrapExpression(node.expression)) && locals.has(unwrapExpression(node.expression).text)) {
|
|
2432
|
+
found = true
|
|
2433
|
+
return
|
|
2434
|
+
}
|
|
2435
|
+
ts.forEachChild(node, visit)
|
|
2436
|
+
}
|
|
2437
|
+
visit(expression)
|
|
2438
|
+
return found
|
|
2439
|
+
}
|
|
2440
|
+
|
|
2441
|
+
function componentPropertyMutation(component, locals) {
|
|
2442
|
+
let invalid
|
|
2443
|
+
const root = expression => {
|
|
2444
|
+
let value = unwrapExpression(expression)
|
|
2445
|
+
while (ts.isPropertyAccessExpression(value) || ts.isElementAccessExpression(value)) value = unwrapExpression(value.expression)
|
|
2446
|
+
return ts.isIdentifier(value) ? value.text : undefined
|
|
2447
|
+
}
|
|
2448
|
+
const visit = node => {
|
|
2449
|
+
if (invalid) return
|
|
2450
|
+
if (ts.isBinaryExpression(node) && node.operatorToken.kind >= ts.SyntaxKind.FirstAssignment && node.operatorToken.kind <= ts.SyntaxKind.LastAssignment && locals.has(root(node.left))) invalid = node
|
|
2451
|
+
else if ((ts.isPrefixUnaryExpression(node) || ts.isPostfixUnaryExpression(node)) && locals.has(root(node.operand))) invalid = node
|
|
2452
|
+
else if (ts.isCallExpression(node) && ts.isPropertyAccessExpression(node.expression) && mutatingListMethods.has(node.expression.name.text) && locals.has(root(node.expression.expression))) invalid = node
|
|
2453
|
+
if (!invalid) ts.forEachChild(node, visit)
|
|
2454
|
+
}
|
|
2455
|
+
visit(component)
|
|
2456
|
+
return invalid
|
|
2457
|
+
}
|
|
2458
|
+
|
|
2459
|
+
function prependJsxChild(root, child, factory) {
|
|
2460
|
+
if (ts.isJsxElement(root)) return factory.updateJsxElement(root, root.openingElement, [child, ...root.children], root.closingElement)
|
|
2461
|
+
const opening = factory.createJsxOpeningElement(root.tagName, root.typeArguments, root.attributes)
|
|
2462
|
+
const closing = factory.createJsxClosingElement(root.tagName)
|
|
2463
|
+
return factory.createJsxElement(opening, [child], closing)
|
|
2464
|
+
}
|
|
2465
|
+
|
|
2466
|
+
function componentPropertyUses(component, returned, effectCalls) {
|
|
2467
|
+
const locals = new Set(component.parameters[0].name.elements.filter(element => !element.dotDotDotToken && ts.isIdentifier(element.name)).map(element => element.name.text))
|
|
2468
|
+
const uses = new Map()
|
|
2469
|
+
const add = (local, field, consumer) => {
|
|
2470
|
+
if (["__proto__", "constructor", "prototype"].includes(field)) return
|
|
2471
|
+
const fields = uses.get(local) ?? new Map()
|
|
2472
|
+
const consumers = fields.get(field) ?? new Set()
|
|
2473
|
+
consumers.add(consumer)
|
|
2474
|
+
fields.set(field, consumers)
|
|
2475
|
+
uses.set(local, fields)
|
|
2476
|
+
}
|
|
2477
|
+
for (const call of effectCalls) {
|
|
2478
|
+
const dependencies = call.arguments[1]
|
|
2479
|
+
if (!ts.isArrayLiteralExpression(dependencies)) continue
|
|
2480
|
+
for (const dependency of dependencies.elements) {
|
|
2481
|
+
const value = unwrapExpression(dependency)
|
|
2482
|
+
if (ts.isPropertyAccessExpression(value) && ts.isIdentifier(unwrapExpression(value.expression)) && locals.has(unwrapExpression(value.expression).text)) add(unwrapExpression(value.expression).text, value.name.text, "effect")
|
|
2483
|
+
}
|
|
2484
|
+
}
|
|
2485
|
+
const visit = node => {
|
|
2486
|
+
if (ts.isPropertyAccessExpression(node) && ts.isIdentifier(unwrapExpression(node.expression)) && locals.has(unwrapExpression(node.expression).text)) {
|
|
2487
|
+
const map = ts.isPropertyAccessExpression(node.parent) && node.parent.expression === node && node.parent.name.text === "map" && ts.isCallExpression(node.parent.parent)
|
|
2488
|
+
add(unwrapExpression(node.expression).text, node.name.text, map ? "list" : "binding")
|
|
2489
|
+
}
|
|
2490
|
+
ts.forEachChild(node, visit)
|
|
2491
|
+
}
|
|
2492
|
+
visit(returned)
|
|
2493
|
+
return new Map([...uses].map(([local, fields]) => [local, [...fields].map(([field, consumers]) => ({ path: [field], consumers: ["binding", "effect", "list"].filter(consumer => consumers.has(consumer)) }))]))
|
|
2494
|
+
}
|
|
2495
|
+
|
|
2240
2496
|
function jsxCallHasDirectStateProp(call, setters) {
|
|
2241
2497
|
const attributes = ts.isJsxElement(call) ? call.openingElement.attributes : call.attributes
|
|
2242
2498
|
const stateNames = new Set(setters.values())
|
|
@@ -2264,10 +2520,6 @@ function componentHasDirectPropStateInitializer(component) {
|
|
|
2264
2520
|
return component.body.statements.some(statement => ts.isVariableStatement(statement) && statement.declarationList.declarations.some(declaration => declaration.initializer && ts.isCallExpression(declaration.initializer) && ts.isIdentifier(declaration.initializer.expression) && declaration.initializer.expression.text === "useState" && declaration.initializer.arguments.length === 1 && ts.isIdentifier(declaration.initializer.arguments[0]) && props.has(declaration.initializer.arguments[0].text)))
|
|
2265
2521
|
}
|
|
2266
2522
|
|
|
2267
|
-
function componentHasDirectPrimitiveState(component, state) {
|
|
2268
|
-
return Boolean(component && ts.isBlock(component.body) && component.body.statements.some(statement => ts.isVariableStatement(statement) && statement.declarationList.declarations.some(declaration => ts.isArrayBindingPattern(declaration.name) && ts.isIdentifier(declaration.name.elements[0]?.name) && declaration.name.elements[0].name.text === state && declaration.initializer && ts.isCallExpression(declaration.initializer) && ts.isIdentifier(declaration.initializer.expression) && declaration.initializer.expression.text === "useState" && declaration.initializer.arguments.length === 1 && isPrimitiveDefaultLiteral(unwrapExpression(declaration.initializer.arguments[0])))))
|
|
2269
|
-
}
|
|
2270
|
-
|
|
2271
2523
|
function componentHasDirectObjectRef(component, ref) {
|
|
2272
2524
|
if (!component || !ts.isBlock(component.body)) return false
|
|
2273
2525
|
const declaration = component.body.statements.some(statement => ts.isVariableStatement(statement) && statement.declarationList.declarations.some(entry => ts.isIdentifier(entry.name) && entry.name.text === ref && entry.initializer && ts.isCallExpression(entry.initializer) && ts.isIdentifier(entry.initializer.expression) && entry.initializer.expression.text === "useRef" && entry.initializer.arguments.length === 1 && entry.initializer.arguments[0].kind === ts.SyntaxKind.NullKeyword))
|
|
@@ -2294,10 +2546,6 @@ function directSetterLiteralCallback(node, setters) {
|
|
|
2294
2546
|
return { setter: expression.expression.text, value: unwrapExpression(expression.arguments[0]) }
|
|
2295
2547
|
}
|
|
2296
2548
|
|
|
2297
|
-
function componentHasDirectArrayState(component, state) {
|
|
2298
|
-
return Boolean(component && ts.isBlock(component.body) && component.body.statements.some(statement => ts.isVariableStatement(statement) && statement.declarationList.declarations.some(declaration => ts.isArrayBindingPattern(declaration.name) && ts.isIdentifier(declaration.name.elements[0]?.name) && declaration.name.elements[0].name.text === state && declaration.initializer && ts.isCallExpression(declaration.initializer) && ts.isIdentifier(declaration.initializer.expression) && declaration.initializer.expression.text === "useState" && declaration.initializer.arguments.length === 1 && ts.isArrayLiteralExpression(unwrapExpression(declaration.initializer.arguments[0])))))
|
|
2299
|
-
}
|
|
2300
|
-
|
|
2301
2549
|
function directSetterPropEffect(component, setterProp) {
|
|
2302
2550
|
if (!component || component.parameters.length !== 1 || !ts.isObjectBindingPattern(component.parameters[0].name) || !ts.isBlock(component.body)) return undefined
|
|
2303
2551
|
const props = component.parameters[0].name.elements.filter(element => !element.dotDotDotToken && ts.isIdentifier(element.name))
|
|
@@ -2649,6 +2897,7 @@ function specializeComponentCall(call, component, sourceFile, factory, context,
|
|
|
2649
2897
|
}
|
|
2650
2898
|
const substitutions = new Map()
|
|
2651
2899
|
const acceptedProps = new Set()
|
|
2900
|
+
const propLocals = new Set()
|
|
2652
2901
|
let rest
|
|
2653
2902
|
const elements = component.parameters[0].name.elements
|
|
2654
2903
|
for (const [index, element] of elements.entries()) {
|
|
@@ -2661,10 +2910,15 @@ function specializeComponentCall(call, component, sourceFile, factory, context,
|
|
|
2661
2910
|
if (element.initializer && !isSerializableStateLiteral(element.initializer)) fail(element.initializer, `${label} component prop defaults must be directly serializable primitive, plain-object, or array literals`)
|
|
2662
2911
|
const prop = (element.propertyName ?? element.name).text
|
|
2663
2912
|
acceptedProps.add(prop)
|
|
2913
|
+
propLocals.add(element.name.text)
|
|
2664
2914
|
substitutions.set(element.name.text, props.has(prop) ? props.get(prop) : element.initializer ?? factory.createIdentifier("undefined"))
|
|
2665
2915
|
}
|
|
2666
2916
|
const restEntries = [...props].filter(([prop]) => !acceptedProps.has(prop))
|
|
2667
2917
|
if (!rest) for (const [prop] of restEntries) fail(call, `Unknown ${label.toLowerCase()} component prop "${prop}"`)
|
|
2918
|
+
if (label === "Object-property component") {
|
|
2919
|
+
const mutation = componentPropertyMutation(component, propLocals)
|
|
2920
|
+
if (mutation) fail(mutation, "Object-property component props must remain immutable")
|
|
2921
|
+
}
|
|
2668
2922
|
const propAnalysis = elements.map(element => ({
|
|
2669
2923
|
name: (element.propertyName ?? element.name).getText(),
|
|
2670
2924
|
local: element.name.getText(),
|
|
@@ -2702,7 +2956,7 @@ function specializeComponentCall(call, component, sourceFile, factory, context,
|
|
|
2702
2956
|
const substitutedProp = propReceiver ? substitutions.get(propReceiver.text) : undefined
|
|
2703
2957
|
const substitutedState = substitutedProp && ts.isIdentifier(unwrapExpression(substitutedProp)) ? unwrapExpression(substitutedProp).text : undefined
|
|
2704
2958
|
const directProp = ts.isIdentifier(initialArgument)
|
|
2705
|
-
const parentInitializer = substitutedState ? directStateInitializer(call, substitutedState) : undefined
|
|
2959
|
+
const parentInitializer = substitutedState ? directStateInitializer(nearestFunction(call), substitutedState) : undefined
|
|
2706
2960
|
const propInitializer = ordinaryHooks && substitutedState && ordinaryStateNames.has(substitutedState) && parentInitializer && (isPrimitiveDefaultLiteral(parentInitializer) || directProp && (ts.isObjectLiteralExpression(parentInitializer) || ts.isArrayLiteralExpression(parentInitializer)))
|
|
2707
2961
|
const rowItemProp = propReceiver && elements.find(element => !element.dotDotDotToken && ts.isIdentifier(element.name) && element.name.text === propReceiver.text)
|
|
2708
2962
|
const rowItemInitializer = !ordinaryHooks && directProp && substitutedState && rowItemProp && directProps.has((rowItemProp.propertyName ?? rowItemProp.name).text)
|
|
@@ -2754,6 +3008,7 @@ function specializeComponentCall(call, component, sourceFile, factory, context,
|
|
|
2754
3008
|
continue
|
|
2755
3009
|
}
|
|
2756
3010
|
if (!ts.isIdentifier(declaration.name) || !declaration.initializer) fail(declaration, `${label} component locals must be initialized identifiers`)
|
|
3011
|
+
if (label === "Object-property component" && referencesComponentProperty(declaration.initializer, propLocals)) fail(declaration.initializer, "Object-property component property aliases are not supported; read the static prop.field path directly at each consumer")
|
|
2757
3012
|
const calculation = substituteClone(declaration.initializer, substitutions, factory, context)
|
|
2758
3013
|
calculations.push({ name: declaration.name.text, expression: calculation })
|
|
2759
3014
|
substitutions.set(declaration.name.text, calculation)
|
|
@@ -2792,6 +3047,7 @@ function specializeComponentCall(call, component, sourceFile, factory, context,
|
|
|
2792
3047
|
ordinaryStates,
|
|
2793
3048
|
ordinaryRefs,
|
|
2794
3049
|
ordinaryIds,
|
|
3050
|
+
propertyUses: componentPropertyUses(component, returned, effectCalls),
|
|
2795
3051
|
propExpressions: props,
|
|
2796
3052
|
props: propAnalysis,
|
|
2797
3053
|
usesComponentId: ordinaryIds.length > 0
|
|
@@ -2806,8 +3062,7 @@ function isSerializableStateLiteral(node) {
|
|
|
2806
3062
|
return value.properties.every(property => ts.isPropertyAssignment(property) && !ts.isComputedPropertyName(property.name) && property.name.text !== "__proto__" && isSerializableStateLiteral(property.initializer))
|
|
2807
3063
|
}
|
|
2808
3064
|
|
|
2809
|
-
function directStateInitializer(
|
|
2810
|
-
const owner = nearestFunction(call)
|
|
3065
|
+
function directStateInitializer(owner, name) {
|
|
2811
3066
|
if (!owner || !ts.isBlock(owner.body)) return
|
|
2812
3067
|
for (const statement of owner.body.statements) {
|
|
2813
3068
|
if (!ts.isVariableStatement(statement)) continue
|
|
@@ -3138,10 +3393,34 @@ function importedSerializableCollectionNames(sourceFile, file, sourceFiles, sour
|
|
|
3138
3393
|
return new Set(importedSerializableCollections(sourceFile, file, sourceFiles, sourceIndex).keys())
|
|
3139
3394
|
}
|
|
3140
3395
|
|
|
3141
|
-
function importedSerializableCollections(sourceFile, file, sourceFiles, sourceIndex) {
|
|
3396
|
+
function importedSerializableCollections(sourceFile, file, sourceFiles, sourceIndex, onlyMapped = false) {
|
|
3142
3397
|
const collections = new Map()
|
|
3398
|
+
const mapped = new Set()
|
|
3399
|
+
const memoNames = new Set(["useMemo"])
|
|
3400
|
+
for (const statement of sourceFile.statements) if (ts.isImportDeclaration(statement) && ["react", "@kudzujs/core"].includes(statement.moduleSpecifier.text) && statement.importClause?.namedBindings && ts.isNamedImports(statement.importClause.namedBindings)) {
|
|
3401
|
+
for (const entry of statement.importClause.namedBindings.elements) if ((entry.propertyName ?? entry.name).text === "useMemo") memoNames.add(entry.name.text)
|
|
3402
|
+
}
|
|
3403
|
+
const collectRoot = node => {
|
|
3404
|
+
if (ts.isPropertyAccessExpression(node) && ["filter", "flatMap", "slice", "toSorted", "map"].includes(node.name.text)) {
|
|
3405
|
+
let expression = node.expression
|
|
3406
|
+
while (ts.isCallExpression(expression) || ts.isPropertyAccessExpression(expression)) expression = expression.expression
|
|
3407
|
+
if (ts.isIdentifier(expression)) mapped.add(expression.text)
|
|
3408
|
+
}
|
|
3409
|
+
ts.forEachChild(node, collectRoot)
|
|
3410
|
+
}
|
|
3411
|
+
const visit = node => {
|
|
3412
|
+
if (ts.isPropertyAccessExpression(node) && node.name.text === "map") {
|
|
3413
|
+
collectRoot(node)
|
|
3414
|
+
}
|
|
3415
|
+
if (ts.isCallExpression(node) && ts.isIdentifier(node.expression) && memoNames.has(node.expression.text) && node.arguments[0]) collectRoot(node.arguments[0])
|
|
3416
|
+
ts.forEachChild(node, visit)
|
|
3417
|
+
}
|
|
3418
|
+
if (onlyMapped) {
|
|
3419
|
+
visit(sourceFile)
|
|
3420
|
+
if (!mapped.size) return collections
|
|
3421
|
+
}
|
|
3143
3422
|
for (const [name, binding] of clientImportBindings(sourceFile, file, sourceFiles)) {
|
|
3144
|
-
if (binding.kind !== "named") continue
|
|
3423
|
+
if (binding.kind !== "named" || onlyMapped && !mapped.has(name)) continue
|
|
3145
3424
|
const imported = parseSourceFile(binding.target, sourceIndex.get(binding.target))
|
|
3146
3425
|
for (const statement of imported.statements) {
|
|
3147
3426
|
if (!ts.isVariableStatement(statement) || !(statement.declarationList.flags & ts.NodeFlags.Const) || !statement.modifiers?.some(modifier => modifier.kind === ts.SyntaxKind.ExportKeyword)) continue
|
|
@@ -3279,7 +3558,9 @@ function orderSourceStyles(entryFiles, sourceFiles, sourceIndex, staticFiles) {
|
|
|
3279
3558
|
const visit = file => {
|
|
3280
3559
|
if (seenSources.has(file)) return
|
|
3281
3560
|
seenSources.add(file)
|
|
3282
|
-
const
|
|
3561
|
+
const source = sourceIndex.get(file)
|
|
3562
|
+
if (importFreeTypeScriptModule(file, source)) return
|
|
3563
|
+
const sourceFile = parseSourceFile(file, source)
|
|
3283
3564
|
for (const statement of sourceFile.statements) {
|
|
3284
3565
|
if ((!ts.isImportDeclaration(statement) && !ts.isExportDeclaration(statement)) || !runtimeModuleReference(statement) || !statement.moduleSpecifier || !ts.isStringLiteral(statement.moduleSpecifier) || !statement.moduleSpecifier.text.startsWith(".")) continue
|
|
3285
3566
|
const specifier = statement.moduleSpecifier.text
|
|
@@ -3390,7 +3671,7 @@ const printHandlerModule = createHandlerCodegen({
|
|
|
3390
3671
|
const { normalizeReactMigrationSyntax, validateUseIdSyntax } = createReactMigrationPass({ cloneAst, jsxTagName })
|
|
3391
3672
|
const normalizeReactRouterSyntax = createRouterPass({ withBase })
|
|
3392
3673
|
|
|
3393
|
-
return { collectClientModules, compileClientModule, compiledPath, compileSource, layoutExportError, orderSourceStyles, reachableSourceFiles, safeStaticFiles }
|
|
3674
|
+
return { collectClientModules, compileClientModule, compiledPath, compileSource, compileSourceAsync, layoutExportError, orderSourceStyles, reachableSourceFiles, safeStaticFiles }
|
|
3394
3675
|
}
|
|
3395
3676
|
|
|
3396
3677
|
const currentCompiler = sourceIndex => createSourceCompiler(createProjectSession(process.cwd(), { sourceIndex }))
|