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,440 +1,11 @@
1
- import { effect, setEffectHook, batch } from './signal.js'
2
- import { mountTemplate } from './compiler.js'
3
- import { NATIVE_SLOTS } from './h.js'
4
- import { attachContextResponder, resolveProvided, requestContext, findInstanceHost } from './context.js'
5
-
6
- // Slots context handed to compiled templates so hSlot() emits real <slot> outlets.
7
- const NATIVE_SLOT_OUTLETS = Object.freeze({ [NATIVE_SLOTS]: true })
8
- import { inject as diInject, rootInjector, runInContext, replaceActiveInjector, EnvironmentInjector } from './di.js'
9
-
10
- let currentInstance = null
11
-
12
- setEffectHook(eff => {
13
- if (currentInstance) currentInstance._effects.push(eff)
14
- })
15
-
16
- /** Returns the component instance currently running setup or a lifecycle hook. */
17
- export function getCurrentInstance() {
18
- return currentInstance
19
- }
20
-
21
- /** Sets the active component instance for framework internals and composables. */
22
- export function setCurrentInstance(instance) {
23
- currentInstance = instance
24
- }
25
-
26
- // Global accessor hook for di.js execution context guard
27
- globalThis.__FUSEE_GET_CURRENT_INSTANCE__ = getCurrentInstance
28
-
29
- /**
30
- * Resolves received props against a declared schema, validating types,
31
- * filling in default values, and checking for missing required props.
32
- */
33
- function resolveProps(schema, received) {
34
- const isArray = Array.isArray(schema)
35
- const resolved = {}
36
-
37
- const normalizedSchema = {}
38
- if (isArray) {
39
- schema.forEach(k => normalizedSchema[k.toLowerCase()] = k)
40
- } else {
41
- for (const key of Object.keys(schema)) {
42
- normalizedSchema[key.toLowerCase()] = key
43
- }
44
- }
45
-
46
- for (const receivedKey of Object.keys(received)) {
47
- const schemaKey = isArray
48
- ? schema.find(k => k.toLowerCase() === receivedKey.toLowerCase())
49
- : normalizedSchema[receivedKey.toLowerCase()]
50
-
51
- if (!schemaKey) {
52
- console.warn(`[framework] Unknown prop "${receivedKey}"`)
53
- continue
54
- }
55
-
56
- const descriptor = Object.getOwnPropertyDescriptor(received, receivedKey)
57
- if (descriptor && descriptor.get) {
58
- Object.defineProperty(resolved, schemaKey, {
59
- get: descriptor.get,
60
- enumerable: true,
61
- configurable: true
62
- })
63
- } else {
64
- resolved[schemaKey] = received[receivedKey]
65
- }
66
- }
67
-
68
- if (!isArray) {
69
- for (const [key, config] of Object.entries(schema)) {
70
- if (!(key in resolved)) {
71
- if (config.required) {
72
- console.error(`[framework] Required prop "${key}" is missing`)
73
- }
74
- if (config.default !== undefined) {
75
- resolved[key] = typeof config.default === 'function'
76
- ? config.default()
77
- : config.default
78
- }
79
- } else if (config.type) {
80
- const value = resolved[key]
81
- const expectedType = config.type.name
82
- const actualType = typeof value
83
- const typeMap = { String: 'string', Number: 'number', Boolean: 'boolean' }
84
- if (typeMap[expectedType] && actualType !== typeMap[expectedType]) {
85
- console.warn(`[framework] Prop "${key}" expected ${expectedType} but got ${actualType}`)
86
- }
87
- }
88
- }
89
- }
90
-
91
- return resolved
92
- }
93
-
94
- /** Creates a batched event emitter that safely dispatches payloads to component listeners. */
95
- function createEmit(listeners) {
96
- return function emit(eventName, ...args) {
97
- const handler = listeners[eventName]
98
- if (typeof handler === 'function') {
99
- batch(() => handler(...args))
100
- } else if (handler !== undefined) {
101
- console.warn(`[framework] emit("${eventName}"): listener is not a function`)
102
- }
103
- }
104
- }
105
-
106
- /** Parses named template slots and returns their content grouped by slot name. */
107
- export function parseSlots(slotHTML) {
108
- const slots = { default: '' }
109
- if (!slotHTML || !slotHTML.trim()) return slots
110
-
111
- const namedSlotRe = /<template\s+slot="([^"]+)"[^>]*>([\s\S]*?)<\/template>/gi
112
- let remaining = slotHTML
113
- let match
114
-
115
- while ((match = namedSlotRe.exec(slotHTML)) !== null) {
116
- slots[match[1]] = match[2].trim()
117
- remaining = remaining.replace(match[0], '')
118
- }
119
-
120
- const defaultContent = remaining.trim()
121
- if (defaultContent) slots.default = defaultContent
122
-
123
- return slots
124
- }
125
-
126
- /** Replaces slot outlets in a component template with supplied slot content. */
127
- function resolveSlots(template, slots) {
128
- let result = template.replace(/<slot\s+name="([^"]+)"\s*>[\s\S]*?<\/slot>/gi, (match, name) => {
129
- return slots[name] ?? ''
130
- })
131
- result = result.replace(/<slot\s*>[\s\S]*?<\/slot>/gi, () => {
132
- return slots.default ?? ''
133
- })
134
-
135
- result = result.replace(/<slot\s+name="([^"]+)"\s*\/>/gi, (_, name) => slots[name] ?? '')
136
- result = result.replace(/<slot\s*\/>/gi, () => slots.default ?? '')
137
- return result
138
- }
139
-
140
- /** Registers a callback to run after the component has rendered. */
141
- export function onMount(fn) {
142
- if (currentInstance) currentInstance._mountHooks.push(fn)
143
- }
144
-
145
- /** Registers a callback to run when the component is unmounted. */
146
- export function onUnmount(fn) {
147
- if (currentInstance) currentInstance._unmountHooks.push(fn)
148
- }
149
-
150
- /** Creates a component factory with setup, rendering, props, slots, and lifecycle state. */
151
- export function defineComponent(options) {
152
- return Object.assign(function ComponentFactory(props = {}, { listeners = {}, slots = {}, parent = null } = {}) {
153
- const effectiveParent = parent || currentInstance
154
-
155
- const parentInjector = (effectiveParent && effectiveParent._injector) || rootInjector
156
- const parentProvides = effectiveParent ? (effectiveParent.provides || effectiveParent._provides || null) : null
157
- const parentApp = effectiveParent ? (effectiveParent._app || (effectiveParent._provides ? effectiveParent : null)) : (options._app || null)
158
- const parentComponents = effectiveParent ? (effectiveParent._components || effectiveParent.components || null) : (parentApp ? (parentApp._components || parentApp.components || null) : {})
159
-
160
- const rawMergedComponents = {
161
- ...(parentComponents || {}),
162
- ...(options.components || {})
163
- }
164
-
165
- const mergedComponents = new Proxy(rawMergedComponents, {
166
- get(target, prop, receiver) {
167
- if (typeof prop !== 'string') return Reflect.get(target, prop, receiver)
168
- if (prop in target) return target[prop]
169
- const lower = prop.toLowerCase()
170
- for (const key of Object.keys(target)) {
171
- if (key.toLowerCase() === lower) {
172
- return target[key]
173
- }
174
- }
175
- return undefined
176
- },
177
- has(target, prop) {
178
- if (typeof prop !== 'string') return Reflect.has(target, prop)
179
- if (prop in target) return true
180
- const lower = prop.toLowerCase()
181
- for (const key of Object.keys(target)) {
182
- if (key.toLowerCase() === lower) return true
183
- }
184
- return false
185
- }
186
- })
187
-
188
- const instance = {
189
- props: options.props ? resolveProps(options.props, props) : props,
190
- _mountHooks: [],
191
- _unmountHooks: [],
192
- _effects: [],
193
- _element: null,
194
- _parent: effectiveParent,
195
- _app: parentApp,
196
- _components: mergedComponents,
197
- provides: parentProvides ? Object.create(parentProvides) : Object.create(null),
198
- _injector: parentInjector,
199
- _ownsInjector: false,
200
- _host: options._host || null,
201
- _detachContext: null
202
- }
203
-
204
- const emit = createEmit(listeners)
205
-
206
- const previousInstance = currentInstance
207
- currentInstance = instance
208
- let result
209
- try {
210
- result = runInContext(instance._injector, () => {
211
- return options.setup ? options.setup(instance.props, { emit, slots }) : {}
212
- })
213
- instance.state = result
214
- if (result && typeof result === 'object') {
215
- result._instance = instance
216
- // _nativeSlots: rendered into a shadow root, where the browser distributes slots.
217
- if (options._nativeSlots) result._slots = NATIVE_SLOT_OUTLETS
218
- }
219
- } finally {
220
- currentInstance = previousInstance
221
- }
222
-
223
- function render(container) {
224
- const prevInstance = currentInstance
225
- currentInstance = instance
226
- try {
227
- instance._element = container
228
- // Before mounting the template, so custom elements connecting during it can find this scope.
229
- if (!instance._detachContext) instance._detachContext = attachContextResponder(container, instance)
230
-
231
- if (options.render) {
232
- const nodes = options.render(result, mergedComponents)
233
- container.innerHTML = ''
234
-
235
- function mountNode(fnode) {
236
- if (!fnode || !fnode.node) return
237
- container.appendChild(fnode.node)
238
-
239
- if (fnode.effects) {
240
- instance._effects.push(...fnode.effects)
241
- }
242
-
243
- if (fnode.children) {
244
- for (const child of fnode.children) {
245
- mountNode(child)
246
- }
247
- }
248
- }
249
-
250
- for (const node of nodes) {
251
- mountNode(node)
252
- }
253
- } else {
254
- const template = result?.template || ''
255
- const resolvedTemplate = options._nativeSlots ? template : resolveSlots(template, slots)
256
- const { effects } = mountTemplate(
257
- resolvedTemplate,
258
- container,
259
- result,
260
- mergedComponents
261
- )
262
- instance._effects.push(...effects)
263
- }
264
-
265
- for (const hook of instance._mountHooks)
266
- hook()
267
-
268
- return instance
269
- } finally {
270
- currentInstance = prevInstance
271
- }
272
- }
273
-
274
- function unmount() {
275
- for (const hook of instance._unmountHooks) hook()
276
- const effects = instance._effects
277
- for (let i = effects.length - 1; i >= 0; i--) {
278
- const cleanup = effects[i]
279
- if (typeof cleanup === 'function') cleanup()
280
- }
281
- instance._effects = []
282
- if (instance._detachContext) {
283
- instance._detachContext()
284
- instance._detachContext = null
285
- }
286
- if (instance._element) {
287
- instance._element.innerHTML = ''
288
- }
289
- }
290
-
291
- return { render, unmount, instance }
292
- }, { _options: options })
293
- }
294
-
295
- /** Provides a value or dependency injection configuration down to descendant components. */
296
- export function provide(key, value) {
297
- if (!currentInstance) return
298
-
299
- const provideKey = (key && typeof key === 'object' && key.provide) ? key.provide : key
300
- currentInstance.provides[provideKey] = value
301
-
302
- if (!currentInstance._ownsInjector) {
303
- currentInstance._injector = new EnvironmentInjector([], currentInstance._injector)
304
- currentInstance._ownsInjector = true
305
- replaceActiveInjector(currentInstance._injector)
306
- }
307
-
308
- if (value === undefined && key && key.provide) {
309
- currentInstance._injector.provide(key)
310
- } else if (value && typeof value === 'object' && (value.useClass || value.useFactory || value.useExisting)) {
311
- currentInstance._injector.provide({ provide: key, ...value })
312
- } else {
313
- currentInstance._injector.provide({ provide: key, useValue: value })
314
- }
315
- }
316
-
317
- /** Defines an asynchronous component wrapper that handles lazy loading, placeholders, and error states. */
318
- export function defineAsyncComponent(loaderOrOptions) {
319
- const options = typeof loaderOrOptions === 'function'
320
- ? { loader: loaderOrOptions }
321
- : loaderOrOptions
322
-
323
- return function AsyncComponentFactory(props = {}, { listeners = {}, slots = {}, parent = null } = {}) {
324
- const effectiveParent = parent || currentInstance
325
- const parentInjector = (effectiveParent && effectiveParent._injector) || rootInjector
326
- const parentProvides = effectiveParent
327
- ? (effectiveParent.provides || effectiveParent._provides || null)
328
- : null
329
- const parentApp = effectiveParent ? effectiveParent._app : null
330
- const parentComponents = effectiveParent ? effectiveParent._components : (parentApp ? parentApp._components : {})
331
-
332
- const instance = {
333
- props,
334
- _mountHooks: [],
335
- _unmountHooks: [],
336
- _effects: [],
337
- _element: null,
338
- _parent: effectiveParent,
339
- _app: parentApp,
340
- _components: parentComponents,
341
- provides: parentProvides ? Object.create(parentProvides) : Object.create(null),
342
- _injector: parentInjector,
343
- _ownsInjector: false
344
- }
345
-
346
- let childApi = null
347
- let loadingApi = null
348
- let isUnmounted = false
349
-
350
- function render(container) {
351
- const prevInstance = currentInstance
352
- currentInstance = instance
353
- try {
354
- instance._element = container
355
-
356
- if (options.loadingComponent) {
357
- loadingApi = options.loadingComponent({}, { parent: instance })
358
- loadingApi.render(container)
359
- } else {
360
- container.innerHTML = '<!-- async component boundary -->'
361
- }
362
-
363
- Promise.resolve(options.loader())
364
- .then(comp => {
365
- if (isUnmounted) return
366
- let ComponentFn = comp.default || comp
367
-
368
- if (typeof ComponentFn === 'object' && ComponentFn !== null) {
369
- ComponentFn = Object.values(ComponentFn).find(v => typeof v === 'function') || ComponentFn
370
- }
371
-
372
- if (loadingApi) {
373
- loadingApi.unmount()
374
- loadingApi = null
375
- }
376
-
377
- childApi = ComponentFn(props, { listeners, slots, parent: instance })
378
-
379
- container.innerHTML = ''
380
- childApi.render(container)
381
- })
382
- .catch(err => {
383
- console.error('[framework] Failed to load async component:', err)
384
- })
385
-
386
- return instance
387
- } finally {
388
- currentInstance = prevInstance
389
- }
390
- }
391
-
392
- function unmount() {
393
- isUnmounted = true
394
- if (loadingApi) loadingApi.unmount()
395
- if (childApi) childApi.unmount()
396
- if (instance._element && !childApi && !loadingApi) {
397
- instance._element.innerHTML = ''
398
- }
399
- }
400
-
401
- return { render, unmount, instance }
402
- }
403
- }
404
-
405
- /** Resolves and injects a provided value or dependency from the current component's provider tree or global injector. */
406
- export function inject(key, defaultValue, options) {
407
- const instance = getCurrentInstance()
408
-
409
- let actualOptions = undefined
410
- let actualDefaultValue = undefined
411
-
412
- if (defaultValue && typeof defaultValue === 'object' &&
413
- ('optional' in defaultValue || 'skipSelf' in defaultValue || 'self' in defaultValue)) {
414
- actualOptions = defaultValue
415
- } else {
416
- actualDefaultValue = defaultValue
417
- if (options && typeof options === 'object') {
418
- actualOptions = options
419
- }
420
- }
421
-
422
- const provided = resolveProvided(instance, key, actualOptions)
423
- if (provided.found) return provided.value
424
-
425
- // Inside a custom element, providers outside fusée (e.g. @lit/context) answer next.
426
- const host = actualOptions?.self ? null : findInstanceHost(instance)
427
- if (host) {
428
- const requested = requestContext(host, key)
429
- if (requested.found) return requested.value
430
- }
431
-
432
- try {
433
- return diInject(key, actualOptions !== undefined ? actualOptions : actualDefaultValue)
434
- } catch (err) {
435
- if (actualDefaultValue !== undefined) {
436
- return actualDefaultValue
437
- }
438
- throw err
439
- }
440
- }
1
+ // Components with string templates: defineComponent() brings the runtime template compiler.
2
+ // Everything else lives in component-core.js, which compiled-only code can import alone.
3
+ import { mountTemplate } from './compiler.js'
4
+ import { createComponentDefinition } from './component-core.js'
5
+
6
+ export * from './component-core.js'
7
+
8
+ /** Creates a component factory with setup, rendering (string template or compiled render), props, slots, and lifecycle state. */
9
+ export function defineComponent(options) {
10
+ return createComponentDefinition(options, mountTemplate)
11
+ }
@@ -1,29 +1,29 @@
1
- import { getCurrentInstance } from './component.js'
2
-
3
- /** Marks a function as a composable while preserving its name and call shape. */
4
- export function defineComposable(fn) {
5
- const composable = function (...args) {
6
- return fn(...args)
7
- }
8
-
9
- composable._isComposable = true
10
-
11
- Object.defineProperty(composable, 'name', {
12
- value: fn.name || 'composable',
13
- configurable: true
14
- })
15
-
16
- return composable
17
- }
18
-
19
- /** Warns when a composable runs outside component setup and returns the instance. */
20
- export function assertSetupContext(name) {
21
- const instance = getCurrentInstance()
22
- if (!instance) {
23
- console.warn(
24
- `[framework] ${name ? name + ' ' : ''}composable was called outside of setup(). ` +
25
- `Lifecycle hooks (onMount, onUnmount) and injections will not work.`
26
- )
27
- }
28
- return instance
29
- }
1
+ import { getCurrentInstance } from './component-core.js'
2
+
3
+ /** Marks a function as a composable while preserving its name and call shape. */
4
+ export function defineComposable(fn) {
5
+ const composable = function (...args) {
6
+ return fn(...args)
7
+ }
8
+
9
+ composable._isComposable = true
10
+
11
+ Object.defineProperty(composable, 'name', {
12
+ value: fn.name || 'composable',
13
+ configurable: true
14
+ })
15
+
16
+ return composable
17
+ }
18
+
19
+ /** Warns when a composable runs outside component setup and returns the instance. */
20
+ export function assertSetupContext(name) {
21
+ const instance = getCurrentInstance()
22
+ if (!instance) {
23
+ console.warn(
24
+ `[framework] ${name ? name + ' ' : ''}composable was called outside of setup(). ` +
25
+ `Lifecycle hooks (onMount, onUnmount) and injections will not work.`
26
+ )
27
+ }
28
+ return instance
29
+ }