fusee-framework 1.6.0 → 1.8.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 (129) hide show
  1. package/README.md +145 -123
  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 +50 -62
  8. package/app/main.ts +78 -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 +53 -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/async-work.js +65 -0
  17. package/app/pages/composables.js +158 -0
  18. package/app/pages/composables.ts +158 -0
  19. package/app/pages/dynamic.js +40 -0
  20. package/app/pages/dynamic.ts +40 -0
  21. package/app/pages/index.js +137 -0
  22. package/app/pages/index.ts +87 -0
  23. package/app/pages/login.ts +21 -0
  24. package/app/pages/objects/[id].js +56 -0
  25. package/app/pages/objects/[id].ts +56 -0
  26. package/app/pages/posts/[category]/[slug].js +56 -0
  27. package/app/pages/posts/[category]/[slug].ts +56 -0
  28. package/app/pages/router-example.js +79 -0
  29. package/app/pages/router-example.ts +56 -0
  30. package/app/pages/scroll-demo.ts +102 -0
  31. package/app/pages/test-for.js +65 -0
  32. package/app/pages/test-for.ts +80 -0
  33. package/app/pages/users/[id].js +24 -0
  34. package/app/pages/users/[id].ts +46 -0
  35. package/app/pages/users/index.js +22 -0
  36. package/app/pages/users/index.ts +29 -0
  37. package/app/pages/users.js +43 -0
  38. package/app/pages/users.ts +78 -0
  39. package/app/providers/ChildComponent.js +8 -2
  40. package/app/providers/ChildComponent.ts +47 -47
  41. package/app/providers/GrandchildComponent.js +19 -0
  42. package/app/providers/ParentComponent.js +5 -1
  43. package/app/routes/About.js +1 -1
  44. package/app/routes/About.ts +1 -1
  45. package/app/routes/AuthRoutes.ts +41 -0
  46. package/app/routes/Home.js +9 -0
  47. package/app/routes/NotFound.js +16 -0
  48. package/app/routes/NotFound.ts +16 -0
  49. package/app/routes/RouterExample.js +78 -0
  50. package/app/routes/RouterExample.ts +56 -0
  51. package/app/routes/TestResource.js +47 -0
  52. package/app/routes/WildcardDemo.js +35 -0
  53. package/app/routes/WildcardDemo.ts +35 -0
  54. package/app/services.js +15 -0
  55. package/auto-imports.d.ts +9 -0
  56. package/bin/create-fusee-linux +0 -0
  57. package/bin/create-fusee-mac-arm64 +0 -0
  58. package/bin/create-fusee-mac-intel +0 -0
  59. package/bin/create-fusee-win.exe +0 -0
  60. package/bin/create-fusee.exe +0 -0
  61. package/bin/index.js +38 -0
  62. package/create-fusee-go/cmd/generate.go +72 -0
  63. package/create-fusee-go/cmd/init.go +38 -0
  64. package/create-fusee-go/cmd/root.go +121 -0
  65. package/create-fusee-go/go.mod +10 -0
  66. package/create-fusee-go/go.sum +10 -0
  67. package/create-fusee-go/internal/assets/assets.go +78 -0
  68. package/create-fusee-go/internal/assets/embed/framework/auto-import-preset.js +9 -0
  69. package/create-fusee-go/internal/assets/embed/framework/core/compiler.js +253 -0
  70. package/create-fusee-go/internal/assets/embed/framework/core/component.js +266 -0
  71. package/create-fusee-go/internal/assets/embed/framework/core/composable.js +27 -0
  72. package/create-fusee-go/internal/assets/embed/framework/core/directives.js +875 -0
  73. package/create-fusee-go/internal/assets/embed/framework/core/evaluator.js +153 -0
  74. package/create-fusee-go/internal/assets/embed/framework/core/event-delegation.js +191 -0
  75. package/create-fusee-go/internal/assets/embed/framework/core/signal.js +412 -0
  76. package/create-fusee-go/internal/assets/embed/framework/core/store.js +81 -0
  77. package/create-fusee-go/internal/assets/embed/framework/index.d.ts +3 -0
  78. package/create-fusee-go/internal/assets/embed/framework/index.js +10 -0
  79. package/create-fusee-go/internal/assets/embed/framework/router/file-router.js +177 -0
  80. package/create-fusee-go/internal/assets/embed/framework/router/router.js +462 -0
  81. package/create-fusee-go/internal/assets/embed/framework/types/compiler.d.ts +15 -0
  82. package/create-fusee-go/internal/assets/embed/framework/types/component.d.ts +88 -0
  83. package/create-fusee-go/internal/assets/embed/framework/types/composable.d.ts +14 -0
  84. package/create-fusee-go/internal/assets/embed/framework/types/directives.d.ts +55 -0
  85. package/create-fusee-go/internal/assets/embed/framework/types/evaluator.d.ts +13 -0
  86. package/create-fusee-go/internal/assets/embed/framework/types/event-delegation.d.ts +32 -0
  87. package/create-fusee-go/internal/assets/embed/framework/types/index.d.ts +22 -0
  88. package/create-fusee-go/internal/assets/embed/framework/types/router.d.ts +32 -0
  89. package/create-fusee-go/internal/assets/embed/framework/types/signal.d.ts +79 -0
  90. package/create-fusee-go/internal/assets/embed/framework/types/store.d.ts +8 -0
  91. package/create-fusee-go/internal/assets/templates/Counter.tmpl +26 -0
  92. package/create-fusee-go/internal/assets/templates/component.tmpl +11 -0
  93. package/create-fusee-go/internal/assets/templates/components/Loading.tmpl +24 -0
  94. package/create-fusee-go/internal/assets/templates/index.html.tmpl +15 -0
  95. package/create-fusee-go/internal/assets/templates/main.tmpl +14 -0
  96. package/create-fusee-go/internal/assets/templates/package.json.tmpl +14 -0
  97. package/create-fusee-go/internal/assets/templates/page.tmpl +12 -0
  98. package/create-fusee-go/internal/assets/templates/pages/about.tmpl +20 -0
  99. package/create-fusee-go/internal/assets/templates/pages/index.tmpl +22 -0
  100. package/create-fusee-go/internal/assets/templates/pages/layout.tmpl +18 -0
  101. package/create-fusee-go/internal/assets/templates/tsconfig.json.tmpl +14 -0
  102. package/create-fusee-go/internal/assets/templates/vite.config.js.tmpl +18 -0
  103. package/create-fusee-go/main.go +7 -0
  104. package/framework/auto-import-preset.js +13 -9
  105. package/framework/core/compiler.js +252 -249
  106. package/framework/core/component.js +279 -260
  107. package/framework/core/di.js +187 -0
  108. package/framework/core/directives.js +819 -786
  109. package/framework/core/evaluator.js +153 -70
  110. package/framework/core/event-delegation.js +33 -14
  111. package/framework/core/reconcile.js +151 -0
  112. package/framework/core/signal.js +679 -403
  113. package/framework/core/store.js +322 -81
  114. package/framework/index.js +12 -11
  115. package/framework/router/file-router.js +177 -0
  116. package/framework/router/router.js +554 -80
  117. package/framework/types/component.d.ts +89 -88
  118. package/framework/types/di.d.ts +43 -0
  119. package/framework/types/event-delegation.d.ts +0 -58
  120. package/framework/types/index.d.ts +32 -22
  121. package/framework/types/reconcile.d.ts +7 -0
  122. package/framework/types/router.d.ts +42 -15
  123. package/framework/types/signal.d.ts +147 -78
  124. package/framework/types/store.d.ts +66 -8
  125. package/index.html +638 -626
  126. package/package.json +5 -3
  127. package/tsconfig.json +1 -0
  128. package/vite.config.js +7 -1
  129. /package/{framework → create-fusee-go/internal/assets/embed/framework}/bin/create-fusee.js +0 -0
