fusee-framework 2.7.0 → 2.7.2

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 (61) hide show
  1. package/README.md +98 -26
  2. package/bin/create-fusee-linux +0 -0
  3. package/bin/create-fusee-mac-arm64 +0 -0
  4. package/bin/create-fusee-mac-intel +0 -0
  5. package/bin/create-fusee-win.exe +0 -0
  6. package/framework/auto-import-preset.js +20 -12
  7. package/framework/auto-imports.d.ts +68 -39
  8. package/framework/core/compiler.js +310 -310
  9. package/framework/core/component-core.js +445 -0
  10. package/framework/core/component.js +11 -440
  11. package/framework/core/composable.js +29 -29
  12. package/framework/core/h.js +119 -44
  13. package/framework/core/jsx-helpers.js +11 -2
  14. package/framework/core/jsx-runtime.js +73 -73
  15. package/framework/core/rust-compiler/pkg/fusee_compiler_bg.wasm +0 -0
  16. package/framework/core/store.js +333 -333
  17. package/framework/globals.js +42 -0
  18. package/framework/index.js +22 -55
  19. package/framework/router/file-router.js +184 -184
  20. package/framework/server/actions.js +183 -183
  21. package/framework/server/generate-manifest.js +3 -3
  22. package/framework/types/component.d.ts +97 -86
  23. package/framework/types/index.d.ts +118 -117
  24. package/package.json +83 -80
  25. package/framework/core/rust-compiler/Cargo.toml +0 -31
  26. package/framework/core/rust-compiler/README.md +0 -69
  27. package/framework/core/rust-compiler/pkg/README.md +0 -69
  28. package/framework/core/rust-compiler/pkg/fusee_compiler.d.ts +0 -6
  29. package/framework/core/rust-compiler/pkg/fusee_compiler.js +0 -9
  30. package/framework/core/rust-compiler/pkg/fusee_compiler_bg.js +0 -137
  31. package/framework/core/rust-compiler/pkg/fusee_compiler_bg.wasm.d.ts +0 -11
  32. package/framework/core/rust-compiler/pkg/package.json +0 -18
  33. package/framework/core/rust-compiler/src/ast.rs +0 -274
  34. package/framework/core/rust-compiler/src/errors.rs +0 -204
  35. package/framework/core/rust-compiler/src/generator.rs +0 -583
  36. package/framework/core/rust-compiler/src/lexer.rs +0 -414
  37. package/framework/core/rust-compiler/src/lib.rs +0 -87
  38. package/framework/core/rust-compiler/src/main_compiler.rs +0 -165
  39. package/framework/core/rust-compiler/src/parser.rs +0 -516
  40. package/framework/core/rust-compiler/src/ssr_generator.rs +0 -688
  41. package/framework/core/rust-compiler/src/transformer.rs +0 -562
  42. package/framework/core/rust-compiler/src/utils.rs +0 -10
  43. package/framework/engine-go/actions.go +0 -112
  44. package/framework/engine-go/cmd/server/main.go +0 -233
  45. package/framework/engine-go/comet/comet.go +0 -41
  46. package/framework/engine-go/comet/fragment.go +0 -51
  47. package/framework/engine-go/dispatcher.go +0 -120
  48. package/framework/engine-go/elements.go +0 -248
  49. package/framework/engine-go/evalexpr/evalexpr.go +0 -733
  50. package/framework/engine-go/go.mod +0 -3
  51. package/framework/engine-go/hydration.go +0 -43
  52. package/framework/engine-go/load-helpers.go +0 -29
  53. package/framework/engine-go/manifest.go +0 -41
  54. package/framework/engine-go/reactive.go +0 -123
  55. package/framework/engine-go/render.go +0 -635
  56. package/framework/engine-go/reqcontext.go +0 -48
  57. package/framework/engine-go/router.go +0 -85
  58. package/framework/engine-go/rustast/rustast.go +0 -317
  59. package/framework/engine-go/ssr.go +0 -103
  60. package/framework/engine-go/static-assets.go +0 -139
  61. package/framework/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +0 -1
