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.
Files changed (112) hide show
  1. package/README.md +29 -13
  2. package/app/benchmark/benchmark.js +633 -0
  3. package/app/benchmark/index.html +102 -0
  4. package/app/benchmark/styles.css +249 -0
  5. package/app/components/Counter.js +17 -7
  6. package/app/components/CounterSection.js +3 -4
  7. package/app/main.js +29 -62
  8. package/app/main.ts +72 -63
  9. package/app/pages/404.js +16 -0
  10. package/app/pages/[...all].js +35 -0
  11. package/app/pages/[...all].ts +35 -0
  12. package/app/pages/_layout.js +26 -0
  13. package/app/pages/_layout.ts +26 -0
  14. package/app/pages/about.js +23 -0
  15. package/app/pages/about.ts +29 -0
  16. package/app/pages/composables.js +158 -0
  17. package/app/pages/composables.ts +158 -0
  18. package/app/pages/dynamic.js +40 -0
  19. package/app/pages/dynamic.ts +40 -0
  20. package/app/pages/index.js +117 -0
  21. package/app/pages/index.ts +78 -0
  22. package/app/pages/login.ts +21 -0
  23. package/app/pages/objects/[id].js +56 -0
  24. package/app/pages/objects/[id].ts +56 -0
  25. package/app/pages/posts/[category]/[slug].js +56 -0
  26. package/app/pages/posts/[category]/[slug].ts +56 -0
  27. package/app/pages/router-example.js +56 -0
  28. package/app/pages/router-example.ts +56 -0
  29. package/app/pages/test-for.js +65 -0
  30. package/app/pages/test-for.ts +80 -0
  31. package/app/pages/users/[id].js +24 -0
  32. package/app/pages/users/[id].ts +46 -0
  33. package/app/pages/users/index.js +22 -0
  34. package/app/pages/users/index.ts +29 -0
  35. package/app/pages/users.js +43 -0
  36. package/app/pages/users.ts +78 -0
  37. package/app/routes/About.js +1 -1
  38. package/app/routes/About.ts +1 -1
  39. package/app/routes/AuthRoutes.ts +41 -0
  40. package/app/routes/Home.js +9 -0
  41. package/app/routes/NotFound.js +16 -0
  42. package/app/routes/NotFound.ts +16 -0
  43. package/app/routes/RouterExample.js +56 -0
  44. package/app/routes/RouterExample.ts +56 -0
  45. package/app/routes/WildcardDemo.js +35 -0
  46. package/app/routes/WildcardDemo.ts +35 -0
  47. package/auto-imports.d.ts +1 -0
  48. package/bin/create-fusee-linux +0 -0
  49. package/bin/create-fusee-mac-arm64 +0 -0
  50. package/bin/create-fusee-mac-intel +0 -0
  51. package/bin/create-fusee-win.exe +0 -0
  52. package/bin/create-fusee.exe +0 -0
  53. package/bin/index.js +38 -0
  54. package/create-fusee-go/cmd/generate.go +72 -0
  55. package/create-fusee-go/cmd/init.go +38 -0
  56. package/create-fusee-go/cmd/root.go +121 -0
  57. package/create-fusee-go/go.mod +10 -0
  58. package/create-fusee-go/go.sum +10 -0
  59. package/create-fusee-go/internal/assets/assets.go +78 -0
  60. package/create-fusee-go/internal/assets/embed/framework/auto-import-preset.js +9 -0
  61. package/create-fusee-go/internal/assets/embed/framework/core/compiler.js +253 -0
  62. package/create-fusee-go/internal/assets/embed/framework/core/component.js +266 -0
  63. package/create-fusee-go/internal/assets/embed/framework/core/composable.js +27 -0
  64. package/create-fusee-go/internal/assets/embed/framework/core/directives.js +875 -0
  65. package/create-fusee-go/internal/assets/embed/framework/core/evaluator.js +153 -0
  66. package/create-fusee-go/internal/assets/embed/framework/core/event-delegation.js +191 -0
  67. package/create-fusee-go/internal/assets/embed/framework/core/signal.js +412 -0
  68. package/create-fusee-go/internal/assets/embed/framework/core/store.js +81 -0
  69. package/create-fusee-go/internal/assets/embed/framework/index.d.ts +3 -0
  70. package/create-fusee-go/internal/assets/embed/framework/index.js +10 -0
  71. package/create-fusee-go/internal/assets/embed/framework/router/file-router.js +177 -0
  72. package/create-fusee-go/internal/assets/embed/framework/router/router.js +462 -0
  73. package/create-fusee-go/internal/assets/embed/framework/types/compiler.d.ts +15 -0
  74. package/create-fusee-go/internal/assets/embed/framework/types/component.d.ts +88 -0
  75. package/create-fusee-go/internal/assets/embed/framework/types/composable.d.ts +14 -0
  76. package/create-fusee-go/internal/assets/embed/framework/types/directives.d.ts +55 -0
  77. package/create-fusee-go/internal/assets/embed/framework/types/evaluator.d.ts +13 -0
  78. package/create-fusee-go/internal/assets/embed/framework/types/event-delegation.d.ts +32 -0
  79. package/create-fusee-go/internal/assets/embed/framework/types/index.d.ts +22 -0
  80. package/create-fusee-go/internal/assets/embed/framework/types/router.d.ts +32 -0
  81. package/create-fusee-go/internal/assets/embed/framework/types/signal.d.ts +79 -0
  82. package/create-fusee-go/internal/assets/embed/framework/types/store.d.ts +8 -0
  83. package/create-fusee-go/internal/assets/templates/Counter.tmpl +26 -0
  84. package/create-fusee-go/internal/assets/templates/component.tmpl +11 -0
  85. package/create-fusee-go/internal/assets/templates/components/Loading.tmpl +24 -0
  86. package/create-fusee-go/internal/assets/templates/index.html.tmpl +15 -0
  87. package/create-fusee-go/internal/assets/templates/main.tmpl +14 -0
  88. package/create-fusee-go/internal/assets/templates/package.json.tmpl +14 -0
  89. package/create-fusee-go/internal/assets/templates/page.tmpl +12 -0
  90. package/create-fusee-go/internal/assets/templates/pages/about.tmpl +20 -0
  91. package/create-fusee-go/internal/assets/templates/pages/index.tmpl +22 -0
  92. package/create-fusee-go/internal/assets/templates/pages/layout.tmpl +18 -0
  93. package/create-fusee-go/internal/assets/templates/tsconfig.json.tmpl +14 -0
  94. package/create-fusee-go/internal/assets/templates/vite.config.js.tmpl +18 -0
  95. package/create-fusee-go/main.go +7 -0
  96. package/framework/auto-import-preset.js +1 -1
  97. package/framework/core/compiler.js +252 -249
  98. package/framework/core/component.js +265 -260
  99. package/framework/core/directives.js +875 -786
  100. package/framework/core/evaluator.js +153 -70
  101. package/framework/core/event-delegation.js +33 -14
  102. package/framework/core/signal.js +412 -403
  103. package/framework/index.js +10 -11
  104. package/framework/router/file-router.js +177 -0
  105. package/framework/router/router.js +462 -80
  106. package/framework/types/event-delegation.d.ts +0 -58
  107. package/framework/types/router.d.ts +32 -15
  108. package/framework/types/signal.d.ts +1 -0
  109. package/index.html +638 -626
  110. package/package.json +5 -3
  111. package/tsconfig.json +1 -0
  112. /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,3 @@
1
+ // ─── Fusée Framework Main Types ──────────────────────────────────────────────
2
+
3
+ export * from './types/index'
@@ -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
+ }