@@ -1,261 +1,280 @@
1
- import { effect, setEffectHook, batch } from './signal.js'
2
- import { mountTemplate } from './compiler.js'
3
-
4
- let currentInstance = null
5
-
6
- setEffectHook(eff => {
7
- if (currentInstance) currentInstance._effects.push(eff)
8
- })
9
-
10
- export function getCurrentInstance() {
11
- return currentInstance
12
- }
13
-
14
- export function setCurrentInstance(instance) {
15
- currentInstance = instance
16
- }
17
-
18
- function resolveProps(schema, received) {
19
- const isArray = Array.isArray(schema)
20
- const resolved = {}
21
-
22
- const normalizedSchema = {}
23
- if (isArray) {
24
- schema.forEach(k => normalizedSchema[k.toLowerCase()] = k)
25
- } else {
26
- for (const key of Object.keys(schema)) {
27
- normalizedSchema[key.toLowerCase()] = key
28
- }
29
- }
30
-
31
- for (const receivedKey of Object.keys(received)) {
32
- const schemaKey = isArray
33
- ? schema.find(k => k.toLowerCase() === receivedKey.toLowerCase())
34
- : normalizedSchema[receivedKey.toLowerCase()]
35
-
36
- if (!schemaKey) {
37
- console.warn(`[framework] Unknown prop "${receivedKey}"`)
38
- continue
39
- }
40
-
41
- const descriptor = Object.getOwnPropertyDescriptor(received, receivedKey)
42
- if (descriptor && descriptor.get) {
43
- Object.defineProperty(resolved, schemaKey, {
44
- get: descriptor.get,
45
- enumerable: true,
46
- configurable: true
47
- })
48
- } else {
49
- resolved[schemaKey] = received[receivedKey]
50
- }
51
- }
52
-
53
- if (!isArray) {
54
- for (const [key, config] of Object.entries(schema)) {
55
- if (!(key in resolved)) {
56
- if (config.required) {
57
- console.error(`[framework] Required prop "${key}" is missing`)
58
- }
59
- if (config.default !== undefined) {
60
- resolved[key] = typeof config.default === 'function'
61
- ? config.default()
62
- : config.default
63
- }
64
- } else if (config.type) {
65
- const value = resolved[key]
66
- const expectedType = config.type.name
67
- const actualType = typeof value
68
- const typeMap = { String: 'string', Number: 'number', Boolean: 'boolean' }
69
- if (typeMap[expectedType] && actualType !== typeMap[expectedType]) {
70
- console.warn(`[framework] Prop "${key}" expected ${expectedType} but got ${actualType}`)
71
- }
72
- }
73
- }
74
- }
75
-
76
- return resolved
77
- }
78
-
79
- function createEmit(listeners) {
80
- return function emit(eventName, ...args) {
81
- const handler = listeners[eventName]
82
- if (typeof handler === 'function') {
83
- batch(() => handler(...args))
84
- } else if (handler !== undefined) {
85
- console.warn(`[framework] emit("${eventName}"): listener is not a function`)
86
- }
87
- }
88
- }
89
-
90
- export function parseSlots(slotHTML) {
91
- const slots = { default: '' }
92
- if (!slotHTML || !slotHTML.trim()) return slots
93
-
94
- const namedSlotRe = /<template\s+slot="([^"]+)"[^>]*>([\s\S]*?)<\/template>/gi
95
- let remaining = slotHTML
96
- let match
97
-
98
- while ((match = namedSlotRe.exec(slotHTML)) !== null) {
99
- slots[match[1]] = match[2].trim()
100
- remaining = remaining.replace(match[0], '')
101
- }
102
-
103
- const defaultContent = remaining.trim()
104
- if (defaultContent) slots.default = defaultContent
105
-
106
- return slots
107
- }
108
-
109
- function resolveSlots(template, slots) {
110
- let result = template.replace(/<slot\s+name="([^"]+)"\s*>[\s\S]*?<\/slot>/gi, (match, name) => {
111
- return slots[name] ?? ''
112
- })
113
- result = result.replace(/<slot\s*>[\s\S]*?<\/slot>/gi, () => {
114
- return slots.default ?? ''
115
- })
116
-
117
- result = result.replace(/<slot\s+name="([^"]+)"\s*\/>/gi, (_, name) => slots[name] ?? '')
118
- result = result.replace(/<slot\s*\/>/gi, () => slots.default ?? '')
119
- return result
120
- }
121
-
122
- export function onMount(fn) {
123
- if (currentInstance) currentInstance._mountHooks.push(fn)
124
- }
125
-
126
- export function onUnmount(fn) {
127
- if (currentInstance) currentInstance._unmountHooks.push(fn)
128
- }
129
-
130
- export function defineComponent(options) {
131
- return function ComponentFactory(props = {}, { listeners = {}, slots = {}, parent = null } = {}) {
132
- const instance = {
133
- props: options.props ? resolveProps(options.props, props) : props,
134
- _mountHooks: [],
135
- _unmountHooks: [],
136
- _effects: [],
137
- _element: null,
138
- _provides: {},
139
- _parent: parent,
140
- }
141
-
142
- const emit = createEmit(listeners)
143
-
144
- currentInstance = instance
145
- const result = options.setup(instance.props, { emit, slots })
146
- instance.state = result
147
- result._instance = instance
148
- currentInstance = null
149
-
150
- function render(container) {
151
- instance._element = container
152
-
153
- const resolvedTemplate = resolveSlots(result.template, slots)
154
-
155
- const { effects } = mountTemplate(
156
- resolvedTemplate,
157
- container,
158
- result,
159
- options.components || {}
160
- )
161
-
162
- instance._effects.push(...effects)
163
- for (const hook of instance._mountHooks) hook()
164
-
165
- return instance
166
- }
167
-
168
- function unmount() {
169
- for (const hook of instance._unmountHooks) hook()
170
- for (const e of instance._effects) {
171
- if (typeof e === 'function') e()
172
- }
173
- if (instance._element) instance._element.innerHTML = ''
174
- }
175
-
176
- return { render, unmount, instance }
177
- }
178
- }
179
-
180
- export function provide(key, value) {
181
- if (!currentInstance) return
182
- currentInstance._provides[key] = value
183
- }
184
-
185
- export function inject(key) {
186
- let parent = currentInstance?._parent
187
- while (parent) {
188
- if (key in parent._provides) return parent._provides[key];
189
- parent = parent._parent;
190
- }
191
- return null;
192
- }
193
-
194
- export function defineAsyncComponent(loaderOrOptions) {
195
- const options = typeof loaderOrOptions === 'function'
196
- ? { loader: loaderOrOptions }
197
- : loaderOrOptions
198
-
199
- return function AsyncComponentFactory(props = {}, { listeners = {}, slots = {}, parent = null } = {}) {
200
- const instance = {
201
- props,
202
- _mountHooks: [],
203
- _unmountHooks: [],
204
- _effects: [],
205
- _element: null,
206
- _provides: {},
207
- _parent: parent,
208
- }
209
-
210
- let childApi = null
211
- let loadingApi = null
212
- let isUnmounted = false
213
-
214
- function render(container) {
215
- instance._element = container
216
-
217
- if (options.loadingComponent) {
218
- loadingApi = options.loadingComponent({}, { parent })
219
- loadingApi.render(container)
220
- } else {
221
- container.innerHTML = '<!-- async component boundary -->'
222
- }
223
-
224
- Promise.resolve(options.loader())
225
- .then(comp => {
226
- if (isUnmounted) return
227
- let ComponentFn = comp.default || comp
228
-
229
- if (typeof ComponentFn === 'object' && ComponentFn !== null) {
230
- ComponentFn = Object.values(ComponentFn).find(v => typeof v === 'function') || ComponentFn
231
- }
232
-
233
- if (loadingApi) {
234
- loadingApi.unmount()
235
- loadingApi = null
236
- }
237
-
238
- childApi = ComponentFn(props, { listeners, slots, parent })
239
-
240
- container.innerHTML = ''
241
- childApi.render(container)
242
- })
243
- .catch(err => {
244
- console.error('[framework] Failed to load async component:', err)
245
- })
246
-
247
- return instance
248
- }
249
-
250
- function unmount() {
251
- isUnmounted = true
252
- if (loadingApi) loadingApi.unmount()
253
- if (childApi) childApi.unmount()
254
- if (instance._element && !childApi && !loadingApi) {
255
- instance._element.innerHTML = ''
256
- }
257
- }
258
-
259
- return { render, unmount, instance }
260
- }
1
+ import { effect, setEffectHook, batch } from './signal.js'
2
+ import { mountTemplate } from './compiler.js'
3
+ import { rootInjector, runInContext, replaceActiveInjector, EnvironmentInjector } from './di.js'
4
+ export { inject } from './di.js'
5
+
6
+ let currentInstance = null
7
+
8
+ setEffectHook(eff => {
9
+ if (currentInstance) currentInstance._effects.push(eff)
10
+ })
11
+
12
+ export function getCurrentInstance() {
13
+ return currentInstance
14
+ }
15
+
16
+ export function setCurrentInstance(instance) {
17
+ currentInstance = instance
18
+ }
19
+
20
+ function resolveProps(schema, received) {
21
+ const isArray = Array.isArray(schema)
22
+ const resolved = {}
23
+
24
+ const normalizedSchema = {}
25
+ if (isArray) {
26
+ schema.forEach(k => normalizedSchema[k.toLowerCase()] = k)
27
+ } else {
28
+ for (const key of Object.keys(schema)) {
29
+ normalizedSchema[key.toLowerCase()] = key
30
+ }
31
+ }
32
+
33
+ for (const receivedKey of Object.keys(received)) {
34
+ const schemaKey = isArray
35
+ ? schema.find(k => k.toLowerCase() === receivedKey.toLowerCase())
36
+ : normalizedSchema[receivedKey.toLowerCase()]
37
+
38
+ if (!schemaKey) {
39
+ console.warn(`[framework] Unknown prop "${receivedKey}"`)
40
+ continue
41
+ }
42
+
43
+ const descriptor = Object.getOwnPropertyDescriptor(received, receivedKey)
44
+ if (descriptor && descriptor.get) {
45
+ Object.defineProperty(resolved, schemaKey, {
46
+ get: descriptor.get,
47
+ enumerable: true,
48
+ configurable: true
49
+ })
50
+ } else {
51
+ resolved[schemaKey] = received[receivedKey]
52
+ }
53
+ }
54
+
55
+ if (!isArray) {
56
+ for (const [key, config] of Object.entries(schema)) {
57
+ if (!(key in resolved)) {
58
+ if (config.required) {
59
+ console.error(`[framework] Required prop "${key}" is missing`)
60
+ }
61
+ if (config.default !== undefined) {
62
+ resolved[key] = typeof config.default === 'function'
63
+ ? config.default()
64
+ : config.default
65
+ }
66
+ } else if (config.type) {
67
+ const value = resolved[key]
68
+ const expectedType = config.type.name
69
+ const actualType = typeof value
70
+ const typeMap = { String: 'string', Number: 'number', Boolean: 'boolean' }
71
+ if (typeMap[expectedType] && actualType !== typeMap[expectedType]) {
72
+ console.warn(`[framework] Prop "${key}" expected ${expectedType} but got ${actualType}`)
73
+ }
74
+ }
75
+ }
76
+ }
77
+
78
+ return resolved
79
+ }
80
+
81
+ function createEmit(listeners) {
82
+ return function emit(eventName, ...args) {
83
+ const handler = listeners[eventName]
84
+ if (typeof handler === 'function') {
85
+ batch(() => handler(...args))
86
+ } else if (handler !== undefined) {
87
+ console.warn(`[framework] emit("${eventName}"): listener is not a function`)
88
+ }
89
+ }
90
+ }
91
+
92
+ export function parseSlots(slotHTML) {
93
+ const slots = { default: '' }
94
+ if (!slotHTML || !slotHTML.trim()) return slots
95
+
96
+ const namedSlotRe = /<template\s+slot="([^"]+)"[^>]*>([\s\S]*?)<\/template>/gi
97
+ let remaining = slotHTML
98
+ let match
99
+
100
+ while ((match = namedSlotRe.exec(slotHTML)) !== null) {
101
+ slots[match[1]] = match[2].trim()
102
+ remaining = remaining.replace(match[0], '')
103
+ }
104
+
105
+ const defaultContent = remaining.trim()
106
+ if (defaultContent) slots.default = defaultContent
107
+
108
+ return slots
109
+ }
110
+
111
+ function resolveSlots(template, slots) {
112
+ let result = template.replace(/<slot\s+name="([^"]+)"\s*>[\s\S]*?<\/slot>/gi, (match, name) => {
113
+ return slots[name] ?? ''
114
+ })
115
+ result = result.replace(/<slot\s*>[\s\S]*?<\/slot>/gi, () => {
116
+ return slots.default ?? ''
117
+ })
118
+
119
+ result = result.replace(/<slot\s+name="([^"]+)"\s*\/>/gi, (_, name) => slots[name] ?? '')
120
+ result = result.replace(/<slot\s*\/>/gi, () => slots.default ?? '')
121
+ return result
122
+ }
123
+
124
+ export function onMount(fn) {
125
+ if (currentInstance) currentInstance._mountHooks.push(fn)
126
+ }
127
+
128
+ export function onUnmount(fn) {
129
+ if (currentInstance) currentInstance._unmountHooks.push(fn)
130
+ }
131
+
132
+ export function defineComponent(options) {
133
+ return function ComponentFactory(props = {}, { listeners = {}, slots = {}, parent = null } = {}) {
134
+ const parentInjector = (parent && parent._injector) || rootInjector;
135
+
136
+ const instance = {
137
+ props: options.props ? resolveProps(options.props, props) : props,
138
+ _mountHooks: [],
139
+ _unmountHooks: [],
140
+ _effects: [],
141
+ _element: null,
142
+ _parent: parent,
143
+ _injector: parentInjector,
144
+ _ownsInjector: false
145
+ }
146
+
147
+ const emit = createEmit(listeners)
148
+
149
+ currentInstance = instance
150
+ const result = runInContext(instance._injector, () => {
151
+ return options.setup(instance.props, { emit, slots });
152
+ });
153
+ instance.state = result
154
+ result._instance = instance
155
+ currentInstance = null
156
+
157
+ function render(container) {
158
+ instance._element = container
159
+
160
+ const resolvedTemplate = resolveSlots(result.template, slots)
161
+
162
+ const { effects } = mountTemplate(
163
+ resolvedTemplate,
164
+ container,
165
+ result,
166
+ options.components || {}
167
+ )
168
+
169
+ instance._effects.push(...effects)
170
+ for (const hook of instance._mountHooks) hook()
171
+
172
+ return instance
173
+ }
174
+
175
+ function unmount() {
176
+ for (const hook of instance._unmountHooks) hook()
177
+ const effects = instance._effects
178
+ for (let i = effects.length - 1; i >= 0; i--) {
179
+ const cleanup = effects[i]
180
+ if (typeof cleanup === 'function') cleanup()
181
+ }
182
+ instance._effects = []
183
+ if (instance._element) {
184
+ instance._element.innerHTML = ''
185
+ }
186
+ }
187
+
188
+ return { render, unmount, instance }
189
+ }
190
+ }
191
+
192
+ export function provide(key, value) {
193
+ if (!currentInstance) return;
194
+
195
+ if (!currentInstance._ownsInjector) {
196
+ currentInstance._injector = new EnvironmentInjector([], currentInstance._injector);
197
+ currentInstance._ownsInjector = true;
198
+ replaceActiveInjector(currentInstance._injector);
199
+ }
200
+
201
+ if (value === undefined && key && key.provide) {
202
+ currentInstance._injector.provide(key);
203
+ } else if (value && typeof value === 'object' && (value.useClass || value.useFactory || value.useExisting)) {
204
+ currentInstance._injector.provide({ provide: key, ...value });
205
+ } else {
206
+ currentInstance._injector.provide({ provide: key, useValue: value });
207
+ }
208
+ }
209
+
210
+ export function defineAsyncComponent(loaderOrOptions) {
211
+ const options = typeof loaderOrOptions === 'function'
212
+ ? { loader: loaderOrOptions }
213
+ : loaderOrOptions
214
+
215
+ return function AsyncComponentFactory(props = {}, { listeners = {}, slots = {}, parent = null } = {}) {
216
+ const parentInjector = (parent && parent._injector) || rootInjector;
217
+
218
+ const instance = {
219
+ props,
220
+ _mountHooks: [],
221
+ _unmountHooks: [],
222
+ _effects: [],
223
+ _element: null,
224
+ _parent: parent,
225
+ _injector: parentInjector,
226
+ _ownsInjector: false
227
+ }
228
+
229
+ let childApi = null
230
+ let loadingApi = null
231
+ let isUnmounted = false
232
+
233
+ function render(container) {
234
+ instance._element = container
235
+
236
+ if (options.loadingComponent) {
237
+ loadingApi = options.loadingComponent({}, { parent })
238
+ loadingApi.render(container)
239
+ } else {
240
+ container.innerHTML = '<!-- async component boundary -->'
241
+ }
242
+
243
+ Promise.resolve(options.loader())
244
+ .then(comp => {
245
+ if (isUnmounted) return
246
+ let ComponentFn = comp.default || comp
247
+
248
+ if (typeof ComponentFn === 'object' && ComponentFn !== null) {
249
+ ComponentFn = Object.values(ComponentFn).find(v => typeof v === 'function') || ComponentFn
250
+ }
251
+
252
+ if (loadingApi) {
253
+ loadingApi.unmount()
254
+ loadingApi = null
255
+ }
256
+
257
+ childApi = ComponentFn(props, { listeners, slots, parent })
258
+
259
+ container.innerHTML = ''
260
+ childApi.render(container)
261
+ })
262
+ .catch(err => {
263
+ console.error('[framework] Failed to load async component:', err)
264
+ })
265
+
266
+ return instance
267
+ }
268
+
269
+ function unmount() {
270
+ isUnmounted = true
271
+ if (loadingApi) loadingApi.unmount()
272
+ if (childApi) childApi.unmount()
273
+ if (instance._element && !childApi && !loadingApi) {
274
+ instance._element.innerHTML = ''
275
+ }
276
+ }
277
+
278
+ return { render, unmount, instance }
279
+ }
261
280
  }