@@ -1,4 +1,4 @@
1
- import { effect, batch, signal, microEffect } from '../core/signal.js'
1
+ import { effect, batch, signal, microEffect, untrack, runDetached } from '../core/signal.js'
2
2
  import { createEventHandler, registerDelegatedEvent, isDelegatedEvent } from '../core/event-delegation.js'
3
3
  import { resolveElementProperty } from '../core/element-props.js'
4
4
 
@@ -70,14 +70,19 @@ export function hIf(branches) {
70
70
 
71
71
  let currentNodes = []
72
72
  let currentBranch = -1
73
+ const mounted = _createMountTracker()
73
74
 
74
75
  const cleanup = effect(() => {
76
+ mounted.track()
75
77
  let matchIdx = -1
76
78
  for (let i = 0; i < branches.length; i++) {
77
79
  const [cond] = branches[i]
78
80
  if (cond === null || cond()) { matchIdx = i; break }
79
81
  }
80
82
 
83
+ const parent = anchor.parentNode
84
+ if (!parent) return mounted.defer()
85
+
81
86
  if (matchIdx === currentBranch) return
82
87
 
83
88
  _unmountNodes(currentNodes)
@@ -87,24 +92,28 @@ export function hIf(branches) {
87
92
  if (matchIdx === -1) return
88
93
 
89
94
  const [, getChildren] = branches[matchIdx]
90
- const children = getChildren()
91
- const parent = anchor.parentNode
95
+ // Detached: branch effects belong to the branch, which is unmounted explicitly below.
96
+ const children = runDetached(getChildren)
92
97
 
93
- if (parent) {
94
- for (const fnode of children) {
95
- parent.insertBefore(fnode.node, anchor)
96
- currentNodes.push(fnode)
97
- }
98
+ for (const fnode of children) {
99
+ if (!fnode?.node) continue
100
+ insertFNode(parent, fnode, anchor)
101
+ currentNodes.push(fnode)
98
102
  }
99
103
  })
100
104
 
101
- effects.push(cleanup)
105
+ effects.push(() => {
106
+ cleanup()
107
+ _unmountNodes(currentNodes)
108
+ currentNodes = []
109
+ })
102
110
 
103
111
  return {
104
112
  node: anchor,
105
113
  effects,
106
114
  _ifBranchNodes: () => currentNodes,
107
115
  _isIf: true,
116
+ _afterInsert: mounted.resume,
108
117
  }
109
118
  }
110
119
 
@@ -118,19 +127,28 @@ export function hFor(sourceGetter, renderItem, keyFn) {
118
127
  let _keyEvalCurrent = null
119
128
  const keyEvaluatorGetter = () => _keyEvalCurrent
120
129
  keyEvaluatorGetter.isSignal = true
130
+ const mounted = _createMountTracker()
121
131
 
122
132
  const cleanup = effect(() => {
133
+ mounted.track()
123
134
  const list = sourceGetter() ?? []
124
135
  const parent = anchor.parentNode
125
136
 
126
- if (!parent) {
127
- return
128
- }
137
+ if (!parent) return mounted.defer()
129
138
 
130
139
  const newKeys = new Array(list.length)
131
140
  for (let i = 0; i < list.length; i++) {
132
141
  _keyEvalCurrent = list[i]
133
- newKeys[i] = keyFn(keyEvaluatorGetter, i)
142
+ const key = keyFn(keyEvaluatorGetter, i)
143
+ // Loops without :key compile to a key of undefined; key them by position, as SSR does.
144
+ newKeys[i] = key === undefined ? i : key
145
+ }
146
+
147
+ if (newKeys.length === 0 && keyedMap.size > 0) {
148
+ _clearRows(parent, keyedMap)
149
+ keyedMap = new Map()
150
+ oldKeys = newKeys
151
+ return
134
152
  }
135
153
 
136
154
  if (oldKeys.length === newKeys.length) {
@@ -140,10 +158,7 @@ export function hFor(sourceGetter, renderItem, keyFn) {
140
158
  }
141
159
  if (identical) {
142
160
  for (let i = 0; i < list.length; i++) {
143
- const entry = keyedMap.get(newKeys[i])
144
- if (entry.itemSignal() !== list[i]) {
145
- entry.itemSignal(list[i])
146
- }
161
+ _setItem(keyedMap.get(newKeys[i]), list[i])
147
162
  }
148
163
  return
149
164
  }
@@ -172,12 +187,9 @@ export function hFor(sourceGetter, renderItem, keyFn) {
172
187
  const parentEl = nodeA.parentNode
173
188
  parentEl.insertBefore(nodeA, nodeB.nextSibling)
174
189
  parentEl.insertBefore(nodeB, refA)
175
- if (entryA.itemSignal() !== list[diffA]) {
176
- entryA.itemSignal(list[diffA])
177
- }
178
- if (entryB.itemSignal() !== list[diffB]) {
179
- entryB.itemSignal(list[diffB])
180
- }
190
+ // The two rows traded places: each keeps its key and receives the item now at its new index.
191
+ _setItem(entryA, list[diffB])
192
+ _setItem(entryB, list[diffA])
181
193
  oldKeys = newKeys
182
194
  return
183
195
  }
@@ -191,13 +203,13 @@ export function hFor(sourceGetter, renderItem, keyFn) {
191
203
 
192
204
  const exists = keyedMap.get(key)
193
205
  if (exists) {
194
- if (exists.itemSignal() !== item) {
195
- exists.itemSignal(item)
196
- }
206
+ _setItem(exists, item)
197
207
  newMap.set(key, exists)
198
208
  } else {
199
209
  const itemSignal = signal(item)
200
- const fnode = renderItem(itemSignal, i)
210
+ // Detached: otherwise every row's effects are registered as children of this
211
+ // list effect and stay reachable after the row is removed.
212
+ const fnode = runDetached(() => renderItem(itemSignal, i))
201
213
  newMap.set(key, { fnode, key, itemSignal })
202
214
  }
203
215
  }
@@ -228,10 +240,10 @@ export function hFor(sourceGetter, renderItem, keyFn) {
228
240
  if (inLis) {
229
241
  lisPtr--
230
242
  if (!entry.fnode.node.parentNode) {
231
- parent.insertBefore(entry.fnode.node, ref)
243
+ insertFNode(parent, entry.fnode, ref)
232
244
  }
233
245
  } else if (entry.fnode.node.nextSibling !== ref || !entry.fnode.node.parentNode) {
234
- parent.insertBefore(entry.fnode.node, ref)
246
+ insertFNode(parent, entry.fnode, ref)
235
247
  }
236
248
  }
237
249
 
@@ -239,14 +251,80 @@ export function hFor(sourceGetter, renderItem, keyFn) {
239
251
  oldKeys = newKeys
240
252
  })
241
253
 
242
- effects.push(cleanup)
254
+ effects.push(() => {
255
+ cleanup()
256
+ _unmountNodes([...keyedMap.values()].map(e => e.fnode))
257
+ keyedMap = new Map()
258
+ oldKeys = []
259
+ })
243
260
 
244
261
  return {
245
262
  node: anchor,
246
263
  effects,
247
264
  _isFor: true,
248
265
  _getNodes: () => [...keyedMap.values()].map(e => e.fnode),
266
+ _afterInsert: mounted.resume,
267
+ }
268
+ }
269
+
270
+ /**
271
+ * Unmounts every row. When the rows are the only elements in the parent, the parent is emptied
272
+ * in one operation instead of one removeChild per row, and its other nodes (the anchor, template
273
+ * whitespace, text) are put back in order.
274
+ */
275
+ function _clearRows(parent, keyedMap) {
276
+ const fnodes = [...keyedMap.values()].map(e => e.fnode)
277
+ const rowsAreElements = fnodes.every(f => f.node.nodeType === 1)
278
+
279
+ if (rowsAreElements && parent.childElementCount === fnodes.length) {
280
+ const kept = []
281
+ for (let n = parent.firstChild; n; n = n.nextSibling) {
282
+ if (n.nodeType !== 1) kept.push(n)
283
+ }
284
+ for (const fnode of fnodes) _disposeEffects(fnode)
285
+ parent.textContent = ''
286
+ parent.append(...kept)
287
+ return
249
288
  }
289
+ _unmountNodes(fnodes)
290
+ }
291
+
292
+ /** Updates a keyed row's item signal without subscribing the list effect to it. */
293
+ function _setItem(entry, item) {
294
+ if (untrack(entry.itemSignal) !== item) entry.itemSignal(item)
295
+ }
296
+
297
+ /**
298
+ * hIf/hFor are built before their anchor has a parent (children are created before the
299
+ * element that holds them), so their first render waits until insertFNode() places the
300
+ * anchor and calls _afterInsert.
301
+ */
302
+ function _createMountTracker() {
303
+ const tick = signal(0)
304
+ let pending = false
305
+ return {
306
+ track: () => tick(),
307
+ defer: () => { pending = true },
308
+ resume: () => {
309
+ if (!pending) return
310
+ pending = false
311
+ tick(untrack(tick) + 1)
312
+ }
313
+ }
314
+ }
315
+
316
+ /**
317
+ * Inserts a framework node (and slot nodes it carries) before ref, then lets anchor-based
318
+ * nodes (hIf/hFor) render now that they have a parent.
319
+ */
320
+ export function insertFNode(parent, fnode, ref = null) {
321
+ parent.insertBefore(fnode.node, ref)
322
+ if (fnode._isSlot && fnode._slotNodes) {
323
+ for (const sn of fnode._slotNodes) {
324
+ if (sn?.node) insertFNode(parent, sn, ref)
325
+ }
326
+ }
327
+ if (typeof fnode._afterInsert === 'function') fnode._afterInsert()
250
328
  }
251
329
 
252
330
  function _getLIS(arr) {
@@ -479,13 +557,7 @@ function _mountChildren(parent, children, parentEffects) {
479
557
  for (const fnode of children) {
480
558
  if (!fnode || !fnode.node) continue
481
559
 
482
- parent.appendChild(fnode.node)
483
-
484
- if (fnode._isSlot && fnode._slotNodes) {
485
- for (const sn of fnode._slotNodes) {
486
- if (sn?.node) parent.insertBefore(sn.node, null)
487
- }
488
- }
560
+ insertFNode(parent, fnode)
489
561
 
490
562
  if (fnode.effects) parentEffects.push(...fnode.effects)
491
563
  mounted.push(fnode)
@@ -494,16 +566,19 @@ function _mountChildren(parent, children, parentEffects) {
494
566
  return mounted
495
567
  }
496
568
 
569
+ function _disposeEffects(fnode) {
570
+ if (!fnode.effects) return
571
+ for (let i = fnode.effects.length - 1; i >= 0; i--) {
572
+ const cleanup = fnode.effects[i]
573
+ if (typeof cleanup === 'function') cleanup()
574
+ }
575
+ }
576
+
497
577
  function _unmountNodes(fnodes) {
498
578
  for (const fnode of fnodes) {
499
579
  if (!fnode) continue
500
580
 
501
- if (fnode.effects) {
502
- for (let i = fnode.effects.length - 1; i >= 0; i--) {
503
- const cleanup = fnode.effects[i]
504
- if (typeof cleanup === 'function') cleanup()
505
- }
506
- }
581
+ _disposeEffects(fnode)
507
582
 
508
583
  if (fnode.node?.parentNode) {
509
584
  fnode.node.parentNode.removeChild(fnode.node)
@@ -545,7 +620,7 @@ export function mount(renderFn, ctx, components, container) {
545
620
 
546
621
  for (const fnode of fnodes) {
547
622
  if (!fnode?.node) continue
548
- container.appendChild(fnode.node)
623
+ insertFNode(container, fnode)
549
624
  if (fnode.effects) effects.push(...fnode.effects)
550
625
  }
551
626
 
@@ -1,4 +1,4 @@
1
- import { hFor, hIf } from './h.js'
1
+ import { hFor, hIf, hText } from './h.js'
2
2
 
3
3
  /**
4
4
  * Flattens JSX children into a single array of fnodes, dropping
@@ -6,7 +6,8 @@ import { hFor, hIf } from './h.js'
6
6
  * behave the way JSX authors expect. Recursively flattens nested arrays,
7
7
  * which is all Fragment needs: it returns its children as-is, and whatever
8
8
  * consumes those children (h()'s children arg, or another flattenChildren
9
- * call) inlines them automatically.
9
+ * call) inlines them automatically. Text and numbers become text nodes, and
10
+ * getters (dynamic `{expr}` wrapped by the Babel plugin) become reactive ones.
10
11
  */
11
12
  export function flattenChildren(children) {
12
13
  const out = []
@@ -19,6 +20,14 @@ export function flattenChildren(children) {
19
20
  stack.unshift(...child)
20
21
  continue
21
22
  }
23
+ if (typeof child === 'string' || typeof child === 'number' || typeof child === 'bigint') {
24
+ out.push(hText(String(child)))
25
+ continue
26
+ }
27
+ if (typeof child === 'function') {
28
+ out.push(hText(child))
29
+ continue
30
+ }
22
31
  out.push(child)
23
32
  }
24
33
 
@@ -1,74 +1,74 @@
1
- // Point your bundler at this module and JSX compiles here with zero custom
2
- // parser/Babel plugin needed yet:
3
- // - esbuild/Vite: jsx: 'automatic', jsxImportSource: '<path-to-this-folder>'
4
- // - tsconfig.json: "jsx": "react-jsx", "jsxImportSource": "<path-to-this-folder>"
5
-
6
- import { h, createComponent } from './h.js'
7
- import {
8
- Fragment,
9
- For,
10
- Show,
11
- Slot,
12
- on,
13
- flattenChildren,
14
- normalizeEventProps,
15
- normalizeComponentListenerProps,
16
- splitSlotChildren
17
- } from './jsx-helpers.js'
18
- import { defineComponent as coreDefineComponent } from '../component.js' // adjust to your project layout
19
-
20
- export { Fragment, For, Show, Slot, on }
21
-
22
- /**
23
- * Wraps defineComponent so JSX can tell "a fusée component" apart from a
24
- * plain helper function (Fragment/For/Show/Slot, or your own stateless JSX
25
- * helpers). Import defineComponent from here instead of component.js in
26
- * any file that defines components with JSX.
27
- */
28
- export function defineComponent(options) {
29
- const factory = coreDefineComponent(options)
30
- factory._isFuseeComponent = true
31
- factory._componentName = options?.name
32
- return factory
33
- }
34
-
35
- /** Dispatches a JSX element to h() (DOM tags), createComponent() (fusée components), or a direct call (helpers). */
36
- function createNode(type, rawProps) {
37
- const { children, ...rest } = rawProps || {}
38
-
39
- if (type === Fragment) {
40
- return Fragment({ children })
41
- }
42
-
43
- if (typeof type === 'string') {
44
- return h(type, normalizeEventProps(rest), flattenChildren(children))
45
- }
46
-
47
- if (typeof type === 'function' && type._isFuseeComponent) {
48
- const name = type._componentName || type.name || 'AnonymousComponent'
49
- const props = normalizeComponentListenerProps(rest)
50
- return createComponent(name, type, props, splitSlotChildren(children))
51
- }
52
-
53
- if (typeof type === 'function') {
54
- return type({ ...rest, children })
55
- }
56
-
57
- console.warn('[fusée] Unsupported JSX element type:', type)
58
- return null
59
- }
60
-
61
- /** Automatic JSX runtime entry point for a single (or statically known) child. */
62
- export function jsx(type, props, _key) {
63
- return createNode(type, props)
64
- }
65
-
66
- /** Automatic JSX runtime entry point when children are a static array. */
67
- export function jsxs(type, props, _key) {
68
- return createNode(type, props)
69
- }
70
-
71
- /** Dev-mode entry point; fusée doesn't use the extra source/self-closing args (yet). */
72
- export function jsxDEV(type, props, _key, _isStaticChildren, _source, _self) {
73
- return createNode(type, props)
1
+ // Point your bundler at this module and JSX compiles here with zero custom
2
+ // parser/Babel plugin needed yet:
3
+ // - esbuild/Vite: jsx: 'automatic', jsxImportSource: '<path-to-this-folder>'
4
+ // - tsconfig.json: "jsx": "react-jsx", "jsxImportSource": "<path-to-this-folder>"
5
+
6
+ import { h, createComponent } from './h.js'
7
+ import {
8
+ Fragment,
9
+ For,
10
+ Show,
11
+ Slot,
12
+ on,
13
+ flattenChildren,
14
+ normalizeEventProps,
15
+ normalizeComponentListenerProps,
16
+ splitSlotChildren
17
+ } from './jsx-helpers.js'
18
+ import { defineCompiledComponent as coreDefineComponent } from './component-core.js'
19
+
20
+ export { Fragment, For, Show, Slot, on }
21
+
22
+ /**
23
+ * Wraps defineComponent so JSX can tell "a fusée component" apart from a
24
+ * plain helper function (Fragment/For/Show/Slot, or your own stateless JSX
25
+ * helpers). Import defineComponent from here instead of component.js in
26
+ * any file that defines components with JSX.
27
+ */
28
+ export function defineComponent(options) {
29
+ const factory = coreDefineComponent(options)
30
+ factory._isFuseeComponent = true
31
+ factory._componentName = options?.name
32
+ return factory
33
+ }
34
+
35
+ /** Dispatches a JSX element to h() (DOM tags), createComponent() (fusée components), or a direct call (helpers). */
36
+ function createNode(type, rawProps) {
37
+ const { children, ...rest } = rawProps || {}
38
+
39
+ if (type === Fragment) {
40
+ return Fragment({ children })
41
+ }
42
+
43
+ if (typeof type === 'string') {
44
+ return h(type, normalizeEventProps(rest), flattenChildren(children))
45
+ }
46
+
47
+ if (typeof type === 'function' && type._isFuseeComponent) {
48
+ const name = type._componentName || type.name || 'AnonymousComponent'
49
+ const props = normalizeComponentListenerProps(rest)
50
+ return createComponent(name, type, props, splitSlotChildren(children))
51
+ }
52
+
53
+ if (typeof type === 'function') {
54
+ return type({ ...rest, children })
55
+ }
56
+
57
+ console.warn('[fusée] Unsupported JSX element type:', type)
58
+ return null
59
+ }
60
+
61
+ /** Automatic JSX runtime entry point for a single (or statically known) child. */
62
+ export function jsx(type, props, _key) {
63
+ return createNode(type, props)
64
+ }
65
+
66
+ /** Automatic JSX runtime entry point when children are a static array. */
67
+ export function jsxs(type, props, _key) {
68
+ return createNode(type, props)
69
+ }
70
+
71
+ /** Dev-mode entry point; fusée doesn't use the extra source/self-closing args (yet). */
72
+ export function jsxDEV(type, props, _key, _isStaticChildren, _source, _self) {
73
+ return createNode(type, props)
74
74
  }