fusee-framework 1.8.0 → 1.9.1
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 +64 -3
- package/app/benchmark/vite.config.js +23 -0
- package/auto-imports.d.ts +4 -0
- package/bin/create-fusee-linux +0 -0
- package/bin/create-fusee-mac-arm64 +0 -0
- package/bin/create-fusee-mac-intel +0 -0
- package/bin/create-fusee-win.exe +0 -0
- package/bin/create-fusee.exe +0 -0
- package/create-fusee-go/internal/assets/embed/framework/auto-import-preset.js +15 -9
- package/create-fusee-go/internal/assets/embed/framework/core/async-context.js +27 -0
- package/create-fusee-go/internal/assets/embed/framework/core/compiler/ast.js +167 -0
- package/create-fusee-go/internal/assets/embed/framework/core/compiler/errors-list.js +47 -0
- package/create-fusee-go/internal/assets/embed/framework/core/compiler/errors.js +107 -0
- package/create-fusee-go/internal/assets/embed/framework/core/compiler/generator.js +363 -0
- package/create-fusee-go/internal/assets/embed/framework/core/compiler/lexer.js +347 -0
- package/create-fusee-go/internal/assets/embed/framework/core/compiler/main-compiler.js +115 -0
- package/create-fusee-go/internal/assets/embed/framework/core/compiler/parser.js +414 -0
- package/create-fusee-go/internal/assets/embed/framework/core/compiler/plugins/actions-plugin.js +263 -0
- package/create-fusee-go/internal/assets/embed/framework/core/compiler/plugins/compiler-plugin.d.ts +5 -0
- package/create-fusee-go/internal/assets/embed/framework/core/compiler/plugins/compiler-plugin.js +45 -0
- package/create-fusee-go/internal/assets/embed/framework/core/compiler/plugins/file-router-plugin.js +301 -0
- package/create-fusee-go/internal/assets/embed/framework/core/compiler/plugins/router-plugin.js +196 -0
- package/create-fusee-go/internal/assets/embed/framework/core/compiler/transformer.js +398 -0
- package/create-fusee-go/internal/assets/embed/framework/core/component.js +61 -23
- package/create-fusee-go/internal/assets/embed/framework/core/di.js +187 -0
- package/create-fusee-go/internal/assets/embed/framework/core/directives.js +40 -96
- package/create-fusee-go/internal/assets/embed/framework/core/evaluator.js +1 -1
- package/create-fusee-go/internal/assets/embed/framework/core/h.js +430 -0
- package/create-fusee-go/internal/assets/embed/framework/core/hydration.js +83 -0
- package/create-fusee-go/internal/assets/embed/framework/core/reconcile.js +151 -0
- package/create-fusee-go/internal/assets/embed/framework/core/signal.js +269 -0
- package/create-fusee-go/internal/assets/embed/framework/core/store.js +322 -81
- package/create-fusee-go/internal/assets/embed/framework/index.js +5 -3
- package/create-fusee-go/internal/assets/embed/framework/router/file-router.js +1 -1
- package/create-fusee-go/internal/assets/embed/framework/router/router.js +113 -21
- package/create-fusee-go/internal/assets/embed/framework/server/actions.js +173 -0
- package/create-fusee-go/internal/assets/embed/framework/server/actions.server.js +88 -0
- package/create-fusee-go/internal/assets/embed/framework/server/index.js +18 -0
- package/create-fusee-go/internal/assets/embed/framework/types/actions.d.ts +50 -0
- package/create-fusee-go/internal/assets/embed/framework/types/actions.server.d.ts +36 -0
- package/create-fusee-go/internal/assets/embed/framework/types/compiler/ast.d.ts +166 -0
- package/create-fusee-go/internal/assets/embed/framework/types/compiler/errors-codes.d.ts +23 -0
- package/create-fusee-go/internal/assets/embed/framework/types/compiler/errors.d.ts +43 -0
- package/create-fusee-go/internal/assets/embed/framework/types/compiler/generator.d.ts +8 -0
- package/create-fusee-go/internal/assets/embed/framework/types/compiler/h.d.ts +46 -0
- package/create-fusee-go/internal/assets/embed/framework/types/compiler/lexer.d.ts +27 -0
- package/create-fusee-go/internal/assets/embed/framework/types/compiler/main-compiler.d.ts +60 -0
- package/create-fusee-go/internal/assets/embed/framework/types/compiler/parser.d.ts +4 -0
- package/create-fusee-go/internal/assets/embed/framework/types/compiler/plugins/actions.d.ts +30 -0
- package/create-fusee-go/internal/assets/embed/framework/types/compiler/plugins/compiler-plugin.d.ts +5 -0
- package/create-fusee-go/internal/assets/embed/framework/types/compiler/plugins/file-router-plugins.d.ts +32 -0
- package/create-fusee-go/internal/assets/embed/framework/types/compiler/plugins/router-plugin.d.ts +24 -0
- package/create-fusee-go/internal/assets/embed/framework/types/compiler/transformer.d.ts +28 -0
- package/create-fusee-go/internal/assets/embed/framework/types/component.d.ts +89 -88
- package/create-fusee-go/internal/assets/embed/framework/types/di.d.ts +43 -0
- package/create-fusee-go/internal/assets/embed/framework/types/hydration.d.ts +30 -0
- package/create-fusee-go/internal/assets/embed/framework/types/index.d.ts +38 -22
- package/create-fusee-go/internal/assets/embed/framework/types/reconcile.d.ts +7 -0
- package/create-fusee-go/internal/assets/embed/framework/types/router.d.ts +11 -1
- package/create-fusee-go/internal/assets/embed/framework/types/server.d.ts +11 -0
- package/create-fusee-go/internal/assets/embed/framework/types/signal.d.ts +142 -79
- package/create-fusee-go/internal/assets/embed/framework/types/store.d.ts +66 -8
- package/framework/auto-import-preset.js +3 -1
- package/framework/core/async-context.js +27 -0
- package/framework/core/compiler/ast.js +167 -0
- package/framework/core/compiler/errors-list.js +47 -0
- package/framework/core/compiler/errors.js +107 -0
- package/framework/core/compiler/generator.js +363 -0
- package/framework/core/compiler/lexer.js +347 -0
- package/framework/core/compiler/main-compiler.js +115 -0
- package/framework/core/compiler/parser.js +414 -0
- package/framework/core/compiler/plugins/actions-plugin.js +263 -0
- package/framework/core/compiler/plugins/compiler-plugin.d.ts +5 -0
- package/framework/core/compiler/plugins/compiler-plugin.js +45 -0
- package/framework/core/compiler/plugins/file-router-plugin.js +301 -0
- package/framework/core/compiler/plugins/router-plugin.js +196 -0
- package/framework/core/compiler/transformer.js +398 -0
- package/framework/core/component.js +34 -10
- package/framework/core/evaluator.js +1 -1
- package/framework/core/h.js +430 -0
- package/framework/core/hydration.js +83 -0
- package/framework/core/signal.js +17 -15
- package/framework/server/actions.js +173 -0
- package/framework/server/actions.server.js +88 -0
- package/framework/server/index.js +18 -0
- package/framework/types/actions.d.ts +50 -0
- package/framework/types/actions.server.d.ts +36 -0
- package/framework/types/compiler/ast.d.ts +166 -0
- package/framework/types/compiler/errors-codes.d.ts +23 -0
- package/framework/types/compiler/errors.d.ts +43 -0
- package/framework/types/compiler/generator.d.ts +8 -0
- package/framework/types/compiler/h.d.ts +46 -0
- package/framework/types/compiler/lexer.d.ts +27 -0
- package/framework/types/compiler/main-compiler.d.ts +60 -0
- package/framework/types/compiler/parser.d.ts +4 -0
- package/framework/types/compiler/plugins/actions.d.ts +30 -0
- package/framework/types/compiler/plugins/compiler-plugin.d.ts +5 -0
- package/framework/types/compiler/plugins/file-router-plugins.d.ts +32 -0
- package/framework/types/compiler/plugins/router-plugin.d.ts +24 -0
- package/framework/types/compiler/transformer.d.ts +28 -0
- package/framework/types/hydration.d.ts +30 -0
- package/framework/types/index.d.ts +6 -0
- package/framework/types/server.d.ts +11 -0
- package/framework/types/signal.d.ts +2 -7
- package/ghid_go_fusee_integrat_go1_26.txt +276 -0
- package/ghid_rust_fusee_rust_1_95_0.txt +1118 -0
- package/package.json +44 -36
- package/vite.config.js +65 -53
- package/create-fusee-go/internal/assets/embed/framework/bin/create-fusee.js +0 -199
|
@@ -0,0 +1,430 @@
|
|
|
1
|
+
import { effect, batch, signal } from '../core/signal.js'
|
|
2
|
+
import { createEventHandler, registerDelegatedEvent, isDelegatedEvent } from '../core/event-delegation.js'
|
|
3
|
+
|
|
4
|
+
export { effect as _effect, batch as _batch }
|
|
5
|
+
|
|
6
|
+
function isGetter(v) {
|
|
7
|
+
return typeof v === 'function' && !v.isSignal
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const SENSITIVE_ATTRS = new Set(['href', 'src', 'srcset', 'formaction', 'xlink:href', 'data'])
|
|
11
|
+
const DANGEROUS_SCHEME = /^(javascript|data|vbscript|file):/i
|
|
12
|
+
|
|
13
|
+
function sanitize(name, value) {
|
|
14
|
+
if (SENSITIVE_ATTRS.has(name.toLowerCase())) {
|
|
15
|
+
const trimmed = String(value).trim()
|
|
16
|
+
if (DANGEROUS_SCHEME.test(trimmed)) {
|
|
17
|
+
console.warn(`[fusée] Blocked potential XSS on "${name}": "${value}"`)
|
|
18
|
+
return 'about:blank'
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
return value
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function h(tag, props = {}, children = [], isStatic = false) {
|
|
25
|
+
const el = document.createElement(tag)
|
|
26
|
+
const effects = []
|
|
27
|
+
|
|
28
|
+
if (!isStatic) {
|
|
29
|
+
_applyProps(el, props, effects)
|
|
30
|
+
} else {
|
|
31
|
+
_applyStaticProps(el, props)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const childNodes = _mountChildren(el, children, effects)
|
|
35
|
+
|
|
36
|
+
return {
|
|
37
|
+
node: el,
|
|
38
|
+
effects,
|
|
39
|
+
props,
|
|
40
|
+
children: childNodes,
|
|
41
|
+
_tag: tag,
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function hText(value) {
|
|
46
|
+
const node = document.createTextNode('')
|
|
47
|
+
const effects = []
|
|
48
|
+
|
|
49
|
+
if (typeof value === 'function') {
|
|
50
|
+
const cleanup = effect(() => {
|
|
51
|
+
node.textContent = String(value() ?? '')
|
|
52
|
+
})
|
|
53
|
+
effects.push(cleanup)
|
|
54
|
+
} else {
|
|
55
|
+
node.textContent = String(value ?? '')
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return { node, effects }
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function hIf(branches) {
|
|
62
|
+
const anchor = document.createComment('f-if')
|
|
63
|
+
const effects = []
|
|
64
|
+
|
|
65
|
+
let currentNodes = []
|
|
66
|
+
let currentBranch = -1
|
|
67
|
+
|
|
68
|
+
const cleanup = effect(() => {
|
|
69
|
+
let matchIdx = -1
|
|
70
|
+
for (let i = 0; i < branches.length; i++) {
|
|
71
|
+
const [cond] = branches[i]
|
|
72
|
+
if (cond === null || cond()) { matchIdx = i; break }
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (matchIdx === currentBranch) return
|
|
76
|
+
|
|
77
|
+
_unmountNodes(currentNodes)
|
|
78
|
+
currentNodes = []
|
|
79
|
+
currentBranch = matchIdx
|
|
80
|
+
|
|
81
|
+
if (matchIdx === -1) return
|
|
82
|
+
|
|
83
|
+
const [, getChildren] = branches[matchIdx]
|
|
84
|
+
const children = getChildren()
|
|
85
|
+
const parent = anchor.parentNode
|
|
86
|
+
|
|
87
|
+
if (parent) {
|
|
88
|
+
for (const fnode of children) {
|
|
89
|
+
parent.insertBefore(fnode.node, anchor)
|
|
90
|
+
currentNodes.push(fnode)
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
effects.push(cleanup)
|
|
96
|
+
|
|
97
|
+
return {
|
|
98
|
+
node: anchor,
|
|
99
|
+
effects,
|
|
100
|
+
_ifBranchNodes: () => currentNodes,
|
|
101
|
+
_isIf: true,
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export function hFor(sourceGetter, renderItem, keyFn) {
|
|
106
|
+
const anchor = document.createComment('f-for')
|
|
107
|
+
const effects = []
|
|
108
|
+
|
|
109
|
+
let keyedMap = new Map()
|
|
110
|
+
const keyEvaluatorSignal = signal(null)
|
|
111
|
+
|
|
112
|
+
const cleanup = effect(() => {
|
|
113
|
+
const list = sourceGetter() ?? []
|
|
114
|
+
const parent = anchor.parentNode
|
|
115
|
+
|
|
116
|
+
if (!parent) {
|
|
117
|
+
return
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const newMap = new Map()
|
|
121
|
+
const newKeys = []
|
|
122
|
+
|
|
123
|
+
for (let i = 0; i < list.length; i++) {
|
|
124
|
+
const item = list[i]
|
|
125
|
+
|
|
126
|
+
keyEvaluatorSignal(item)
|
|
127
|
+
const key = keyFn(keyEvaluatorSignal, i)
|
|
128
|
+
newKeys.push(key)
|
|
129
|
+
|
|
130
|
+
const exists = keyedMap.get(key)
|
|
131
|
+
if (exists) {
|
|
132
|
+
if (exists.itemSignal() !== item) {
|
|
133
|
+
exists.itemSignal(item)
|
|
134
|
+
}
|
|
135
|
+
newMap.set(key, exists)
|
|
136
|
+
} else {
|
|
137
|
+
const itemSignal = signal(item)
|
|
138
|
+
const fnode = renderItem(itemSignal, i)
|
|
139
|
+
newMap.set(key, { fnode, key, itemSignal })
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
for (const [key, entry] of keyedMap) {
|
|
144
|
+
if (!newMap.has(key)) {
|
|
145
|
+
_unmountNodes([entry.fnode])
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
for (let i = newKeys.length - 1; i >= 0; i--) {
|
|
150
|
+
const key = newKeys[i]
|
|
151
|
+
const entry = newMap.get(key)
|
|
152
|
+
const ref = i === newKeys.length - 1 ? anchor : newMap.get(newKeys[i + 1])?.fnode?.node
|
|
153
|
+
|
|
154
|
+
if (ref && entry.fnode.node.nextSibling !== ref) {
|
|
155
|
+
parent.insertBefore(entry.fnode.node, ref ?? anchor)
|
|
156
|
+
} else if (!entry.fnode.node.parentNode) {
|
|
157
|
+
parent.insertBefore(entry.fnode.node, anchor)
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
keyedMap = newMap
|
|
162
|
+
})
|
|
163
|
+
|
|
164
|
+
effects.push(cleanup)
|
|
165
|
+
|
|
166
|
+
return {
|
|
167
|
+
node: anchor,
|
|
168
|
+
effects,
|
|
169
|
+
_isFor: true,
|
|
170
|
+
_getNodes: () => [...keyedMap.values()].map(e => e.fnode),
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
export function hSlot(slots, name, fallback = []) {
|
|
175
|
+
const anchor = document.createComment(`slot:${name}`)
|
|
176
|
+
const effects = []
|
|
177
|
+
const slotFn = slots?.[name]
|
|
178
|
+
const nodes = typeof slotFn === 'function' ? slotFn() : fallback
|
|
179
|
+
return {
|
|
180
|
+
node: anchor,
|
|
181
|
+
effects,
|
|
182
|
+
_slotNodes: nodes,
|
|
183
|
+
_isSlot: true,
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
export function createComponent(name, ComponentFn, rawProps = {}, rawSlots = {}) {
|
|
188
|
+
if (!ComponentFn) {
|
|
189
|
+
console.warn(`[fusée] Component "${name}" is not registered`)
|
|
190
|
+
const placeholder = document.createComment(`missing:${name}`)
|
|
191
|
+
return { node: placeholder, effects: [] }
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
const resolvedProps = _resolveComponentProps(rawProps)
|
|
195
|
+
const listeners = {}
|
|
196
|
+
const slots = {}
|
|
197
|
+
|
|
198
|
+
for (const [key, value] of Object.entries(rawProps)) {
|
|
199
|
+
if (key.startsWith('on:')) {
|
|
200
|
+
listeners[key.slice(3)] = value
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
for (const [slotName, slotFn] of Object.entries(rawSlots)) {
|
|
205
|
+
slots[slotName] = slotFn
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
const container = document.createElement('div')
|
|
209
|
+
container.setAttribute('data-fusee-component', name)
|
|
210
|
+
|
|
211
|
+
const api = ComponentFn(resolvedProps, { listeners, slots })
|
|
212
|
+
api.render(container)
|
|
213
|
+
const root = container.firstElementChild ?? container
|
|
214
|
+
|
|
215
|
+
const effects = []
|
|
216
|
+
effects.push(() => api.unmount())
|
|
217
|
+
|
|
218
|
+
return { node: root, effects, _component: api }
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
function _applyProps(el, props, effects) {
|
|
222
|
+
for (const [key, value] of Object.entries(props)) {
|
|
223
|
+
if (key.startsWith('@')) {
|
|
224
|
+
const eventName = key.slice(1)
|
|
225
|
+
const { handler, modifiers = [] } = value
|
|
226
|
+
const handlerState = { timeoutId: null, throttleTimeoutId: null, lastRun: 0 }
|
|
227
|
+
const wrappedHandler = createEventHandler(handler, modifiers, null, null, handlerState)
|
|
228
|
+
|
|
229
|
+
let cleanup
|
|
230
|
+
if (isDelegatedEvent(eventName)) {
|
|
231
|
+
cleanup = registerDelegatedEvent(el, eventName, wrappedHandler, { modifiers, handlerState })
|
|
232
|
+
} else {
|
|
233
|
+
el.addEventListener(eventName, wrappedHandler)
|
|
234
|
+
cleanup = () => el.removeEventListener(eventName, wrappedHandler)
|
|
235
|
+
}
|
|
236
|
+
effects.push(cleanup)
|
|
237
|
+
continue
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
if (key === 'f-show') {
|
|
241
|
+
const cleanup = effect(() => {
|
|
242
|
+
el.style.display = value() ? '' : 'none'
|
|
243
|
+
})
|
|
244
|
+
effects.push(cleanup)
|
|
245
|
+
continue
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
if (key === 'f-html') {
|
|
249
|
+
const cleanup = effect(() => {
|
|
250
|
+
el.innerHTML = String(value() ?? '')
|
|
251
|
+
})
|
|
252
|
+
effects.push(cleanup)
|
|
253
|
+
continue
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
if (key === 'f-model') {
|
|
257
|
+
const sig = value()
|
|
258
|
+
const cleanup = effect(() => {
|
|
259
|
+
const v = typeof sig === 'function' ? sig() : sig
|
|
260
|
+
if (el.value !== String(v ?? '')) el.value = String(v ?? '')
|
|
261
|
+
})
|
|
262
|
+
effects.push(cleanup)
|
|
263
|
+
continue
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
if (key === 'f-ref') {
|
|
267
|
+
el.dispatchEvent(new CustomEvent('fusee:ref', {
|
|
268
|
+
detail: { name: value },
|
|
269
|
+
bubbles: true,
|
|
270
|
+
}))
|
|
271
|
+
continue
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
if (key === 'f-once') continue
|
|
275
|
+
|
|
276
|
+
if (typeof value !== 'function') {
|
|
277
|
+
_applyAttr(el, key, value)
|
|
278
|
+
continue
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
const attrName = key
|
|
282
|
+
const getter = value
|
|
283
|
+
const cleanup = effect(() => {
|
|
284
|
+
_applyAttr(el, attrName, getter())
|
|
285
|
+
})
|
|
286
|
+
effects.push(cleanup)
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
function _applyStaticProps(el, props) {
|
|
291
|
+
for (const [key, value] of Object.entries(props)) {
|
|
292
|
+
if (key.startsWith('@') || key.startsWith('f-'))
|
|
293
|
+
continue
|
|
294
|
+
if (typeof value !== 'function')
|
|
295
|
+
_applyAttr(el, key, value)
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
function _applyAttr(el, name, value) {
|
|
300
|
+
if (name === 'class') {
|
|
301
|
+
const cls = _resolveClass(value)
|
|
302
|
+
if (cls) el.className = cls
|
|
303
|
+
else el.removeAttribute('class')
|
|
304
|
+
return
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
if (name === 'style') {
|
|
308
|
+
if (value !== null && typeof value === 'object') {
|
|
309
|
+
for (const k in value) el.style[k] = value[k]
|
|
310
|
+
} else {
|
|
311
|
+
el.style.cssText = String(value ?? '')
|
|
312
|
+
}
|
|
313
|
+
return
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
if (name in el && typeof el[name] !== 'undefined' && name !== 'type') {
|
|
317
|
+
try {
|
|
318
|
+
el[name] = value
|
|
319
|
+
return
|
|
320
|
+
} catch (_) {
|
|
321
|
+
return
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
if (typeof value === 'boolean') {
|
|
326
|
+
value ? el.setAttribute(name, '') : el.removeAttribute(name)
|
|
327
|
+
return
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
if (value == null) {
|
|
331
|
+
el.removeAttribute(name)
|
|
332
|
+
return
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
el.setAttribute(name, sanitize(name, String(value)))
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
function _resolveClass(value) {
|
|
339
|
+
if (typeof value === 'string')
|
|
340
|
+
return value
|
|
341
|
+
if (Array.isArray(value))
|
|
342
|
+
return value.filter(Boolean).join(' ')
|
|
343
|
+
if (value && typeof value === 'object')
|
|
344
|
+
return Object.entries(value).filter(([, v]) => !!v).map(([k]) => k).join(' ')
|
|
345
|
+
return String(value ?? '')
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
function _mountChildren(parent, children, parentEffects) {
|
|
349
|
+
const mounted = []
|
|
350
|
+
|
|
351
|
+
for (const fnode of children) {
|
|
352
|
+
if (!fnode || !fnode.node) continue
|
|
353
|
+
|
|
354
|
+
parent.appendChild(fnode.node)
|
|
355
|
+
|
|
356
|
+
if (fnode._isSlot && fnode._slotNodes) {
|
|
357
|
+
for (const sn of fnode._slotNodes) {
|
|
358
|
+
if (sn?.node) parent.insertBefore(sn.node, null)
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
if (fnode.effects) parentEffects.push(...fnode.effects)
|
|
363
|
+
mounted.push(fnode)
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
return mounted
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
function _unmountNodes(fnodes) {
|
|
370
|
+
for (const fnode of fnodes) {
|
|
371
|
+
if (!fnode) continue
|
|
372
|
+
|
|
373
|
+
if (fnode.effects) {
|
|
374
|
+
for (let i = fnode.effects.length - 1; i >= 0; i--) {
|
|
375
|
+
const cleanup = fnode.effects[i]
|
|
376
|
+
if (typeof cleanup === 'function') cleanup()
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
if (fnode.node?.parentNode) {
|
|
381
|
+
fnode.node.parentNode.removeChild(fnode.node)
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
if (fnode._ifBranchNodes) {
|
|
385
|
+
_unmountNodes(fnode._ifBranchNodes())
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
if (fnode._getNodes) {
|
|
389
|
+
_unmountNodes(fnode._getNodes())
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
function _resolveComponentProps(rawProps) {
|
|
395
|
+
const resolved = {}
|
|
396
|
+
|
|
397
|
+
for (const [key, value] of Object.entries(rawProps)) {
|
|
398
|
+
if (key.startsWith('on:')) continue
|
|
399
|
+
|
|
400
|
+
if (isGetter(value)) {
|
|
401
|
+
Object.defineProperty(resolved, key, {
|
|
402
|
+
get: value,
|
|
403
|
+
enumerable: true,
|
|
404
|
+
configurable: true,
|
|
405
|
+
})
|
|
406
|
+
} else {
|
|
407
|
+
resolved[key] = value
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
return resolved
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
export function mount(renderFn, ctx, components, container) {
|
|
415
|
+
const fnodes = renderFn(ctx, components)
|
|
416
|
+
const effects = []
|
|
417
|
+
|
|
418
|
+
for (const fnode of fnodes) {
|
|
419
|
+
if (!fnode?.node) continue
|
|
420
|
+
container.appendChild(fnode.node)
|
|
421
|
+
if (fnode.effects) effects.push(...fnode.effects)
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
return {
|
|
425
|
+
unmount() {
|
|
426
|
+
_unmountNodes(fnodes)
|
|
427
|
+
container.innerHTML = ''
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
const _hydrationRegistry = new Map()
|
|
2
|
+
const _registeredCaches = []
|
|
3
|
+
|
|
4
|
+
export function _registerResourceCache(resourceKey, cache) {
|
|
5
|
+
_registeredCaches.push({ resourceKey, cache })
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function extractHydrationData() {
|
|
9
|
+
const snapshot = {}
|
|
10
|
+
|
|
11
|
+
for (const { resourceKey, cache } of _registeredCaches) {
|
|
12
|
+
const entries = {}
|
|
13
|
+
for (const [cacheKey, entry] of cache) {
|
|
14
|
+
entries[cacheKey] = { data: entry.data, updatedAt: entry.updatedAt }
|
|
15
|
+
}
|
|
16
|
+
if (Object.keys(entries).length > 0) {
|
|
17
|
+
snapshot[resourceKey] = entries
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
return snapshot
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function dehydrate(snapshot = extractHydrationData()) {
|
|
25
|
+
try {
|
|
26
|
+
const json = JSON.stringify(snapshot)
|
|
27
|
+
return json.replace(/<\/script>/gi, '<\\/script>')
|
|
28
|
+
} catch (err) {
|
|
29
|
+
console.error('[fusée] dehydrate() failed to serialize hydration data:', err)
|
|
30
|
+
return '{}'
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function loadHydration(snapshot) {
|
|
35
|
+
if (!snapshot || typeof snapshot !== 'object') {
|
|
36
|
+
console.warn('[fusée] loadHydration() received invalid snapshot')
|
|
37
|
+
return
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
for (const [resourceKey, entries] of Object.entries(snapshot)) {
|
|
41
|
+
if (!_hydrationRegistry.has(resourceKey)) {
|
|
42
|
+
_hydrationRegistry.set(resourceKey, new Map())
|
|
43
|
+
}
|
|
44
|
+
const map = _hydrationRegistry.get(resourceKey)
|
|
45
|
+
for (const [cacheKey, entry] of Object.entries(entries)) {
|
|
46
|
+
map.set(cacheKey, { data: entry.data, updatedAt: entry.updatedAt })
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function hydrateFromWindow() {
|
|
52
|
+
if (typeof window === 'undefined') return
|
|
53
|
+
const raw = window.__FUSEE_HYDRATION__
|
|
54
|
+
if (raw) loadHydration(raw)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function getHydratedEntries(resourceKey) {
|
|
58
|
+
return _hydrationRegistry.get(resourceKey) ?? null
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function isHydrationFresh(resourceKey, cacheKey, staleTime = 0) {
|
|
62
|
+
const entries = _hydrationRegistry.get(resourceKey)
|
|
63
|
+
if (!entries) return false
|
|
64
|
+
const entry = entries.get(cacheKey)
|
|
65
|
+
if (!entry) return false
|
|
66
|
+
return (Date.now() - entry.updatedAt) <= staleTime
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export function clearHydration() {
|
|
70
|
+
_hydrationRegistry.clear()
|
|
71
|
+
_registeredCaches.length = 0
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export function getHydrationSnapshot() {
|
|
75
|
+
const out = {}
|
|
76
|
+
for (const [resourceKey, map] of _hydrationRegistry) {
|
|
77
|
+
out[resourceKey] = {}
|
|
78
|
+
for (const [cacheKey, entry] of map) {
|
|
79
|
+
out[resourceKey][cacheKey] = { ...entry }
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
return out
|
|
83
|
+
}
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
export function reconcileArrays(parentNode, anchor, oldList, newList, keyFn, createFn, updateFn) {
|
|
2
|
+
let oldEnd = oldList.length - 1;
|
|
3
|
+
let newEnd = newList.length - 1;
|
|
4
|
+
let oldStart = 0;
|
|
5
|
+
let newStart = 0;
|
|
6
|
+
|
|
7
|
+
let oldStartNode = oldList[oldStart];
|
|
8
|
+
let oldEndNode = oldList[oldEnd];
|
|
9
|
+
let newStartItem = newList[newStart];
|
|
10
|
+
let newEndItem = newList[newEnd];
|
|
11
|
+
|
|
12
|
+
const nextOrder = new Array(newList.length);
|
|
13
|
+
|
|
14
|
+
while (oldStart <= oldEnd && newStart <= newEnd) {
|
|
15
|
+
if (oldStartNode === null) {
|
|
16
|
+
oldStartNode = oldList[++oldStart];
|
|
17
|
+
} else if (oldEndNode === null) {
|
|
18
|
+
oldEndNode = oldList[--oldEnd];
|
|
19
|
+
} else if (keyFn(oldStartNode) === keyFn(newStartItem)) {
|
|
20
|
+
updateFn(oldStartNode, newStartItem, newStart);
|
|
21
|
+
nextOrder[newStart] = oldStartNode;
|
|
22
|
+
oldStartNode = oldList[++oldStart];
|
|
23
|
+
newStartItem = newList[++newStart];
|
|
24
|
+
} else if (keyFn(oldEndNode) === keyFn(newEndItem)) {
|
|
25
|
+
updateFn(oldEndNode, newEndItem, newEnd);
|
|
26
|
+
nextOrder[newEnd] = oldEndNode;
|
|
27
|
+
oldEndNode = oldList[--oldEnd];
|
|
28
|
+
newEndItem = newList[--newEnd];
|
|
29
|
+
} else if (keyFn(oldStartNode) === keyFn(newEndItem)) {
|
|
30
|
+
updateFn(oldStartNode, newEndItem, newEnd);
|
|
31
|
+
const nextSibling = oldEndNode.node.nextSibling;
|
|
32
|
+
parentNode.insertBefore(oldStartNode.node, nextSibling === anchor ? anchor : nextSibling);
|
|
33
|
+
nextOrder[newEnd] = oldStartNode;
|
|
34
|
+
oldStartNode = oldList[++oldStart];
|
|
35
|
+
newEndItem = newList[--newEnd];
|
|
36
|
+
} else if (keyFn(oldEndNode) === keyFn(newStartItem)) {
|
|
37
|
+
updateFn(oldEndNode, newStartItem, newStart);
|
|
38
|
+
parentNode.insertBefore(oldEndNode.node, oldStartNode.node);
|
|
39
|
+
nextOrder[newStart] = oldEndNode;
|
|
40
|
+
oldEndNode = oldList[--oldEnd];
|
|
41
|
+
newStartItem = newList[++newStart];
|
|
42
|
+
} else {
|
|
43
|
+
break;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (oldStart > oldEnd) {
|
|
48
|
+
for (; newStart <= newEnd; ++newStart) {
|
|
49
|
+
const nextPos = newStart + 1;
|
|
50
|
+
const refNode = nextPos < newList.length && nextOrder[nextPos] ? nextOrder[nextPos].node : (oldStart < oldList.length && oldList[oldStart] ? oldList[oldStart].node : anchor);
|
|
51
|
+
const newNode = createFn(newList[newStart], newStart);
|
|
52
|
+
parentNode.insertBefore(newNode.node, refNode);
|
|
53
|
+
nextOrder[newStart] = newNode;
|
|
54
|
+
}
|
|
55
|
+
} else if (newStart > newEnd) {
|
|
56
|
+
for (; oldStart <= oldEnd; ++oldStart) {
|
|
57
|
+
if (oldList[oldStart]) {
|
|
58
|
+
const node = oldList[oldStart];
|
|
59
|
+
node.node.remove();
|
|
60
|
+
if (node.effects) {
|
|
61
|
+
node.effects.forEach(cleanup => typeof cleanup === 'function' && cleanup());
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
} else {
|
|
66
|
+
const keyToIndex = new Map();
|
|
67
|
+
for (let i = newStart; i <= newEnd; ++i) {
|
|
68
|
+
keyToIndex.set(keyFn(newList[i]), i);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
let nodesToMove = 0;
|
|
72
|
+
let lastIndex = 0;
|
|
73
|
+
const newIndexToOldIndexMap = new Array(newEnd - newStart + 1).fill(0);
|
|
74
|
+
|
|
75
|
+
for (let i = oldStart; i <= oldEnd; ++i) {
|
|
76
|
+
const oldNode = oldList[i];
|
|
77
|
+
if (!oldNode) continue;
|
|
78
|
+
|
|
79
|
+
const newIndex = keyToIndex.get(keyFn(oldNode));
|
|
80
|
+
if (newIndex === undefined) {
|
|
81
|
+
oldNode.node.remove();
|
|
82
|
+
if (oldNode.effects) {
|
|
83
|
+
oldNode.effects.forEach(cleanup => typeof cleanup === 'function' && cleanup());
|
|
84
|
+
}
|
|
85
|
+
} else {
|
|
86
|
+
updateFn(oldNode, newList[newIndex], newIndex);
|
|
87
|
+
newIndexToOldIndexMap[newIndex - newStart] = i + 1;
|
|
88
|
+
if (newIndex >= lastIndex) {
|
|
89
|
+
lastIndex = newIndex;
|
|
90
|
+
} else {
|
|
91
|
+
nodesToMove++;
|
|
92
|
+
}
|
|
93
|
+
nextOrder[newIndex] = oldNode;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
for (let i = newEnd - newStart; i >= 0; i--) {
|
|
98
|
+
const newIndex = i + newStart;
|
|
99
|
+
const nextPos = newIndex + 1;
|
|
100
|
+
const refNode = nextPos < nextOrder.length && nextOrder[nextPos] ? nextOrder[nextPos].node : anchor;
|
|
101
|
+
|
|
102
|
+
if (newIndexToOldIndexMap[i] === 0) {
|
|
103
|
+
const newNode = createFn(newList[newIndex], newIndex);
|
|
104
|
+
parentNode.insertBefore(newNode.node, refNode);
|
|
105
|
+
nextOrder[newIndex] = newNode;
|
|
106
|
+
} else if (nodesToMove > 0) {
|
|
107
|
+
let currentPosIndex = i;
|
|
108
|
+
const oldNode = nextOrder[newIndex];
|
|
109
|
+
if (oldNode.node.nextSibling !== refNode) {
|
|
110
|
+
parentNode.insertBefore(oldNode.node, refNode);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
return nextOrder;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export function reconcile(container, oldList, newList, keyFn, createFn) {
|
|
120
|
+
if (!container._renderedNodes) {
|
|
121
|
+
container._renderedNodes = [];
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const mappedOldList = container._renderedNodes;
|
|
125
|
+
const mappedNewList = newList.map((item, index) => ({
|
|
126
|
+
item: item,
|
|
127
|
+
key: keyFn(item),
|
|
128
|
+
rawIndex: index
|
|
129
|
+
}));
|
|
130
|
+
|
|
131
|
+
const nextOrder = reconcileArrays(
|
|
132
|
+
container,
|
|
133
|
+
null,
|
|
134
|
+
mappedOldList,
|
|
135
|
+
mappedNewList,
|
|
136
|
+
(x) => x.key,
|
|
137
|
+
(newItem) => {
|
|
138
|
+
const domNode = createFn(newItem.item);
|
|
139
|
+
return {
|
|
140
|
+
node: domNode,
|
|
141
|
+
item: newItem.item,
|
|
142
|
+
key: newItem.key
|
|
143
|
+
};
|
|
144
|
+
},
|
|
145
|
+
(oldNode, newItem) => {
|
|
146
|
+
oldNode.item = newItem.item;
|
|
147
|
+
}
|
|
148
|
+
);
|
|
149
|
+
|
|
150
|
+
container._renderedNodes = nextOrder;
|
|
151
|
+
}
|