fusee-framework 1.6.0 → 1.7.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/README.md +29 -13
- package/app/benchmark/benchmark.js +633 -0
- package/app/benchmark/index.html +102 -0
- package/app/benchmark/styles.css +249 -0
- package/app/components/Counter.js +17 -7
- package/app/components/CounterSection.js +3 -4
- package/app/main.js +29 -62
- package/app/main.ts +72 -63
- package/app/pages/404.js +16 -0
- package/app/pages/[...all].js +35 -0
- package/app/pages/[...all].ts +35 -0
- package/app/pages/_layout.js +26 -0
- package/app/pages/_layout.ts +26 -0
- package/app/pages/about.js +23 -0
- package/app/pages/about.ts +29 -0
- package/app/pages/composables.js +158 -0
- package/app/pages/composables.ts +158 -0
- package/app/pages/dynamic.js +40 -0
- package/app/pages/dynamic.ts +40 -0
- package/app/pages/index.js +117 -0
- package/app/pages/index.ts +78 -0
- package/app/pages/login.ts +21 -0
- package/app/pages/objects/[id].js +56 -0
- package/app/pages/objects/[id].ts +56 -0
- package/app/pages/posts/[category]/[slug].js +56 -0
- package/app/pages/posts/[category]/[slug].ts +56 -0
- package/app/pages/router-example.js +56 -0
- package/app/pages/router-example.ts +56 -0
- package/app/pages/test-for.js +65 -0
- package/app/pages/test-for.ts +80 -0
- package/app/pages/users/[id].js +24 -0
- package/app/pages/users/[id].ts +46 -0
- package/app/pages/users/index.js +22 -0
- package/app/pages/users/index.ts +29 -0
- package/app/pages/users.js +43 -0
- package/app/pages/users.ts +78 -0
- package/app/routes/About.js +1 -1
- package/app/routes/About.ts +1 -1
- package/app/routes/AuthRoutes.ts +41 -0
- package/app/routes/Home.js +9 -0
- package/app/routes/NotFound.js +16 -0
- package/app/routes/NotFound.ts +16 -0
- package/app/routes/RouterExample.js +56 -0
- package/app/routes/RouterExample.ts +56 -0
- package/app/routes/WildcardDemo.js +35 -0
- package/app/routes/WildcardDemo.ts +35 -0
- package/auto-imports.d.ts +1 -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/bin/index.js +38 -0
- package/create-fusee-go/cmd/generate.go +72 -0
- package/create-fusee-go/cmd/init.go +38 -0
- package/create-fusee-go/cmd/root.go +121 -0
- package/create-fusee-go/go.mod +10 -0
- package/create-fusee-go/go.sum +10 -0
- package/create-fusee-go/internal/assets/assets.go +78 -0
- package/create-fusee-go/internal/assets/embed/framework/auto-import-preset.js +9 -0
- package/create-fusee-go/internal/assets/embed/framework/core/compiler.js +253 -0
- package/create-fusee-go/internal/assets/embed/framework/core/component.js +266 -0
- package/create-fusee-go/internal/assets/embed/framework/core/composable.js +27 -0
- package/create-fusee-go/internal/assets/embed/framework/core/directives.js +875 -0
- package/create-fusee-go/internal/assets/embed/framework/core/evaluator.js +153 -0
- package/create-fusee-go/internal/assets/embed/framework/core/event-delegation.js +191 -0
- package/create-fusee-go/internal/assets/embed/framework/core/signal.js +412 -0
- package/create-fusee-go/internal/assets/embed/framework/core/store.js +81 -0
- package/create-fusee-go/internal/assets/embed/framework/index.d.ts +3 -0
- package/create-fusee-go/internal/assets/embed/framework/index.js +10 -0
- package/create-fusee-go/internal/assets/embed/framework/router/file-router.js +177 -0
- package/create-fusee-go/internal/assets/embed/framework/router/router.js +462 -0
- package/create-fusee-go/internal/assets/embed/framework/types/compiler.d.ts +15 -0
- package/create-fusee-go/internal/assets/embed/framework/types/component.d.ts +88 -0
- package/create-fusee-go/internal/assets/embed/framework/types/composable.d.ts +14 -0
- package/create-fusee-go/internal/assets/embed/framework/types/directives.d.ts +55 -0
- package/create-fusee-go/internal/assets/embed/framework/types/evaluator.d.ts +13 -0
- package/create-fusee-go/internal/assets/embed/framework/types/event-delegation.d.ts +32 -0
- package/create-fusee-go/internal/assets/embed/framework/types/index.d.ts +22 -0
- package/create-fusee-go/internal/assets/embed/framework/types/router.d.ts +32 -0
- package/create-fusee-go/internal/assets/embed/framework/types/signal.d.ts +79 -0
- package/create-fusee-go/internal/assets/embed/framework/types/store.d.ts +8 -0
- package/create-fusee-go/internal/assets/templates/Counter.tmpl +26 -0
- package/create-fusee-go/internal/assets/templates/component.tmpl +11 -0
- package/create-fusee-go/internal/assets/templates/components/Loading.tmpl +24 -0
- package/create-fusee-go/internal/assets/templates/index.html.tmpl +15 -0
- package/create-fusee-go/internal/assets/templates/main.tmpl +14 -0
- package/create-fusee-go/internal/assets/templates/package.json.tmpl +14 -0
- package/create-fusee-go/internal/assets/templates/page.tmpl +12 -0
- package/create-fusee-go/internal/assets/templates/pages/about.tmpl +20 -0
- package/create-fusee-go/internal/assets/templates/pages/index.tmpl +22 -0
- package/create-fusee-go/internal/assets/templates/pages/layout.tmpl +18 -0
- package/create-fusee-go/internal/assets/templates/tsconfig.json.tmpl +14 -0
- package/create-fusee-go/internal/assets/templates/vite.config.js.tmpl +18 -0
- package/create-fusee-go/main.go +7 -0
- package/framework/auto-import-preset.js +1 -1
- package/framework/core/compiler.js +252 -249
- package/framework/core/component.js +265 -260
- package/framework/core/directives.js +875 -786
- package/framework/core/evaluator.js +153 -70
- package/framework/core/event-delegation.js +33 -14
- package/framework/core/signal.js +412 -403
- package/framework/index.js +10 -11
- package/framework/router/file-router.js +177 -0
- package/framework/router/router.js +462 -80
- package/framework/types/event-delegation.d.ts +0 -58
- package/framework/types/router.d.ts +32 -15
- package/framework/types/signal.d.ts +1 -0
- package/index.html +638 -626
- package/package.json +5 -3
- package/tsconfig.json +1 -0
- /package/{framework → create-fusee-go/internal/assets/embed/framework}/bin/create-fusee.js +0 -0
|
@@ -0,0 +1,412 @@
|
|
|
1
|
+
let currentEffect = null
|
|
2
|
+
let currentOwner = null
|
|
3
|
+
let onEffectCreated = null
|
|
4
|
+
|
|
5
|
+
export function setEffectHook(fn) { onEffectCreated = fn }
|
|
6
|
+
|
|
7
|
+
export function onCleanup(fn) {
|
|
8
|
+
if (!currentOwner) return
|
|
9
|
+
currentOwner.cleanups.add(fn)
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
let batchDepth = 0
|
|
13
|
+
const pendingEffects = new Set()
|
|
14
|
+
|
|
15
|
+
export function batch(fn) {
|
|
16
|
+
batchDepth++
|
|
17
|
+
try {
|
|
18
|
+
return fn()
|
|
19
|
+
} finally {
|
|
20
|
+
batchDepth--
|
|
21
|
+
if (batchDepth === 0) {
|
|
22
|
+
while (pendingEffects.size > 0) {
|
|
23
|
+
const toRun = [...pendingEffects]
|
|
24
|
+
pendingEffects.clear()
|
|
25
|
+
for (const eff of toRun) eff()
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function scheduleEffect(effectFn) {
|
|
32
|
+
if (batchDepth > 0) {
|
|
33
|
+
pendingEffects.add(effectFn)
|
|
34
|
+
} else {
|
|
35
|
+
effectFn()
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function signal(initialValue) {
|
|
40
|
+
let value = initialValue
|
|
41
|
+
const subscribers = new Set()
|
|
42
|
+
|
|
43
|
+
function accessor() {
|
|
44
|
+
if (arguments.length === 0) {
|
|
45
|
+
if (currentEffect) {
|
|
46
|
+
subscribers.add(currentEffect)
|
|
47
|
+
currentEffect.deps.add(subscribers)
|
|
48
|
+
}
|
|
49
|
+
return value
|
|
50
|
+
} else {
|
|
51
|
+
const newValue = arguments[0]
|
|
52
|
+
if (!Object.is(value, newValue)) {
|
|
53
|
+
value = newValue
|
|
54
|
+
for (const sub of [...subscribers]) scheduleEffect(sub)
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
accessor.isSignal = true
|
|
60
|
+
|
|
61
|
+
if (Array.isArray(initialValue)) addMutatingArrayMethods(accessor)
|
|
62
|
+
addReactiveArrayMethods(accessor)
|
|
63
|
+
|
|
64
|
+
return accessor
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function effect(fn) {
|
|
68
|
+
let active = true
|
|
69
|
+
let running = false
|
|
70
|
+
|
|
71
|
+
const owner = {
|
|
72
|
+
children: new Set(),
|
|
73
|
+
cleanups: new Set()
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const run = () => {
|
|
77
|
+
if (!active || running) {
|
|
78
|
+
if (running) console.warn('[framework] Recursive effect detected')
|
|
79
|
+
return
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
running = true
|
|
83
|
+
|
|
84
|
+
for (const dep of run.deps) dep.delete(run)
|
|
85
|
+
run.deps.clear()
|
|
86
|
+
|
|
87
|
+
const prevEffect = currentEffect
|
|
88
|
+
const prevOwner = currentOwner
|
|
89
|
+
|
|
90
|
+
currentEffect = run
|
|
91
|
+
currentOwner = owner
|
|
92
|
+
|
|
93
|
+
try { fn() }
|
|
94
|
+
finally {
|
|
95
|
+
currentEffect = prevEffect
|
|
96
|
+
currentOwner = prevOwner
|
|
97
|
+
running = false
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
run.deps = new Set()
|
|
102
|
+
|
|
103
|
+
const cleanup = () => {
|
|
104
|
+
if (!active) return
|
|
105
|
+
active = false
|
|
106
|
+
|
|
107
|
+
pendingEffects.delete(run)
|
|
108
|
+
|
|
109
|
+
for (const dep of run.deps) dep.delete(run)
|
|
110
|
+
run.deps.clear()
|
|
111
|
+
|
|
112
|
+
for (const child of owner.children) child()
|
|
113
|
+
owner.children.clear()
|
|
114
|
+
|
|
115
|
+
for (const fn of owner.cleanups) fn()
|
|
116
|
+
owner.cleanups.clear()
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
if (onEffectCreated) onEffectCreated(cleanup)
|
|
120
|
+
if (currentOwner) {
|
|
121
|
+
currentOwner.children.add(cleanup)
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
run()
|
|
125
|
+
return cleanup
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export function computed(fn) {
|
|
129
|
+
let value
|
|
130
|
+
let dirty = true
|
|
131
|
+
let active = true
|
|
132
|
+
const subscribers = new Set()
|
|
133
|
+
const deps = new Set()
|
|
134
|
+
|
|
135
|
+
const computedNode = () => {
|
|
136
|
+
if (!active || dirty) return
|
|
137
|
+
|
|
138
|
+
if (subscribers.size > 0) {
|
|
139
|
+
if (!runRecompute()) return
|
|
140
|
+
} else {
|
|
141
|
+
dirty = true
|
|
142
|
+
for (const d of deps) d.delete(computedNode)
|
|
143
|
+
deps.clear()
|
|
144
|
+
return
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
for (const sub of [...subscribers]) scheduleEffect(sub)
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
computedNode.deps = deps
|
|
151
|
+
|
|
152
|
+
let computing = false
|
|
153
|
+
|
|
154
|
+
function runRecompute() {
|
|
155
|
+
if (computing) {
|
|
156
|
+
console.warn('[framework] Circular dependency detected in computed()')
|
|
157
|
+
return false
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
computing = true
|
|
161
|
+
for (const d of deps) d.delete(computedNode)
|
|
162
|
+
deps.clear()
|
|
163
|
+
|
|
164
|
+
const prevEffect = currentEffect
|
|
165
|
+
currentEffect = active ? computedNode : null
|
|
166
|
+
try {
|
|
167
|
+
const newValue = fn()
|
|
168
|
+
const changed = !Object.is(newValue, value)
|
|
169
|
+
value = newValue
|
|
170
|
+
dirty = false
|
|
171
|
+
return changed
|
|
172
|
+
} finally {
|
|
173
|
+
currentEffect = prevEffect
|
|
174
|
+
computing = false
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function accessor() {
|
|
179
|
+
if (arguments.length === 0) {
|
|
180
|
+
if (active && currentEffect) {
|
|
181
|
+
subscribers.add(currentEffect)
|
|
182
|
+
currentEffect.deps.add(subscribers)
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
if (dirty) {
|
|
186
|
+
runRecompute()
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
return value
|
|
190
|
+
}
|
|
191
|
+
console.warn('[framework] computed() is read-only')
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
accessor.dispose = () => {
|
|
195
|
+
active = false
|
|
196
|
+
dirty = true
|
|
197
|
+
pendingEffects.delete(computedNode)
|
|
198
|
+
for (const d of deps) d.delete(computedNode)
|
|
199
|
+
deps.clear()
|
|
200
|
+
subscribers.clear()
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
accessor.isSignal = true
|
|
204
|
+
accessor.readonly = true
|
|
205
|
+
addReactiveArrayMethods(accessor)
|
|
206
|
+
|
|
207
|
+
return accessor
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
export function untrack(fn) {
|
|
211
|
+
const prevEffect = currentEffect
|
|
212
|
+
currentEffect = null
|
|
213
|
+
try {
|
|
214
|
+
return fn()
|
|
215
|
+
} finally {
|
|
216
|
+
currentEffect = prevEffect
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
export function watch(source, callback, options = {}) {
|
|
221
|
+
if (typeof callback !== 'function') {
|
|
222
|
+
console.warn('[framework] watch() callback must be a function')
|
|
223
|
+
return () => { }
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
const { immediate = false, equals = Object.is } = options
|
|
227
|
+
const { getter, isMultiSource } = normalizeWatchSource(source)
|
|
228
|
+
|
|
229
|
+
let initialized = false
|
|
230
|
+
let oldValue
|
|
231
|
+
const cleanupRef = { fn: null }
|
|
232
|
+
|
|
233
|
+
const onCleanup = (fn) => {
|
|
234
|
+
if (typeof fn !== 'function') {
|
|
235
|
+
console.warn('[framework] onCleanup() expects a function')
|
|
236
|
+
return
|
|
237
|
+
}
|
|
238
|
+
cleanupRef.fn = fn
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
const stopEffect = effect(() => {
|
|
242
|
+
const newValue = getter()
|
|
243
|
+
|
|
244
|
+
if (!initialized) {
|
|
245
|
+
initialized = true
|
|
246
|
+
oldValue = cloneWatchValue(newValue, isMultiSource)
|
|
247
|
+
if (immediate) {
|
|
248
|
+
untrack(() => callback(newValue, undefined, onCleanup))
|
|
249
|
+
}
|
|
250
|
+
return
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
if (!hasWatchChanged(newValue, oldValue, isMultiSource, equals)) {
|
|
254
|
+
return
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
const previousValue = oldValue
|
|
258
|
+
oldValue = cloneWatchValue(newValue, isMultiSource)
|
|
259
|
+
|
|
260
|
+
runWatchCleanup(cleanupRef)
|
|
261
|
+
untrack(() => callback(newValue, previousValue, onCleanup))
|
|
262
|
+
})
|
|
263
|
+
|
|
264
|
+
return () => {
|
|
265
|
+
stopEffect()
|
|
266
|
+
runWatchCleanup(cleanupRef)
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
export function inspect(...args) {
|
|
271
|
+
const isDev = typeof import.meta.env !== 'undefined' ? !!import.meta.env.DEV : true
|
|
272
|
+
if (!isDev) return
|
|
273
|
+
|
|
274
|
+
return effect(() => {
|
|
275
|
+
const values = args.map(arg => {
|
|
276
|
+
if (typeof arg === 'function') {
|
|
277
|
+
return arg()
|
|
278
|
+
}
|
|
279
|
+
return arg
|
|
280
|
+
})
|
|
281
|
+
console.log('[inspect]', ...values)
|
|
282
|
+
})
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
function addReactiveArrayMethods(accessor) {
|
|
286
|
+
// Transformations
|
|
287
|
+
accessor.map = (fn) => computed(() => accessor()?.map?.(fn) ?? [])
|
|
288
|
+
accessor.filter = (fn) => computed(() => accessor()?.filter?.(fn) ?? [])
|
|
289
|
+
accessor.slice = (...args) => computed(() => accessor()?.slice?.(...args) ?? [])
|
|
290
|
+
accessor.concat = (...args) => computed(() => accessor()?.concat?.(...args) ?? [])
|
|
291
|
+
accessor.flat = (depth) => computed(() => accessor()?.flat?.(depth) ?? [])
|
|
292
|
+
accessor.flatMap = (fn) => computed(() => accessor()?.flatMap?.(fn) ?? [])
|
|
293
|
+
|
|
294
|
+
// Searches
|
|
295
|
+
accessor.find = (fn) => computed(() => accessor()?.find?.(fn))
|
|
296
|
+
accessor.findLast = (fn) => computed(() => accessor()?.findLast?.(fn))
|
|
297
|
+
accessor.findIndex = (fn) => computed(() => accessor()?.findIndex?.(fn))
|
|
298
|
+
accessor.findLastIndex = (fn) => computed(() => accessor()?.findLastIndex?.(fn))
|
|
299
|
+
accessor.indexOf = (searchElement, fromIdx) => computed(() => accessor()?.indexOf?.(searchElement, fromIdx))
|
|
300
|
+
accessor.lastIndexOf = (searchElement, fromIdx) => computed(() => accessor()?.lastIndexOf?.(searchElement, fromIdx))
|
|
301
|
+
accessor.includes = (searchElement, fromIdx) => computed(() => accessor()?.includes?.(searchElement, fromIdx))
|
|
302
|
+
|
|
303
|
+
// Validations
|
|
304
|
+
accessor.every = (fn) => computed(() => accessor()?.every?.(fn))
|
|
305
|
+
accessor.some = (fn) => computed(() => accessor()?.some?.(fn))
|
|
306
|
+
|
|
307
|
+
// Accumulators & Access
|
|
308
|
+
accessor.reduce = (...args) => computed(() => accessor()?.reduce?.(...args))
|
|
309
|
+
accessor.at = (index) => computed(() => accessor()?.at?.(index))
|
|
310
|
+
accessor.join = (separator) => computed(() => accessor()?.join?.(separator) ?? '')
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
function addMutatingArrayMethods(accessor) {
|
|
314
|
+
accessor.push = (...items) => {
|
|
315
|
+
const next = [...accessor(), ...items]
|
|
316
|
+
accessor(next)
|
|
317
|
+
return next.length
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
accessor.pop = () => {
|
|
321
|
+
const arr = accessor()
|
|
322
|
+
if (arr.length === 0) return undefined
|
|
323
|
+
const item = arr[arr.length - 1]
|
|
324
|
+
accessor(arr.slice(0, -1))
|
|
325
|
+
return item
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
accessor.shift = () => {
|
|
329
|
+
const arr = accessor()
|
|
330
|
+
if (arr.length === 0) return undefined
|
|
331
|
+
const item = arr[0]
|
|
332
|
+
accessor(arr.slice(1))
|
|
333
|
+
return item
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
accessor.unshift = (...items) => {
|
|
337
|
+
const next = [...items, ...accessor()]
|
|
338
|
+
accessor(next)
|
|
339
|
+
return next.length
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
accessor.splice = (start, deleteCount, ...items) => {
|
|
343
|
+
const arr = [...accessor()]
|
|
344
|
+
const removed = arr.splice(start, deleteCount, ...items)
|
|
345
|
+
accessor(arr)
|
|
346
|
+
return removed
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
accessor.remove = (predicate) => {
|
|
350
|
+
accessor(accessor().filter((item, i) => !predicate(item, i)))
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
accessor.clear = () => {
|
|
354
|
+
accessor([])
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
accessor.sort = (compareFn) => {
|
|
358
|
+
const next = [...accessor()].sort(compareFn)
|
|
359
|
+
accessor(next)
|
|
360
|
+
return accessor
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
accessor.reverse = () => {
|
|
364
|
+
const next = [...accessor()].reverse()
|
|
365
|
+
accessor(next)
|
|
366
|
+
return accessor
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
function normalizeWatchSource(source) {
|
|
371
|
+
if (Array.isArray(source)) {
|
|
372
|
+
const getters = source.map(normalizeSingleWatchSource)
|
|
373
|
+
return {
|
|
374
|
+
getter: () => getters.map(get => get()),
|
|
375
|
+
isMultiSource: true
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
return {
|
|
379
|
+
getter: normalizeSingleWatchSource(source),
|
|
380
|
+
isMultiSource: false
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
function normalizeSingleWatchSource(source) {
|
|
385
|
+
if (typeof source === 'function') return source
|
|
386
|
+
console.warn('[framework] watch() source should be a signal, getter, or an array of those')
|
|
387
|
+
return () => source
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
function cloneWatchValue(value, isMultiSource) {
|
|
391
|
+
if (isMultiSource && Array.isArray(value)) return value.slice()
|
|
392
|
+
return value
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
function hasWatchChanged(newValue, oldValue, isMultiSource, equals) {
|
|
396
|
+
if (isMultiSource) {
|
|
397
|
+
if (!Array.isArray(oldValue) || newValue.length !== oldValue.length) return true
|
|
398
|
+
for (let i = 0; i < newValue.length; i++) {
|
|
399
|
+
if (!equals(newValue[i], oldValue[i])) return true
|
|
400
|
+
}
|
|
401
|
+
return false
|
|
402
|
+
}
|
|
403
|
+
return !equals(newValue, oldValue)
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
function runWatchCleanup(cleanupRef) {
|
|
407
|
+
if (typeof cleanupRef.fn === 'function') {
|
|
408
|
+
const fn = cleanupRef.fn
|
|
409
|
+
cleanupRef.fn = null
|
|
410
|
+
untrack(() => fn())
|
|
411
|
+
}
|
|
412
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { getCurrentInstance, setCurrentInstance } from './component.js'
|
|
2
|
+
|
|
3
|
+
const storesRegistry = new Map()
|
|
4
|
+
const storePlugins = []
|
|
5
|
+
|
|
6
|
+
export function registerStorePlugin(plugin) {
|
|
7
|
+
if (typeof plugin === 'function') {
|
|
8
|
+
storePlugins.push(plugin)
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const isDev = typeof import.meta.env !== 'undefined' ? !!import.meta.env.DEV : true
|
|
13
|
+
if (isDev && typeof window !== 'undefined') {
|
|
14
|
+
window.__FRAMEWORK_STORES__ = storesRegistry
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function defineStore(id, setup) {
|
|
18
|
+
if (typeof id !== 'string' || !id.trim()) {
|
|
19
|
+
console.error('[framework] defineStore requires a valid string ID.')
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function useStore() {
|
|
23
|
+
let store = storesRegistry.get(id)
|
|
24
|
+
|
|
25
|
+
if (!store) {
|
|
26
|
+
const prevInstance = getCurrentInstance()
|
|
27
|
+
|
|
28
|
+
setCurrentInstance(null)
|
|
29
|
+
|
|
30
|
+
try {
|
|
31
|
+
store = setup()
|
|
32
|
+
|
|
33
|
+
if (store instanceof Promise) {
|
|
34
|
+
console.error(`[framework] Store "${id}" cannot be async.`)
|
|
35
|
+
throw new Error(`Async store "${id}" is not supported.`)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
} catch (err) {
|
|
39
|
+
console.error(`[framework] Error initializing store "${id}":`, err)
|
|
40
|
+
throw err
|
|
41
|
+
} finally {
|
|
42
|
+
setCurrentInstance(prevInstance)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
Object.defineProperties(store, {
|
|
46
|
+
$id: {
|
|
47
|
+
value: id,
|
|
48
|
+
enumerable: false
|
|
49
|
+
},
|
|
50
|
+
$type: {
|
|
51
|
+
value: 'store',
|
|
52
|
+
enumerable: false
|
|
53
|
+
}
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
for (const plugin of storePlugins) {
|
|
57
|
+
try {
|
|
58
|
+
plugin(store, id)
|
|
59
|
+
} catch (e) {
|
|
60
|
+
console.warn(`[framework] Store plugin error for "${id}":`, e)
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
storesRegistry.set(id, store)
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return store
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
useStore.$id = id
|
|
71
|
+
|
|
72
|
+
return useStore
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export function resetStore(id) {
|
|
76
|
+
storesRegistry.delete(id)
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export function clearStores() {
|
|
80
|
+
storesRegistry.clear()
|
|
81
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
// ─── Framework Public API ─────────────────────────────────────────────────────
|
|
2
|
+
export { signal, computed, effect, batch, untrack, inspect, watch, onCleanup } from './core/signal.js'
|
|
3
|
+
export { defineComponent, defineAsyncComponent, onMount, onUnmount, parseSlots, provide, inject, getCurrentInstance } from './core/component.js'
|
|
4
|
+
export { createRouter, navigate, mountOutlet, currentRoute, routeParams, matchedRoutes } from './router/router.js'
|
|
5
|
+
export { generateRoutes } from './router/file-router.js'
|
|
6
|
+
export { mountTemplate } from './core/compiler.js'
|
|
7
|
+
export { defineComposable, assertSetupContext } from './core/composable.js'
|
|
8
|
+
export { defineStore, resetStore, clearStores, registerStorePlugin } from './core/store.js'
|
|
9
|
+
export { directive } from './core/directives.js'
|
|
10
|
+
export const emit = () => console.warn('[framework] emit() can only be used inside component setup()')
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import { defineAsyncComponent } from '../core/component.js';
|
|
2
|
+
|
|
3
|
+
export function generateRoutes(globResults, options = {}) {
|
|
4
|
+
const entries = _parseGlobEntries(globResults, options);
|
|
5
|
+
const tree = _buildRouteTree(entries, false);
|
|
6
|
+
_sortRoutesRecursive(tree);
|
|
7
|
+
return tree;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function _parseGlobEntries(globResults, options) {
|
|
11
|
+
const entries = [];
|
|
12
|
+
|
|
13
|
+
for (const filePath of Object.keys(globResults)) {
|
|
14
|
+
const loader = globResults[filePath];
|
|
15
|
+
|
|
16
|
+
let cleanPath = filePath
|
|
17
|
+
.replace(/^\.?\/?app\/pages\//, '')
|
|
18
|
+
.replace(/^\.?\/?pages\//, '')
|
|
19
|
+
.replace(/\.[jt]sx?$/, '');
|
|
20
|
+
|
|
21
|
+
const parts = cleanPath.split('/');
|
|
22
|
+
const rawFileName = parts.pop();
|
|
23
|
+
const rawDirSegments = parts;
|
|
24
|
+
|
|
25
|
+
const dirSegments = rawDirSegments.map(_transformSegment);
|
|
26
|
+
const fileName = _transformSegment(rawFileName);
|
|
27
|
+
|
|
28
|
+
const isIndex = rawFileName === 'index';
|
|
29
|
+
const isCatchAll = /^\[\.\.\./.test(rawFileName);
|
|
30
|
+
|
|
31
|
+
entries.push({
|
|
32
|
+
filePath,
|
|
33
|
+
loader,
|
|
34
|
+
dirSegments,
|
|
35
|
+
fileName,
|
|
36
|
+
isIndex,
|
|
37
|
+
isCatchAll,
|
|
38
|
+
component: defineAsyncComponent({
|
|
39
|
+
loader,
|
|
40
|
+
loadingComponent: options.loadingComponent
|
|
41
|
+
})
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return entries;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function _transformSegment(seg) {
|
|
49
|
+
return seg
|
|
50
|
+
.replace(/\[\.\.\.([^\]]+)\]/g, '*')
|
|
51
|
+
.replace(/\[([^\]]+)\]/g, ':$1');
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function _buildRouteTree(entries, isChild) {
|
|
55
|
+
const filesHere = new Map();
|
|
56
|
+
const grouped = new Map();
|
|
57
|
+
|
|
58
|
+
for (const entry of entries) {
|
|
59
|
+
if (entry.dirSegments.length === 0) {
|
|
60
|
+
filesHere.set(entry.fileName, entry);
|
|
61
|
+
} else {
|
|
62
|
+
const firstDir = entry.dirSegments[0];
|
|
63
|
+
if (!grouped.has(firstDir)) grouped.set(firstDir, []);
|
|
64
|
+
grouped.get(firstDir).push(entry);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
let rootLayoutEntry = null;
|
|
69
|
+
if (filesHere.has('_layout')) {
|
|
70
|
+
rootLayoutEntry = filesHere.get('_layout');
|
|
71
|
+
filesHere.delete('_layout');
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const routes = [];
|
|
75
|
+
const usedAsLayout = new Set();
|
|
76
|
+
|
|
77
|
+
for (const [dirName, dirEntries] of grouped) {
|
|
78
|
+
const layoutEntry = filesHere.get(dirName);
|
|
79
|
+
|
|
80
|
+
if (layoutEntry) {
|
|
81
|
+
usedAsLayout.add(dirName);
|
|
82
|
+
|
|
83
|
+
const childEntries = dirEntries.map(e => ({
|
|
84
|
+
...e,
|
|
85
|
+
dirSegments: e.dirSegments.slice(1)
|
|
86
|
+
}));
|
|
87
|
+
|
|
88
|
+
const children = _buildRouteTree(childEntries, true);
|
|
89
|
+
|
|
90
|
+
routes.push({
|
|
91
|
+
path: isChild ? dirName : '/' + dirName,
|
|
92
|
+
component: layoutEntry.component,
|
|
93
|
+
children
|
|
94
|
+
});
|
|
95
|
+
} else {
|
|
96
|
+
for (const entry of dirEntries) {
|
|
97
|
+
const segments = [...entry.dirSegments];
|
|
98
|
+
if (!entry.isIndex) segments.push(entry.fileName);
|
|
99
|
+
|
|
100
|
+
const routePath = isChild
|
|
101
|
+
? segments.join('/')
|
|
102
|
+
: '/' + segments.join('/');
|
|
103
|
+
|
|
104
|
+
routes.push({
|
|
105
|
+
path: routePath,
|
|
106
|
+
component: entry.component
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
for (const [name, entry] of filesHere) {
|
|
113
|
+
if (usedAsLayout.has(name)) continue;
|
|
114
|
+
|
|
115
|
+
if (entry.isIndex) {
|
|
116
|
+
routes.push({ path: isChild ? '' : '/', component: entry.component });
|
|
117
|
+
} else if (entry.isCatchAll || name === '*') {
|
|
118
|
+
routes.push({ path: '*', component: entry.component });
|
|
119
|
+
} else {
|
|
120
|
+
routes.push({
|
|
121
|
+
path: isChild ? name : '/' + name,
|
|
122
|
+
component: entry.component
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (rootLayoutEntry) {
|
|
128
|
+
return [{
|
|
129
|
+
path: '',
|
|
130
|
+
component: rootLayoutEntry.component,
|
|
131
|
+
children: routes
|
|
132
|
+
}];
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
return routes;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function _sortRoutesRecursive(routes) {
|
|
139
|
+
routes.sort(_compareRoutes);
|
|
140
|
+
for (const route of routes) {
|
|
141
|
+
if (route.children) _sortRoutesRecursive(route.children);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function _getPathArray(path) {
|
|
146
|
+
return Array.isArray(path) ? path : [path];
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function _compareRoutes(a, b) {
|
|
150
|
+
const aPaths = _getPathArray(a.path);
|
|
151
|
+
const bPaths = _getPathArray(b.path);
|
|
152
|
+
|
|
153
|
+
const aPath = aPaths[0];
|
|
154
|
+
const bPath = bPaths[0];
|
|
155
|
+
|
|
156
|
+
if (aPath === '*' && bPath !== '*') return 1;
|
|
157
|
+
if (aPath !== '*' && bPath === '*') return -1;
|
|
158
|
+
|
|
159
|
+
const aWild = aPath.endsWith('/*');
|
|
160
|
+
const bWild = bPath.endsWith('/*');
|
|
161
|
+
if (aWild && !bWild) return 1;
|
|
162
|
+
if (!aWild && bWild) return -1;
|
|
163
|
+
|
|
164
|
+
const aDynamic = aPath.includes(':');
|
|
165
|
+
const bDynamic = bPath.includes(':');
|
|
166
|
+
if (aDynamic && !bDynamic) return 1;
|
|
167
|
+
if (!aDynamic && bDynamic) return -1;
|
|
168
|
+
|
|
169
|
+
if (a.children && !b.children) return -1;
|
|
170
|
+
if (!a.children && b.children) return 1;
|
|
171
|
+
|
|
172
|
+
const aSegs = aPath.split('/').filter(Boolean).length;
|
|
173
|
+
const bSegs = bPath.split('/').filter(Boolean).length;
|
|
174
|
+
if (aSegs !== bSegs) return bSegs - aSegs;
|
|
175
|
+
|
|
176
|
+
return bPath.length - aPath.length;
|
|
177
|
+
}
|