@kudzujs/core 0.8.14 → 0.8.15
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 +34 -6
- package/RELEASES.md +32 -0
- package/framework/README.md +18 -2
- package/framework/build.mjs +98 -2868
- package/framework/compiler/animation-frame-pass.mjs +103 -0
- package/framework/compiler/ast-helpers.mjs +181 -0
- package/framework/compiler/browser-signal-passes.mjs +182 -0
- package/framework/compiler/custom-hook-timer-pass.mjs +126 -0
- package/framework/compiler/effect-codegen.mjs +884 -0
- package/framework/compiler/handler-codegen.mjs +296 -0
- package/framework/compiler/normalization-pipeline.mjs +9 -0
- package/framework/compiler/react-migration-pass.mjs +338 -0
- package/framework/compiler/render-control-pass.mjs +96 -0
- package/framework/compiler/router-pass.mjs +245 -0
- package/framework/compiler/worker-compiler.mjs +163 -0
- package/framework/dev-server.mjs +244 -0
- package/package.json +1 -1
|
@@ -0,0 +1,884 @@
|
|
|
1
|
+
import { join } from "node:path"
|
|
2
|
+
|
|
3
|
+
export function createEffectCodegen({ assetPath, inlineJson, relativeModulePath }) {
|
|
4
|
+
function printEffectEntry(effects, output, handlerModules, assetsDirectory, base, paramPath, runtimeName) {
|
|
5
|
+
const hasCleanup = effects.some(effect => effect.cleanup)
|
|
6
|
+
const hasDependencies = effects.some(effect => effect.dependencies?.length || effect.itemDependencies?.length)
|
|
7
|
+
const hasOwners = effects.some(effect => effect.owner)
|
|
8
|
+
const hasDependencyExpressions = effects.some(effect => effect.dependencyExpressions?.length)
|
|
9
|
+
const moduleUrls = [...new Set(effects.map(effect => effect.module))]
|
|
10
|
+
const modules = moduleUrls.map(url => {
|
|
11
|
+
const module = handlerModules.find(entry => assetPath(base, `assets/${entry.path}`) === url)
|
|
12
|
+
if (!module) throw new Error(`Effect handler module was not emitted: ${url}`)
|
|
13
|
+
return module
|
|
14
|
+
})
|
|
15
|
+
const imports = [
|
|
16
|
+
hasCleanup || hasDependencies || hasOwners
|
|
17
|
+
? `import * as __kRuntime from ${JSON.stringify(relativeModulePath(output, join(assetsDirectory, runtimeName)))}\nconst { browserState, commitDom } = __kRuntime`
|
|
18
|
+
: `import { browserState, commitDom } from ${JSON.stringify(relativeModulePath(output, join(assetsDirectory, runtimeName)))}`,
|
|
19
|
+
`import { createEffectContext } from ${JSON.stringify(relativeModulePath(output, join(assetsDirectory, "kudzu-effect.js")))}`,
|
|
20
|
+
...(hasDependencyExpressions ? [`import { evaluateCollectionExpression as __kEvaluateDependency } from ${JSON.stringify(relativeModulePath(output, join(assetsDirectory, "kudzu-collection-selector.js")))}`] : []),
|
|
21
|
+
...(paramPath ? [`import ${JSON.stringify(relativeModulePath(output, join(assetsDirectory, paramPath)))}`] : []),
|
|
22
|
+
...modules.map((module, index) => `import * as __kEffectModule${index} from ${JSON.stringify(relativeModulePath(output, join(assetsDirectory, module.path)))}`)
|
|
23
|
+
]
|
|
24
|
+
const entries = moduleUrls.map((url, index) => `[${JSON.stringify(url)}, __kEffectModule${index}]`).join(",")
|
|
25
|
+
if (hasOwners) return printOwnedEffectEntry(imports, effects, entries)
|
|
26
|
+
if (effects.length === 1 && effects[0].dependencies?.length === 1 && !hasDependencyExpressions) return printSingleDependencyEffect(imports, effects[0], hasCleanup)
|
|
27
|
+
const disposal = hasCleanup ? `
|
|
28
|
+
let disposed = false
|
|
29
|
+
const dispose = root => {
|
|
30
|
+
if (root !== document || disposed) return
|
|
31
|
+
disposed = true
|
|
32
|
+
active = false
|
|
33
|
+
pending.clear()
|
|
34
|
+
for (const record of records) invokeCleanup(record)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (__kRuntime.registerUnmountHook) __kRuntime.registerUnmountHook(dispose)
|
|
38
|
+
addEventListener("pagehide", event => {
|
|
39
|
+
if (event.persisted) return
|
|
40
|
+
if (__kRuntime.unmountDom) __kRuntime.unmountDom(document)
|
|
41
|
+
else dispose(document)
|
|
42
|
+
})` : ""
|
|
43
|
+
if (hasDependencies) return `${imports.join("\n")}
|
|
44
|
+
const effects = ${inlineJson(effects)}
|
|
45
|
+
const modules = new Map([${entries}])
|
|
46
|
+
const records = effects.map((effect, index) => ({ effect, index, values: undefined, cleanup: undefined, token: undefined }))
|
|
47
|
+
const dependencies = new Map()
|
|
48
|
+
const pending = new Set()
|
|
49
|
+
let scheduled = false
|
|
50
|
+
let flushing = false
|
|
51
|
+
let active = true
|
|
52
|
+
for (const record of records) {
|
|
53
|
+
for (const id of record.effect.dependencies ?? []) {
|
|
54
|
+
const subscribers = dependencies.get(id) ?? new Set()
|
|
55
|
+
subscribers.add(record)
|
|
56
|
+
dependencies.set(id, subscribers)
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
__kRuntime.registerCommitter(id => {
|
|
60
|
+
if (!active) return
|
|
61
|
+
for (const record of dependencies.get(id) ?? []) pending.add(record)
|
|
62
|
+
schedule()
|
|
63
|
+
})
|
|
64
|
+
for (const record of records) {
|
|
65
|
+
try {
|
|
66
|
+
record.values = readDependencies(record)
|
|
67
|
+
invoke(record)
|
|
68
|
+
} catch (error) {
|
|
69
|
+
console.error(error)
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
function schedule() {
|
|
73
|
+
if (!pending.size || scheduled || flushing) return
|
|
74
|
+
scheduled = true
|
|
75
|
+
queueMicrotask(flush)
|
|
76
|
+
}
|
|
77
|
+
async function flush() {
|
|
78
|
+
scheduled = false
|
|
79
|
+
if (!active) return pending.clear()
|
|
80
|
+
flushing = true
|
|
81
|
+
try {
|
|
82
|
+
const selected = [...pending].sort((left, right) => left.index - right.index)
|
|
83
|
+
pending.clear()
|
|
84
|
+
const changed = []
|
|
85
|
+
for (const record of selected) {
|
|
86
|
+
try {
|
|
87
|
+
const values = readDependencies(record)
|
|
88
|
+
if (!record.values || values.some((value, index) => !Object.is(value, record.values[index]))) {
|
|
89
|
+
record.values = values
|
|
90
|
+
changed.push(record)
|
|
91
|
+
}
|
|
92
|
+
} catch (error) {
|
|
93
|
+
console.error(error)
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
for (const record of changed) await invokeCleanup(record)
|
|
97
|
+
if (active) for (const record of changed) invoke(record)
|
|
98
|
+
} finally {
|
|
99
|
+
flushing = false
|
|
100
|
+
if (active) schedule()
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
function readDependencies(record) {
|
|
104
|
+
${hasDependencyExpressions ? printDerivedDependencyRead("browserState") : ""}
|
|
105
|
+
return (record.effect.dependencies ?? []).map(id => {
|
|
106
|
+
const value = browserState.get(id)
|
|
107
|
+
if (value !== null && typeof value !== "string" && typeof value !== "boolean" && !(typeof value === "number" && Number.isFinite(value) && !Object.is(value, -0))) throw new Error("useEffect() dependency state must remain a JSON-safe primitive")
|
|
108
|
+
return value
|
|
109
|
+
})
|
|
110
|
+
}
|
|
111
|
+
function invoke(record) {
|
|
112
|
+
try {
|
|
113
|
+
const effect = record.effect
|
|
114
|
+
const token = { active: true }
|
|
115
|
+
record.token = token
|
|
116
|
+
const result = modules.get(effect.module)[effect.handler](createEffectContext(browserState, effect.states, commitDom, effect.scope, () => active && token.active && record.token === token))
|
|
117
|
+
if (effect.cleanup && typeof result === "function") record.cleanup = result
|
|
118
|
+
else if (result && typeof result.then === "function") result.catch(error => console.error(error))
|
|
119
|
+
} catch (error) {
|
|
120
|
+
console.error(error)
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
async function invokeCleanup(record) {
|
|
124
|
+
if (record.token) record.token.active = false
|
|
125
|
+
record.token = undefined
|
|
126
|
+
const cleanup = record.cleanup
|
|
127
|
+
record.cleanup = undefined
|
|
128
|
+
if (!cleanup) return
|
|
129
|
+
try {
|
|
130
|
+
await cleanup()
|
|
131
|
+
} catch (error) {
|
|
132
|
+
console.error(error)
|
|
133
|
+
}
|
|
134
|
+
}${disposal}`
|
|
135
|
+
if (!hasCleanup) return `${imports.join("\n")}
|
|
136
|
+
const effects = ${inlineJson(effects)}
|
|
137
|
+
const modules = new Map([${entries}])
|
|
138
|
+
for (const effect of effects) {
|
|
139
|
+
try {
|
|
140
|
+
const result = modules.get(effect.module)[effect.handler](createEffectContext(browserState, effect.states, commitDom, effect.scope))
|
|
141
|
+
if (result && typeof result.then === "function") result.catch(error => console.error(error))
|
|
142
|
+
} catch (error) {
|
|
143
|
+
console.error(error)
|
|
144
|
+
}
|
|
145
|
+
}`
|
|
146
|
+
return `${imports.join("\n")}
|
|
147
|
+
const effects = ${inlineJson(effects)}
|
|
148
|
+
const modules = new Map([${entries}])
|
|
149
|
+
const cleanups = []
|
|
150
|
+
for (const effect of effects) {
|
|
151
|
+
try {
|
|
152
|
+
const result = modules.get(effect.module)[effect.handler](createEffectContext(browserState, effect.states, commitDom, effect.scope))
|
|
153
|
+
if (effect.cleanup && typeof result === "function") cleanups.push(result)
|
|
154
|
+
else if (result && typeof result.then === "function") result.catch(error => console.error(error))
|
|
155
|
+
} catch (error) {
|
|
156
|
+
console.error(error)
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
let cleaned = false
|
|
160
|
+
const dispose = root => {
|
|
161
|
+
if (root !== document || cleaned) return
|
|
162
|
+
cleaned = true
|
|
163
|
+
for (const cleanup of cleanups) {
|
|
164
|
+
try {
|
|
165
|
+
const result = cleanup()
|
|
166
|
+
if (result && typeof result.then === "function") result.catch(error => console.error(error))
|
|
167
|
+
} catch (error) {
|
|
168
|
+
console.error(error)
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
cleanups.length = 0
|
|
172
|
+
}
|
|
173
|
+
if (__kRuntime.registerUnmountHook) __kRuntime.registerUnmountHook(dispose)
|
|
174
|
+
addEventListener("pagehide", event => {
|
|
175
|
+
if (event.persisted) return
|
|
176
|
+
if (__kRuntime.unmountDom) __kRuntime.unmountDom(document)
|
|
177
|
+
else dispose(document)
|
|
178
|
+
})`
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function printNavigableEffectEntry(effects, output, handlerModules, assetsDirectory, base) {
|
|
182
|
+
const hasDependencyExpressions = effects.some(effect => effect.dependencyExpressions?.length)
|
|
183
|
+
const moduleUrls = [...new Set(effects.map(effect => effect.module))]
|
|
184
|
+
const modules = moduleUrls.map(url => {
|
|
185
|
+
const module = handlerModules.find(entry => assetPath(base, `assets/${entry.path}`) === url)
|
|
186
|
+
if (!module) throw new Error(`Effect handler module was not emitted: ${url}`)
|
|
187
|
+
return module
|
|
188
|
+
})
|
|
189
|
+
const imports = [
|
|
190
|
+
`import * as __kRuntime from ${JSON.stringify(relativeModulePath(output, join(assetsDirectory, "kudzu.js")))}`,
|
|
191
|
+
`import { createEffectContext } from ${JSON.stringify(relativeModulePath(output, join(assetsDirectory, "kudzu-effect.js")))}`,
|
|
192
|
+
...(hasDependencyExpressions ? [`import { evaluateCollectionExpression as __kEvaluateDependency } from ${JSON.stringify(relativeModulePath(output, join(assetsDirectory, "kudzu-collection-selector.js")))}`] : []),
|
|
193
|
+
...modules.map((module, index) => `import * as __kEffectModule${index} from ${JSON.stringify(relativeModulePath(output, join(assetsDirectory, module.path)))}`)
|
|
194
|
+
]
|
|
195
|
+
const entries = moduleUrls.map((url, index) => `[${JSON.stringify(url)}, __kEffectModule${index}]`).join(",")
|
|
196
|
+
return `${imports.join("\n")}
|
|
197
|
+
const effects = ${inlineJson(effects)}
|
|
198
|
+
const modules = new Map([${entries}])
|
|
199
|
+
export const mountLayoutEffects = () => mount("layout")
|
|
200
|
+
export const mountRouteEffects = () => mount("route")
|
|
201
|
+
function mount(lifetime) {
|
|
202
|
+
let active = true
|
|
203
|
+
let flushing
|
|
204
|
+
const records = effects.filter(effect => effect.lifetime === lifetime).map((effect, index) => ({ effect, index, values: undefined, cleanup: undefined, token: undefined }))
|
|
205
|
+
const dependencies = new Map()
|
|
206
|
+
const pending = new Set()
|
|
207
|
+
let scheduled = false
|
|
208
|
+
for (const record of records) for (const id of record.effect.dependencies ?? []) {
|
|
209
|
+
const subscribers = dependencies.get(id) ?? new Set()
|
|
210
|
+
subscribers.add(record)
|
|
211
|
+
dependencies.set(id, subscribers)
|
|
212
|
+
}
|
|
213
|
+
const unsubscribe = dependencies.size ? __kRuntime.registerCommitter(id => {
|
|
214
|
+
if (!active) return
|
|
215
|
+
for (const record of dependencies.get(id) ?? []) pending.add(record)
|
|
216
|
+
if (pending.size && !scheduled && !flushing) {
|
|
217
|
+
scheduled = true
|
|
218
|
+
queueMicrotask(flush)
|
|
219
|
+
}
|
|
220
|
+
}) : undefined
|
|
221
|
+
for (const record of records) {
|
|
222
|
+
try {
|
|
223
|
+
record.values = readDependencies(record)
|
|
224
|
+
invoke(record)
|
|
225
|
+
} catch (error) {
|
|
226
|
+
console.error(error)
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
async function flush() {
|
|
230
|
+
scheduled = false
|
|
231
|
+
if (!active) return pending.clear()
|
|
232
|
+
const operation = (async () => {
|
|
233
|
+
const selected = [...pending].sort((left, right) => left.index - right.index)
|
|
234
|
+
pending.clear()
|
|
235
|
+
const changed = []
|
|
236
|
+
for (const record of selected) {
|
|
237
|
+
try {
|
|
238
|
+
const values = readDependencies(record)
|
|
239
|
+
if (!record.values || values.some((value, index) => !Object.is(value, record.values[index]))) {
|
|
240
|
+
record.values = values
|
|
241
|
+
changed.push(record)
|
|
242
|
+
}
|
|
243
|
+
} catch (error) {
|
|
244
|
+
console.error(error)
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
for (const record of changed) await cleanup(record)
|
|
248
|
+
if (active) for (const record of changed) invoke(record)
|
|
249
|
+
})()
|
|
250
|
+
flushing = operation
|
|
251
|
+
try { await operation } finally {
|
|
252
|
+
if (flushing === operation) flushing = undefined
|
|
253
|
+
if (active && pending.size && !scheduled) {
|
|
254
|
+
scheduled = true
|
|
255
|
+
queueMicrotask(flush)
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
function readDependencies(record) {
|
|
260
|
+
${hasDependencyExpressions ? printDerivedDependencyRead("__kRuntime.browserState") : ""}
|
|
261
|
+
return (record.effect.dependencies ?? []).map(id => {
|
|
262
|
+
const value = __kRuntime.browserState.get(id)
|
|
263
|
+
if (value !== null && typeof value !== "string" && typeof value !== "boolean" && !(typeof value === "number" && Number.isFinite(value) && !Object.is(value, -0))) throw new Error("useEffect() dependency state must remain a JSON-safe primitive")
|
|
264
|
+
return value
|
|
265
|
+
})
|
|
266
|
+
}
|
|
267
|
+
function invoke(record) {
|
|
268
|
+
const token = { active: true }
|
|
269
|
+
record.token = token
|
|
270
|
+
try {
|
|
271
|
+
const effect = record.effect
|
|
272
|
+
const result = modules.get(effect.module)[effect.handler](createEffectContext(__kRuntime.browserState, effect.states, __kRuntime.commitDom, effect.scope, () => active && token.active && record.token === token))
|
|
273
|
+
if (effect.cleanup && typeof result === "function") record.cleanup = result
|
|
274
|
+
else if (result && typeof result.then === "function") result.catch(error => console.error(error))
|
|
275
|
+
} catch (error) {
|
|
276
|
+
console.error(error)
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
async function cleanup(record) {
|
|
280
|
+
if (record.token) record.token.active = false
|
|
281
|
+
record.token = undefined
|
|
282
|
+
const current = record.cleanup
|
|
283
|
+
record.cleanup = undefined
|
|
284
|
+
if (!current) return
|
|
285
|
+
try { await current() } catch (error) { console.error(error) }
|
|
286
|
+
}
|
|
287
|
+
let disposal
|
|
288
|
+
return async function dispose() {
|
|
289
|
+
if (disposal) return disposal
|
|
290
|
+
disposal = (async () => {
|
|
291
|
+
active = false
|
|
292
|
+
unsubscribe?.()
|
|
293
|
+
pending.clear()
|
|
294
|
+
for (const record of records) if (record.token) record.token.active = false
|
|
295
|
+
if (flushing) await flushing
|
|
296
|
+
for (const record of records) await cleanup(record)
|
|
297
|
+
})()
|
|
298
|
+
return disposal
|
|
299
|
+
}
|
|
300
|
+
}`
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
function printOwnedNavigableEffectEntry(effects, output, handlerModules, assetsDirectory, base) {
|
|
304
|
+
const hasItemDependencies = effects.some(effect => effect.itemDependencies?.length)
|
|
305
|
+
const hasDependencyExpressions = effects.some(effect => effect.dependencyExpressions?.length)
|
|
306
|
+
const moduleUrls = [...new Set(effects.map(effect => effect.module))]
|
|
307
|
+
const modules = moduleUrls.map(url => {
|
|
308
|
+
const module = handlerModules.find(entry => assetPath(base, `assets/${entry.path}`) === url)
|
|
309
|
+
if (!module) throw new Error(`Effect handler module was not emitted: ${url}`)
|
|
310
|
+
return module
|
|
311
|
+
})
|
|
312
|
+
const imports = [
|
|
313
|
+
`import * as __kRuntime from ${JSON.stringify(relativeModulePath(output, join(assetsDirectory, "kudzu.js")))}`,
|
|
314
|
+
`import { createEffectContext } from ${JSON.stringify(relativeModulePath(output, join(assetsDirectory, "kudzu-effect.js")))}`,
|
|
315
|
+
...(hasDependencyExpressions ? [`import { evaluateCollectionExpression as __kEvaluateDependency } from ${JSON.stringify(relativeModulePath(output, join(assetsDirectory, "kudzu-collection-selector.js")))}`] : []),
|
|
316
|
+
...modules.map((module, index) => `import * as __kEffectModule${index} from ${JSON.stringify(relativeModulePath(output, join(assetsDirectory, module.path)))}`)
|
|
317
|
+
]
|
|
318
|
+
const entries = moduleUrls.map((url, index) => `[${JSON.stringify(url)}, __kEffectModule${index}]`).join(",")
|
|
319
|
+
return `${imports.join("\n")}
|
|
320
|
+
const effects = ${inlineJson(effects)}
|
|
321
|
+
const modules = new Map([${entries}])
|
|
322
|
+
export const mountLayoutEffects = () => mount("layout")
|
|
323
|
+
export const mountRouteEffects = () => mount("route")
|
|
324
|
+
function mount(lifetime) {
|
|
325
|
+
let active = true
|
|
326
|
+
let flushing
|
|
327
|
+
let order = 0
|
|
328
|
+
const selectedEffects = effects.map((effect, index) => ({ effect, index })).filter(entry => entry.effect.lifetime === lifetime)
|
|
329
|
+
const records = new Set()
|
|
330
|
+
const owners = new Map()
|
|
331
|
+
const listTemplates = new Map()
|
|
332
|
+
const registrations = new WeakMap()
|
|
333
|
+
const dependencies = new Map()
|
|
334
|
+
const pending = new Set()
|
|
335
|
+
const startedCleanups = new Set()
|
|
336
|
+
let scheduled = false
|
|
337
|
+
for (const template of selectedEffects) {
|
|
338
|
+
if (template.effect.list) listTemplates.set(template.effect.owner, template)
|
|
339
|
+
else {
|
|
340
|
+
const record = createRecord(template, !template.effect.owner)
|
|
341
|
+
if (template.effect.owner) owners.set(template.effect.owner, record)
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
const unsubscribeCommitter = selectedEffects.some(({ effect }) => effect.dependencies?.length) ? __kRuntime.registerCommitter(id => {
|
|
345
|
+
if (!active) return
|
|
346
|
+
for (const record of dependencies.get(id) ?? []) if (record.mounted) pending.add(record)
|
|
347
|
+
schedule()
|
|
348
|
+
}) : undefined
|
|
349
|
+
const unsubscribeMount = __kRuntime.registerMountHook(mountOwned)
|
|
350
|
+
const unsubscribeUnmount = __kRuntime.registerUnmountHook(unmountOwned)
|
|
351
|
+
${hasItemDependencies ? `const unsubscribeItems = [...new Set(selectedEffects.filter(({ effect }) => effect.itemDependencies?.length).map(({ effect }) => effect.listState))].map(listState => __kRuntime.registerListItemHook(listState, root => {
|
|
352
|
+
if (!active) return
|
|
353
|
+
for (const record of registrations.get(root) ?? []) if (record.mounted && record.effect.itemDependencies) pending.add(record)
|
|
354
|
+
schedule()
|
|
355
|
+
}))
|
|
356
|
+
` : ""}for (const record of records) if (record.mounted) start(record)
|
|
357
|
+
mountOwned(document)
|
|
358
|
+
function createRecord(template, mounted = true) {
|
|
359
|
+
const record = { ...template, order: order++, mounted, marker: undefined, version: 0, values: undefined, cleanup: undefined, disposal: undefined, token: undefined }
|
|
360
|
+
records.add(record)
|
|
361
|
+
registerDependencies(record)
|
|
362
|
+
return record
|
|
363
|
+
}
|
|
364
|
+
function registerDependencies(record) {
|
|
365
|
+
for (const id of record.effect.dependencies ?? []) {
|
|
366
|
+
const subscribers = dependencies.get(id) ?? new Set()
|
|
367
|
+
subscribers.add(record)
|
|
368
|
+
dependencies.set(id, subscribers)
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
function unregisterDependencies(record) {
|
|
372
|
+
for (const id of record.effect.dependencies ?? []) {
|
|
373
|
+
const subscribers = dependencies.get(id)
|
|
374
|
+
subscribers?.delete(record)
|
|
375
|
+
if (!subscribers?.size) dependencies.delete(id)
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
function mountOwned(root) {
|
|
379
|
+
if (!active) return
|
|
380
|
+
for (const marker of matching(root)) {
|
|
381
|
+
if (!marker.isConnected) continue
|
|
382
|
+
if (marker.dataset.kEffects) {
|
|
383
|
+
if (registrations.has(marker)) continue
|
|
384
|
+
const rowRecords = JSON.parse(marker.dataset.kEffects).flatMap(owner => {
|
|
385
|
+
const template = listTemplates.get(owner)
|
|
386
|
+
if (!template) return []
|
|
387
|
+
const record = createRecord(template)
|
|
388
|
+
record.marker = marker
|
|
389
|
+
start(record)
|
|
390
|
+
return [record]
|
|
391
|
+
})
|
|
392
|
+
if (rowRecords.length) registrations.set(marker, rowRecords)
|
|
393
|
+
continue
|
|
394
|
+
}
|
|
395
|
+
const record = owners.get(marker.dataset.kEffect)
|
|
396
|
+
if (record && !record.mounted) mountRecord(record, marker)
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
function unmountOwned(root) {
|
|
400
|
+
if (!active) return
|
|
401
|
+
for (const marker of matching(root)) {
|
|
402
|
+
const rowRecords = registrations.get(marker)
|
|
403
|
+
if (rowRecords) {
|
|
404
|
+
for (const record of rowRecords) unmountRecord(record, true)
|
|
405
|
+
registrations.delete(marker)
|
|
406
|
+
continue
|
|
407
|
+
}
|
|
408
|
+
const record = owners.get(marker.dataset.kEffect)
|
|
409
|
+
if (record?.marker === marker) unmountRecord(record)
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
function matching(root) {
|
|
413
|
+
const selector = "template[data-k-effect],[data-k-effects]"
|
|
414
|
+
return [...(root.matches?.(selector) ? [root] : []), ...(root.querySelectorAll?.(selector) ?? [])]
|
|
415
|
+
}
|
|
416
|
+
function mountRecord(record, marker) {
|
|
417
|
+
record.mounted = true
|
|
418
|
+
record.marker = marker
|
|
419
|
+
const version = ++record.version
|
|
420
|
+
const begin = () => {
|
|
421
|
+
if (active && record.mounted && record.version === version && marker.isConnected) start(record)
|
|
422
|
+
}
|
|
423
|
+
if (record.disposal) record.disposal.then(begin)
|
|
424
|
+
else begin()
|
|
425
|
+
}
|
|
426
|
+
function unmountRecord(record, dynamic = false) {
|
|
427
|
+
if (!record.mounted) return
|
|
428
|
+
record.mounted = false
|
|
429
|
+
record.marker = undefined
|
|
430
|
+
record.version++
|
|
431
|
+
pending.delete(record)
|
|
432
|
+
if (dynamic) {
|
|
433
|
+
unregisterDependencies(record)
|
|
434
|
+
records.delete(record)
|
|
435
|
+
}
|
|
436
|
+
void cleanup(record)
|
|
437
|
+
}
|
|
438
|
+
function start(record) {
|
|
439
|
+
try {
|
|
440
|
+
record.values = readDependencies(record)
|
|
441
|
+
invoke(record)
|
|
442
|
+
} catch (error) {
|
|
443
|
+
console.error(error)
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
function schedule() {
|
|
447
|
+
if (!pending.size || scheduled || flushing) return
|
|
448
|
+
scheduled = true
|
|
449
|
+
queueMicrotask(flush)
|
|
450
|
+
}
|
|
451
|
+
async function flush() {
|
|
452
|
+
scheduled = false
|
|
453
|
+
if (!active) return pending.clear()
|
|
454
|
+
const operation = (async () => {
|
|
455
|
+
const changed = []
|
|
456
|
+
const selected = [...pending].filter(record => record.mounted).sort((left, right) => left.index - right.index || left.order - right.order)
|
|
457
|
+
pending.clear()
|
|
458
|
+
for (const record of selected) {
|
|
459
|
+
try {
|
|
460
|
+
const values = readDependencies(record)
|
|
461
|
+
if (!record.values || values.some((value, index) => !Object.is(value, record.values[index]))) changed.push([record, record.version])
|
|
462
|
+
} catch (error) {
|
|
463
|
+
console.error(error)
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
for (const [record] of changed) await cleanup(record)
|
|
467
|
+
if (active) for (const [record, version] of changed) if (record.mounted && record.version === version) {
|
|
468
|
+
try {
|
|
469
|
+
record.values = readDependencies(record)
|
|
470
|
+
invoke(record)
|
|
471
|
+
} catch (error) {
|
|
472
|
+
record.values = undefined
|
|
473
|
+
console.error(error)
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
})()
|
|
477
|
+
flushing = operation
|
|
478
|
+
try { await operation } finally {
|
|
479
|
+
if (flushing === operation) flushing = undefined
|
|
480
|
+
if (active) schedule()
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
function readDependencies(record) {
|
|
484
|
+
${hasDependencyExpressions ? printDerivedDependencyRead("__kRuntime.browserState") : ""}
|
|
485
|
+
const values = (record.effect.dependencies ?? []).map(id => {
|
|
486
|
+
const value = __kRuntime.browserState.get(id)
|
|
487
|
+
if (value !== null && typeof value !== "string" && typeof value !== "boolean" && !(typeof value === "number" && Number.isFinite(value) && !Object.is(value, -0))) throw new Error("useEffect() dependency state must remain a JSON-safe primitive")
|
|
488
|
+
return value
|
|
489
|
+
})
|
|
490
|
+
${hasItemDependencies ? `if (record.effect.itemDependencies) {
|
|
491
|
+
const item = JSON.parse(record.marker.dataset.kEffectItem)
|
|
492
|
+
for (const field of record.effect.itemDependencies) {
|
|
493
|
+
const value = item[field]
|
|
494
|
+
if (value !== null && typeof value !== "string" && typeof value !== "boolean" && !(typeof value === "number" && Number.isFinite(value) && !Object.is(value, -0))) throw new Error(\`useEffect() keyed item dependency "\${field}" must remain a JSON-safe primitive\`)
|
|
495
|
+
values.push(value)
|
|
496
|
+
}
|
|
497
|
+
}` : ""}
|
|
498
|
+
return values
|
|
499
|
+
}
|
|
500
|
+
function invoke(record) {
|
|
501
|
+
const token = { active: true }
|
|
502
|
+
record.token = token
|
|
503
|
+
try {
|
|
504
|
+
const effect = record.effect
|
|
505
|
+
const scope = effect.list
|
|
506
|
+
? Object.fromEntries(Object.entries(effect.scope).map(([name, value]) => [name, value?.type === "list-item" ? JSON.parse(record.marker.dataset.kEffectItem) : value]))
|
|
507
|
+
: effect.scope
|
|
508
|
+
const result = modules.get(effect.module)[effect.handler](createEffectContext(__kRuntime.browserState, effect.states, __kRuntime.commitDom, scope, () => active && token.active && record.token === token))
|
|
509
|
+
if (effect.cleanup && typeof result === "function") record.cleanup = result
|
|
510
|
+
else if (result && typeof result.then === "function") result.catch(error => console.error(error))
|
|
511
|
+
} catch (error) {
|
|
512
|
+
console.error(error)
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
function cleanup(record) {
|
|
516
|
+
if (record.token) record.token.active = false
|
|
517
|
+
record.token = undefined
|
|
518
|
+
if (record.disposal) return record.disposal
|
|
519
|
+
const current = record.cleanup
|
|
520
|
+
record.cleanup = undefined
|
|
521
|
+
if (!current) return Promise.resolve()
|
|
522
|
+
const disposal = (async () => {
|
|
523
|
+
try { await current() } catch (error) { console.error(error) }
|
|
524
|
+
})()
|
|
525
|
+
record.disposal = disposal
|
|
526
|
+
startedCleanups.add(disposal)
|
|
527
|
+
disposal.finally(() => {
|
|
528
|
+
startedCleanups.delete(disposal)
|
|
529
|
+
if (record.disposal === disposal) record.disposal = undefined
|
|
530
|
+
})
|
|
531
|
+
return disposal
|
|
532
|
+
}
|
|
533
|
+
let disposal
|
|
534
|
+
return async function dispose() {
|
|
535
|
+
if (disposal) return disposal
|
|
536
|
+
disposal = (async () => {
|
|
537
|
+
active = false
|
|
538
|
+
unsubscribeCommitter?.()
|
|
539
|
+
${hasItemDependencies ? "for (const unsubscribe of unsubscribeItems) unsubscribe()\n " : ""}unsubscribeMount()
|
|
540
|
+
unsubscribeUnmount()
|
|
541
|
+
pending.clear()
|
|
542
|
+
for (const record of records) if (record.token) record.token.active = false
|
|
543
|
+
if (flushing) await flushing
|
|
544
|
+
const mounted = [...records].filter(record => record.mounted).sort((left, right) => left.index - right.index || left.order - right.order)
|
|
545
|
+
for (const record of mounted) {
|
|
546
|
+
record.mounted = false
|
|
547
|
+
await cleanup(record)
|
|
548
|
+
}
|
|
549
|
+
await Promise.all([...startedCleanups])
|
|
550
|
+
records.clear()
|
|
551
|
+
})()
|
|
552
|
+
return disposal
|
|
553
|
+
}
|
|
554
|
+
}`
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
function printDerivedDependencyRead(state) {
|
|
558
|
+
return ` if (record.effect.dependencyExpressions) return record.effect.dependencyExpressions.map(expression => {
|
|
559
|
+
const value = __kEvaluateDependency(expression, undefined, undefined, name => ${state}.get(record.effect.dependencyStates[name]))
|
|
560
|
+
if (value !== null && typeof value !== "string" && typeof value !== "boolean" && !(typeof value === "number" && Number.isFinite(value) && !Object.is(value, -0))) throw new Error("useEffect() derived dependency must remain a JSON-safe primitive")
|
|
561
|
+
return value
|
|
562
|
+
})`
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
function printOwnedEffectEntry(imports, effects, entries) {
|
|
566
|
+
const hasItemDependencies = effects.some(effect => effect.itemDependencies?.length)
|
|
567
|
+
const hasOrdinaryDependencies = effects.some(effect => effect.dependencies?.length)
|
|
568
|
+
const hasDependencyExpressions = effects.some(effect => effect.dependencyExpressions?.length)
|
|
569
|
+
const hasRowState = effects.some(effect => JSON.stringify([effect.dependencies, effect.dependencyStates, effect.states, effect.scope]).includes("$k"))
|
|
570
|
+
return `${imports.join("\n")}
|
|
571
|
+
const effects = ${inlineJson(effects)}
|
|
572
|
+
const modules = new Map([${entries}])
|
|
573
|
+
${hasItemDependencies ? "let order = 0\n" : ""}const records = effects.map((effect, index) => effect.list ? undefined : createRecord(effect, index)).filter(Boolean)
|
|
574
|
+
const listTemplates = new Map(effects.map((effect, index) => effect.list ? [effect.owner, { effect, index }] : undefined).filter(Boolean))
|
|
575
|
+
const owners = new Map(records.filter(record => record.effect.owner).map(record => [record.effect.owner, record]))
|
|
576
|
+
const listRegistrations = new WeakMap()
|
|
577
|
+
const mountedRecords = new Set(records.filter(record => record.mounted))
|
|
578
|
+
const dependencies = new Map()
|
|
579
|
+
const pending = new Set()
|
|
580
|
+
let scheduled = false
|
|
581
|
+
let flushing = false
|
|
582
|
+
let active = true
|
|
583
|
+
for (const record of records) registerDependencies(record)
|
|
584
|
+
function createRecord(effect, index${hasRowState ? ", marker" : ""}) {
|
|
585
|
+
return { effect: ${hasRowState ? "marker ? specializeRowEffect(effect, marker) : effect" : "effect"}, index, ${hasItemDependencies ? "order: order++, " : ""}mounted: !effect.owner, marker: undefined, version: 0, values: undefined, cleanup: undefined, disposal: undefined, token: undefined }
|
|
586
|
+
}
|
|
587
|
+
${hasRowState ? `function specializeRowEffect(effect, marker) {
|
|
588
|
+
const path = marker.dataset.kRowPath
|
|
589
|
+
const id = value => typeof value === "string" ? value.replace("$k", path) : value
|
|
590
|
+
const capture = value => value?.type === "state" || value?.type === "setter" || value?.type === "ref" ? { ...value, id: id(value.id) } : value?.type === "array" ? { ...value, value: value.value.map(capture) } : value?.type === "object" ? { ...value, value: value.value.map(([key, entry]) => [key, capture(entry)]) } : value
|
|
591
|
+
return { ...effect, dependencies: effect.dependencies?.map(id), dependencyStates: effect.dependencyStates && Object.fromEntries(Object.entries(effect.dependencyStates).map(([name, value]) => [name, id(value)])), states: Object.fromEntries(Object.entries(effect.states).map(([name, value]) => [name, id(value)])), scope: Object.fromEntries(Object.entries(effect.scope).map(([name, value]) => [name, capture(value)])) }
|
|
592
|
+
}
|
|
593
|
+
` : ""}
|
|
594
|
+
function registerDependencies(record) {
|
|
595
|
+
for (const id of record.effect.dependencies ?? []) {
|
|
596
|
+
const subscribers = dependencies.get(id) ?? new Set()
|
|
597
|
+
subscribers.add(record)
|
|
598
|
+
dependencies.set(id, subscribers)
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
function unregisterDependencies(record) {
|
|
602
|
+
for (const id of record.effect.dependencies ?? []) {
|
|
603
|
+
const subscribers = dependencies.get(id)
|
|
604
|
+
subscribers?.delete(record)
|
|
605
|
+
if (!subscribers?.size) dependencies.delete(id)
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
${hasItemDependencies ? `if (${hasOrdinaryDependencies}) ` : ""}__kRuntime.registerCommitter(id => {
|
|
609
|
+
if (!active) return
|
|
610
|
+
for (const record of dependencies.get(id) ?? []) if (record.mounted) pending.add(record)
|
|
611
|
+
schedule()
|
|
612
|
+
})
|
|
613
|
+
${hasItemDependencies ? `for (const listState of new Set(effects.filter(effect => effect.itemDependencies?.length).map(effect => effect.listState))) __kRuntime.registerListItemHook(listState, root => {
|
|
614
|
+
if (!active) return
|
|
615
|
+
for (const record of listRegistrations.get(root) ?? []) if (record.mounted && record.effect.itemDependencies) pending.add(record)
|
|
616
|
+
schedule()
|
|
617
|
+
})
|
|
618
|
+
` : ""}__kRuntime.registerMountHook(root => {
|
|
619
|
+
if (!active) return
|
|
620
|
+
for (const marker of matching(root)) {
|
|
621
|
+
if (marker.dataset.kEffects) {
|
|
622
|
+
if (listRegistrations.has(marker)) continue
|
|
623
|
+
const rowRecords = JSON.parse(marker.dataset.kEffects).map(owner => {
|
|
624
|
+
const template = listTemplates.get(owner)
|
|
625
|
+
if (!template) throw new Error("Keyed row effect template was not emitted")
|
|
626
|
+
const record = createRecord(template.effect, template.index${hasRowState ? ", marker" : ""})
|
|
627
|
+
registerDependencies(record)
|
|
628
|
+
mount(record, marker)
|
|
629
|
+
return record
|
|
630
|
+
})
|
|
631
|
+
listRegistrations.set(marker, rowRecords)
|
|
632
|
+
continue
|
|
633
|
+
}
|
|
634
|
+
const record = owners.get(marker.dataset.kEffect)
|
|
635
|
+
if (!record?.mounted) mount(record, marker)
|
|
636
|
+
}
|
|
637
|
+
})
|
|
638
|
+
__kRuntime.registerUnmountHook(root => {
|
|
639
|
+
if (root === document) {
|
|
640
|
+
if (!active) return
|
|
641
|
+
active = false
|
|
642
|
+
pending.clear()
|
|
643
|
+
for (const record of [...mountedRecords]) unmount(record, record.effect.list)
|
|
644
|
+
return
|
|
645
|
+
}
|
|
646
|
+
for (const marker of matching(root)) {
|
|
647
|
+
const rowRecords = listRegistrations.get(marker)
|
|
648
|
+
if (rowRecords) {
|
|
649
|
+
for (const record of rowRecords) unmount(record, true)
|
|
650
|
+
listRegistrations.delete(marker)
|
|
651
|
+
continue
|
|
652
|
+
}
|
|
653
|
+
const record = owners.get(marker.dataset.kEffect)
|
|
654
|
+
if (record?.marker === marker) unmount(record)
|
|
655
|
+
}
|
|
656
|
+
})
|
|
657
|
+
for (const record of records) if (record.mounted) start(record)
|
|
658
|
+
__kRuntime.mountDom(document)
|
|
659
|
+
addEventListener("pagehide", event => {
|
|
660
|
+
if (!event.persisted) __kRuntime.unmountDom(document)
|
|
661
|
+
})
|
|
662
|
+
function matching(root) {
|
|
663
|
+
const selector = "template[data-k-effect],[data-k-effects]"
|
|
664
|
+
return [...(root.matches?.(selector) ? [root] : []), ...(root.querySelectorAll?.(selector) ?? [])]
|
|
665
|
+
}
|
|
666
|
+
function mount(record, marker) {
|
|
667
|
+
record.mounted = true
|
|
668
|
+
record.marker = marker
|
|
669
|
+
mountedRecords.add(record)
|
|
670
|
+
const version = ++record.version
|
|
671
|
+
const begin = () => {
|
|
672
|
+
if (!active || !record.mounted || record.version !== version || !marker.isConnected) return
|
|
673
|
+
start(record)
|
|
674
|
+
}
|
|
675
|
+
if (record.disposal) record.disposal.then(begin)
|
|
676
|
+
else begin()
|
|
677
|
+
}
|
|
678
|
+
function unmount(record, dynamic = false) {
|
|
679
|
+
if (!record.mounted) return
|
|
680
|
+
record.mounted = false
|
|
681
|
+
record.marker = undefined
|
|
682
|
+
mountedRecords.delete(record)
|
|
683
|
+
if (dynamic) unregisterDependencies(record)
|
|
684
|
+
record.version++
|
|
685
|
+
pending.delete(record)
|
|
686
|
+
invokeCleanup(record)
|
|
687
|
+
}
|
|
688
|
+
function start(record) {
|
|
689
|
+
try {
|
|
690
|
+
record.values = readDependencies(record)
|
|
691
|
+
invoke(record)
|
|
692
|
+
} catch (error) {
|
|
693
|
+
console.error(error)
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
function schedule() {
|
|
697
|
+
if (!pending.size || scheduled || flushing) return
|
|
698
|
+
scheduled = true
|
|
699
|
+
queueMicrotask(flush)
|
|
700
|
+
}
|
|
701
|
+
async function flush() {
|
|
702
|
+
scheduled = false
|
|
703
|
+
if (!active) return pending.clear()
|
|
704
|
+
flushing = true
|
|
705
|
+
try {
|
|
706
|
+
const selected = [...pending].filter(record => record.mounted).sort((left, right) => left.index - right.index${hasItemDependencies ? " || left.order - right.order" : ""})
|
|
707
|
+
pending.clear()
|
|
708
|
+
const changed = []
|
|
709
|
+
for (const record of selected) {
|
|
710
|
+
try {
|
|
711
|
+
const values = readDependencies(record)
|
|
712
|
+
if (!record.values || values.some((value, index) => !Object.is(value, record.values[index]))) changed.push([record, record.version])
|
|
713
|
+
} catch (error) {
|
|
714
|
+
console.error(error)
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
for (const [record] of changed) await invokeCleanup(record)
|
|
718
|
+
if (active) for (const [record, version] of changed) if (record.mounted && record.version === version) {
|
|
719
|
+
try {
|
|
720
|
+
record.values = readDependencies(record)
|
|
721
|
+
invoke(record)
|
|
722
|
+
} catch (error) {
|
|
723
|
+
record.values = undefined
|
|
724
|
+
console.error(error)
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
} finally {
|
|
728
|
+
flushing = false
|
|
729
|
+
if (active) schedule()
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
function readDependencies(record) {
|
|
733
|
+
${hasDependencyExpressions ? printDerivedDependencyRead("browserState") : ""}
|
|
734
|
+
const values = (record.effect.dependencies ?? []).map(id => {
|
|
735
|
+
const value = browserState.get(id)
|
|
736
|
+
if (value !== null && typeof value !== "string" && typeof value !== "boolean" && !(typeof value === "number" && Number.isFinite(value) && !Object.is(value, -0))) throw new Error("useEffect() dependency state must remain a JSON-safe primitive")
|
|
737
|
+
return value
|
|
738
|
+
})
|
|
739
|
+
${hasItemDependencies ? `if (record.effect.itemDependencies) {
|
|
740
|
+
const item = JSON.parse(record.marker.dataset.kEffectItem)
|
|
741
|
+
for (const field of record.effect.itemDependencies) {
|
|
742
|
+
const value = item[field]
|
|
743
|
+
if (value !== null && typeof value !== "string" && typeof value !== "boolean" && !(typeof value === "number" && Number.isFinite(value) && !Object.is(value, -0))) throw new Error(\`useEffect() keyed item dependency "\${field}" must remain a JSON-safe primitive\`)
|
|
744
|
+
values.push(value)
|
|
745
|
+
}
|
|
746
|
+
}` : ""}
|
|
747
|
+
return values
|
|
748
|
+
}
|
|
749
|
+
function invoke(record) {
|
|
750
|
+
const token = { active: true }
|
|
751
|
+
record.token = token
|
|
752
|
+
try {
|
|
753
|
+
const effect = record.effect
|
|
754
|
+
const scope = effect.list
|
|
755
|
+
? Object.fromEntries(Object.entries(effect.scope).map(([name, value]) => [name, value?.type === "list-item" ? JSON.parse(record.marker.dataset.kEffectItem) : value]))
|
|
756
|
+
: effect.scope
|
|
757
|
+
const result = modules.get(effect.module)[effect.handler](createEffectContext(browserState, effect.states, commitDom, scope, () => active && token.active && record.token === token))
|
|
758
|
+
if (effect.cleanup && typeof result === "function") record.cleanup = result
|
|
759
|
+
else if (result && typeof result.then === "function") result.catch(error => console.error(error))
|
|
760
|
+
} catch (error) {
|
|
761
|
+
console.error(error)
|
|
762
|
+
}
|
|
763
|
+
}
|
|
764
|
+
function invokeCleanup(record) {
|
|
765
|
+
if (record.token) record.token.active = false
|
|
766
|
+
record.token = undefined
|
|
767
|
+
if (record.disposal) return record.disposal
|
|
768
|
+
const cleanup = record.cleanup
|
|
769
|
+
record.cleanup = undefined
|
|
770
|
+
if (!cleanup) return Promise.resolve()
|
|
771
|
+
const disposal = (async () => {
|
|
772
|
+
try {
|
|
773
|
+
await cleanup()
|
|
774
|
+
} catch (error) {
|
|
775
|
+
console.error(error)
|
|
776
|
+
}
|
|
777
|
+
})()
|
|
778
|
+
record.disposal = disposal
|
|
779
|
+
disposal.finally(() => {
|
|
780
|
+
if (record.disposal === disposal) record.disposal = undefined
|
|
781
|
+
})
|
|
782
|
+
return disposal
|
|
783
|
+
}`
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
function printSingleDependencyEffect(imports, effect, hasCleanup) {
|
|
787
|
+
const disposal = hasCleanup ? `
|
|
788
|
+
const dispose = root => {
|
|
789
|
+
if (root !== document || !active) return
|
|
790
|
+
active = false
|
|
791
|
+
pending = false
|
|
792
|
+
invokeCleanup()
|
|
793
|
+
}
|
|
794
|
+
if (__kRuntime.registerUnmountHook) __kRuntime.registerUnmountHook(dispose)
|
|
795
|
+
addEventListener("pagehide", event => {
|
|
796
|
+
if (event.persisted) return
|
|
797
|
+
if (__kRuntime.unmountDom) __kRuntime.unmountDom(document)
|
|
798
|
+
else dispose(document)
|
|
799
|
+
})` : ""
|
|
800
|
+
return `${imports.join("\n")}
|
|
801
|
+
const effect = ${inlineJson(effect)}
|
|
802
|
+
const dependency = effect.dependencies[0]
|
|
803
|
+
let value
|
|
804
|
+
let cleanup
|
|
805
|
+
let token
|
|
806
|
+
let active = true
|
|
807
|
+
let pending = false
|
|
808
|
+
let scheduled = false
|
|
809
|
+
let running = false
|
|
810
|
+
__kRuntime.registerCommitter(id => {
|
|
811
|
+
if (active && id === dependency) {
|
|
812
|
+
pending = true
|
|
813
|
+
schedule()
|
|
814
|
+
}
|
|
815
|
+
})
|
|
816
|
+
try {
|
|
817
|
+
value = readDependency()
|
|
818
|
+
invoke()
|
|
819
|
+
} catch (error) {
|
|
820
|
+
console.error(error)
|
|
821
|
+
}
|
|
822
|
+
function schedule() {
|
|
823
|
+
if (!pending || scheduled || running) return
|
|
824
|
+
scheduled = true
|
|
825
|
+
queueMicrotask(flush)
|
|
826
|
+
}
|
|
827
|
+
async function flush() {
|
|
828
|
+
scheduled = false
|
|
829
|
+
if (!active) return
|
|
830
|
+
let next
|
|
831
|
+
try {
|
|
832
|
+
next = readDependency()
|
|
833
|
+
} catch (error) {
|
|
834
|
+
console.error(error)
|
|
835
|
+
return
|
|
836
|
+
}
|
|
837
|
+
pending = false
|
|
838
|
+
if (Object.is(next, value)) return
|
|
839
|
+
value = next
|
|
840
|
+
running = true
|
|
841
|
+
try {
|
|
842
|
+
await invokeCleanup()
|
|
843
|
+
if (active) invoke()
|
|
844
|
+
} finally {
|
|
845
|
+
running = false
|
|
846
|
+
if (active) schedule()
|
|
847
|
+
}
|
|
848
|
+
}
|
|
849
|
+
function readDependency() {
|
|
850
|
+
const next = browserState.get(dependency)
|
|
851
|
+
if (next !== null && typeof next !== "string" && typeof next !== "boolean" && !(typeof next === "number" && Number.isFinite(next) && !Object.is(next, -0))) throw new Error("useEffect() dependency state must remain a JSON-safe primitive")
|
|
852
|
+
return next
|
|
853
|
+
}
|
|
854
|
+
function invoke() {
|
|
855
|
+
try {
|
|
856
|
+
const current = { active: true }
|
|
857
|
+
token = current
|
|
858
|
+
const result = __kEffectModule0[effect.handler](createEffectContext(browserState, effect.states, commitDom, effect.scope, () => active && current.active && token === current))
|
|
859
|
+
if (effect.cleanup && typeof result === "function") cleanup = result
|
|
860
|
+
else if (result && typeof result.then === "function") result.catch(error => console.error(error))
|
|
861
|
+
} catch (error) {
|
|
862
|
+
console.error(error)
|
|
863
|
+
}
|
|
864
|
+
}
|
|
865
|
+
async function invokeCleanup() {
|
|
866
|
+
if (token) token.active = false
|
|
867
|
+
token = undefined
|
|
868
|
+
const current = cleanup
|
|
869
|
+
cleanup = undefined
|
|
870
|
+
if (!current) return
|
|
871
|
+
try {
|
|
872
|
+
await current()
|
|
873
|
+
} catch (error) {
|
|
874
|
+
console.error(error)
|
|
875
|
+
}
|
|
876
|
+
}${disposal}`
|
|
877
|
+
}
|
|
878
|
+
|
|
879
|
+
return (effects, output, handlerModules, assetsDirectory, base, paramPath, runtimeName, navigable) => navigable
|
|
880
|
+
? effects.some(effect => effect.owner)
|
|
881
|
+
? printOwnedNavigableEffectEntry(effects, output, handlerModules, assetsDirectory, base)
|
|
882
|
+
: printNavigableEffectEntry(effects, output, handlerModules, assetsDirectory, base)
|
|
883
|
+
: printEffectEntry(effects, output, handlerModules, assetsDirectory, base, paramPath, runtimeName)
|
|
884
|
+
}
|