fusee-framework 1.7.0 → 1.9.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 (77) hide show
  1. package/README.md +206 -139
  2. package/app/benchmark/benchmark.js +2 -2
  3. package/app/benchmark/vite.config.js +23 -0
  4. package/app/main.js +24 -3
  5. package/app/main.ts +7 -1
  6. package/app/pages/_layout.js +27 -0
  7. package/app/pages/async-work.js +65 -0
  8. package/app/pages/index.js +25 -5
  9. package/app/pages/index.ts +9 -0
  10. package/app/pages/router-example.js +24 -1
  11. package/app/pages/scroll-demo.ts +102 -0
  12. package/app/providers/ChildComponent.js +8 -2
  13. package/app/providers/ChildComponent.ts +47 -47
  14. package/app/providers/GrandchildComponent.js +19 -0
  15. package/app/providers/ParentComponent.js +5 -1
  16. package/app/routes/RouterExample.js +23 -1
  17. package/app/routes/TestResource.js +47 -0
  18. package/app/services.js +15 -0
  19. package/auto-imports.d.ts +12 -0
  20. package/framework/auto-import-preset.js +15 -9
  21. package/framework/core/async-context.js +27 -0
  22. package/framework/core/compiler/ast.js +167 -0
  23. package/framework/core/compiler/errors-list.js +47 -0
  24. package/framework/core/compiler/errors.js +107 -0
  25. package/framework/core/compiler/generator.js +363 -0
  26. package/framework/core/compiler/lexer.js +347 -0
  27. package/framework/core/compiler/main-compiler.js +115 -0
  28. package/framework/core/compiler/parser.js +414 -0
  29. package/framework/core/compiler/plugins/actions-plugin.js +263 -0
  30. package/framework/core/compiler/plugins/compiler-plugin.d.ts +5 -0
  31. package/framework/core/compiler/plugins/compiler-plugin.js +45 -0
  32. package/framework/core/compiler/plugins/file-router-plugin.js +301 -0
  33. package/framework/core/compiler/plugins/router-plugin.js +196 -0
  34. package/framework/core/compiler/transformer.js +398 -0
  35. package/framework/core/component.js +61 -23
  36. package/framework/core/di.js +187 -0
  37. package/framework/core/directives.js +40 -96
  38. package/framework/core/evaluator.js +1 -1
  39. package/framework/core/h.js +430 -0
  40. package/framework/core/hydration.js +83 -0
  41. package/framework/core/reconcile.js +151 -0
  42. package/framework/core/signal.js +269 -0
  43. package/framework/core/store.js +322 -81
  44. package/framework/index.js +5 -3
  45. package/framework/router/file-router.js +1 -1
  46. package/framework/router/router.js +113 -21
  47. package/framework/server/actions.js +173 -0
  48. package/framework/server/actions.server.js +88 -0
  49. package/framework/server/index.js +18 -0
  50. package/framework/types/actions.d.ts +50 -0
  51. package/framework/types/actions.server.d.ts +36 -0
  52. package/framework/types/compiler/ast.d.ts +166 -0
  53. package/framework/types/compiler/errors-codes.d.ts +23 -0
  54. package/framework/types/compiler/errors.d.ts +43 -0
  55. package/framework/types/compiler/generator.d.ts +8 -0
  56. package/framework/types/compiler/h.d.ts +46 -0
  57. package/framework/types/compiler/lexer.d.ts +27 -0
  58. package/framework/types/compiler/main-compiler.d.ts +60 -0
  59. package/framework/types/compiler/parser.d.ts +4 -0
  60. package/framework/types/compiler/plugins/actions.d.ts +30 -0
  61. package/framework/types/compiler/plugins/compiler-plugin.d.ts +5 -0
  62. package/framework/types/compiler/plugins/file-router-plugins.d.ts +32 -0
  63. package/framework/types/compiler/plugins/router-plugin.d.ts +24 -0
  64. package/framework/types/compiler/transformer.d.ts +28 -0
  65. package/framework/types/component.d.ts +89 -88
  66. package/framework/types/di.d.ts +43 -0
  67. package/framework/types/hydration.d.ts +30 -0
  68. package/framework/types/index.d.ts +38 -22
  69. package/framework/types/reconcile.d.ts +7 -0
  70. package/framework/types/router.d.ts +11 -1
  71. package/framework/types/server.d.ts +11 -0
  72. package/framework/types/signal.d.ts +142 -79
  73. package/framework/types/store.d.ts +66 -8
  74. package/ghid_go_fusee_integrat_go1_26.txt +276 -0
  75. package/ghid_rust_fusee_rust_1_95_0.txt +1118 -0
  76. package/package.json +44 -36
  77. package/vite.config.js +65 -47
@@ -0,0 +1,398 @@
1
+ import {NodeType, createExpression, isDirective, findDirective, isStaticText, cloneNode,} from './ast.js'
2
+ import { CompileWarning } from './errors.js'
3
+ import { ErrorCode } from './errors-list.js'
4
+
5
+ export function transform(ast, options = {}) {
6
+ const ctx = {
7
+ components: options.components ?? new Set(),
8
+ source: options.source ?? '',
9
+ hoisted: [],
10
+ warnings: [],
11
+ scope: new Set(options.scope ?? []),
12
+ scopeStack: [],
13
+ }
14
+
15
+ markStaticPass(ast, ctx)
16
+ walkChildren(ast, node => {
17
+ if (node.children) chainConditionalsInChildren(node.children, ctx)
18
+ })
19
+
20
+ walkChildren(ast, node => {
21
+ validateFor(node, ctx)
22
+ validateModel(node, ctx)
23
+ validateComponents(node, ctx)
24
+ })
25
+
26
+ if (ctx.scope.size > 0) {
27
+ analyseScope(ast, ctx, new Set(ctx.scope))
28
+ }
29
+
30
+ ast._hoisted = ctx.hoisted
31
+
32
+ return { ast, warnings: ctx.warnings }
33
+ }
34
+
35
+ // Step 1 & 2: static marking + hoisting
36
+
37
+ function markStaticPass(node, ctx) {
38
+ if (!node) return false
39
+
40
+ if (node.type === NodeType.TEXT) {
41
+ node.isStatic = true
42
+ return true
43
+ }
44
+
45
+ if (node.type === NodeType.EXPRESSION) {
46
+ node.isStatic = isLiteralExpression(node.content)
47
+ return node.isStatic
48
+ }
49
+
50
+ if (node.type === NodeType.INTERPOLATION) {
51
+ node.isStatic = markStaticPass(node.expression, ctx)
52
+ return node.isStatic
53
+ }
54
+
55
+ if (node.type === NodeType.ELEMENT) {
56
+ const hasOnce = node.props.some(p => isDirective(p, 'once'))
57
+ if (hasOnce) {
58
+ node.isStatic = true
59
+ node.hoisted = true
60
+ ctx.hoisted.push(node)
61
+ return true
62
+ }
63
+
64
+ const hasDynamic = node.props.some(p => p.type === NodeType.DIRECTIVE || p.type === NodeType.BINDING || p.type === NodeType.EVENT)
65
+
66
+ if (hasDynamic) {
67
+ node.isStatic = false
68
+ if (node.children) node.children.forEach(c => markStaticPass(c, ctx))
69
+ return false
70
+ }
71
+
72
+ const hasInterpolatedAttr = node.props.some(p => p.type === NodeType.ATTRIBUTE && typeof p.value === 'string' && p.value.includes('{{'))
73
+ if (hasInterpolatedAttr) {
74
+ node.isStatic = false
75
+ if (node.children) node.children.forEach(c => markStaticPass(c, ctx))
76
+ return false
77
+ }
78
+
79
+ const childrenStatic = node.children ? node.children.every(c => markStaticPass(c, ctx)) : true
80
+
81
+ node.isStatic = childrenStatic
82
+
83
+ if (node.isStatic && node.children && node.children.length >= 2) {
84
+ node.hoisted = true
85
+ ctx.hoisted.push(node)
86
+ }
87
+
88
+ return node.isStatic
89
+ }
90
+
91
+ if (node.type === NodeType.COMPONENT) {
92
+ node.isStatic = false
93
+ if (node.slots) {
94
+ for (const slotChildren of Object.values(node.slots)) {
95
+ if (Array.isArray(slotChildren))
96
+ slotChildren.forEach(c => markStaticPass(c, ctx))
97
+ }
98
+ }
99
+ return false
100
+ }
101
+
102
+ if (node.type === NodeType.ROOT) {
103
+ if (node.children) node.children.forEach(c => markStaticPass(c, ctx))
104
+ return false
105
+ }
106
+
107
+ return false
108
+ }
109
+
110
+ // Step 3: chain f-if / f-else-if / f-else
111
+
112
+ function chainConditionalsInChildren(children, ctx) {
113
+ for (let i = 0; i < children.length; i++) {
114
+ const node = children[i]
115
+ if (!isElementOrComponent(node)) continue
116
+
117
+ const ifDir = findDirective(node, 'if')
118
+ if (!ifDir) {
119
+ const elseDir = findDirective(node, 'else') ?? findDirective(node, 'else-if')
120
+ if (elseDir) {
121
+ ctx.warnings.push(new CompileWarning(ErrorCode.V_ELSE_NO_IF, elseDir.loc, ctx.source))
122
+ }
123
+ continue
124
+ }
125
+
126
+ const branches = [{ condition: ifDir.expression, node }]
127
+ let j = i + 1
128
+
129
+ while (j < children.length) {
130
+ const next = children[j]
131
+ if (!isElementOrComponent(next)) {
132
+ j++
133
+ continue
134
+ }
135
+
136
+ const elseIfDir = findDirective(next, 'else-if')
137
+ const elseDir = findDirective(next, 'else')
138
+
139
+ if (elseIfDir) {
140
+ branches.push({ condition: elseIfDir.expression, node: next })
141
+ j++
142
+ } else if (elseDir) {
143
+ branches.push({ condition: null, node: next })
144
+ j++
145
+ break
146
+ } else {
147
+ break
148
+ }
149
+ }
150
+
151
+ const ifNode = {
152
+ type: 'If',
153
+ branches,
154
+ loc: node.loc,
155
+ isStatic: false,
156
+ }
157
+
158
+ children.splice(i, j - i, ifNode)
159
+ }
160
+ }
161
+
162
+ // Step 4: f-for :key validation
163
+
164
+ function validateFor(node, ctx) {
165
+ if (node.type !== NodeType.ELEMENT && node.type !== NodeType.COMPONENT) return
166
+
167
+ const forDir = findDirective(node, 'for')
168
+ if (!forDir) return
169
+
170
+ const hasKey = node.props.some(p => p.type === NodeType.BINDING && p.name === 'key')
171
+ if (!hasKey) {
172
+ ctx.warnings.push(new CompileWarning(ErrorCode.FOR_MISSING_KEY, forDir.loc, ctx.source, node.tag ?? node.name))
173
+ }
174
+ }
175
+
176
+ // Step 5: f-model element validation
177
+
178
+ const MODEL_ALLOWED = new Set(['input', 'textarea', 'select'])
179
+
180
+ function validateModel(node, ctx) {
181
+ if (node.type !== NodeType.ELEMENT) return
182
+ const modelDir = findDirective(node, 'model')
183
+ if (!modelDir) return
184
+ if (!MODEL_ALLOWED.has(node.tag.toLowerCase())) {
185
+ ctx.warnings.push(new CompileWarning(ErrorCode.MODEL_ON_NON_INPUT, modelDir.loc, ctx.source, node.tag))
186
+ }
187
+ }
188
+
189
+ // Step 6: component registration validation
190
+
191
+ function validateComponents(node, ctx) {
192
+ if (node.type !== NodeType.COMPONENT) return
193
+ if (!ctx.components.has(node.name)) {
194
+ ctx.warnings.push(new CompileWarning(ErrorCode.COMPONENT_NOT_REGISTERED, node.loc, ctx.source, node.name))
195
+ }
196
+ }
197
+
198
+ // ─── Step 7 & 8: Semantic analysis — scope + expression validation ────────────
199
+
200
+ function analyseScope(node, ctx, localScope) {
201
+ if (!node) return
202
+
203
+ switch (node.type) {
204
+ case NodeType.ROOT:
205
+ for (const child of node.children ?? []) {
206
+ analyseScope(child, ctx, localScope)
207
+ }
208
+ break
209
+
210
+ case NodeType.ELEMENT:
211
+ case NodeType.COMPONENT: {
212
+ const forDir = findDirective(node, 'for')
213
+ const childScope = forDir ? _extendScopeFromFor(localScope, forDir.arg) : localScope
214
+
215
+ for (const prop of node.props ?? []) {
216
+ _validateProp(prop, ctx, childScope)
217
+ }
218
+
219
+ for (const child of node.children ?? []) {
220
+ analyseScope(child, ctx, childScope)
221
+ }
222
+
223
+ if (node.slots) {
224
+ for (const slotChildren of Object.values(node.slots)) {
225
+ if (Array.isArray(slotChildren)) {
226
+ for (const c of slotChildren)
227
+ analyseScope(c, ctx, childScope)
228
+ }
229
+ }
230
+ }
231
+ break
232
+ }
233
+
234
+ case NodeType.INTERPOLATION:
235
+ _validateExpression(node.expression, ctx, localScope)
236
+ break
237
+
238
+ case 'If':
239
+ for (const branch of node.branches ?? []) {
240
+ if (branch.condition) _validateExpression(branch.condition, ctx, localScope)
241
+ analyseScope(branch.node, ctx, localScope)
242
+ }
243
+ break
244
+
245
+ case NodeType.TEXT:
246
+ case NodeType.EXPRESSION:
247
+ case NodeType.SLOT_OUTLET:
248
+ case NodeType.SLOT_CONTENT:
249
+ break
250
+ }
251
+ }
252
+
253
+ function _validateProp(prop, ctx, scope) {
254
+ switch (prop.type) {
255
+ case NodeType.BINDING:
256
+ _validateExpression(prop.expression, ctx, scope)
257
+ break
258
+
259
+ case NodeType.EVENT:
260
+ _validateExpression(prop.expression, ctx, scope, { isHandler: true })
261
+ break
262
+
263
+ case NodeType.DIRECTIVE:
264
+ if (prop.name === 'for')
265
+ break
266
+ if (prop.expression)
267
+ _validateExpression(prop.expression, ctx, scope)
268
+ break
269
+
270
+ case NodeType.ATTRIBUTE:
271
+ break
272
+ }
273
+ }
274
+
275
+ function _validateExpression(exprNode, ctx, scope, opts = {}) {
276
+ if (!exprNode || !exprNode.content) return
277
+ if (exprNode.isStatic) return
278
+
279
+ const raw = exprNode.content.trim()
280
+ if (!raw) return
281
+
282
+ const identifiers = _extractIdentifiers(raw)
283
+
284
+ for (const id of identifiers) {
285
+ if (JS_GLOBALS.has(id)) continue
286
+ if (opts.isHandler && id === '$event') continue
287
+
288
+ if (!scope.has(id)) {
289
+ ctx.warnings.push(new CompileWarning(
290
+ ErrorCode.UNKNOWN_IDENTIFIER,
291
+ exprNode.loc,
292
+ ctx.source,
293
+ id,
294
+ raw
295
+ ))
296
+ }
297
+ }
298
+
299
+ if (exprNode._isForKey && isLiteralExpression(raw)) {
300
+ ctx.warnings.push(new CompileWarning(
301
+ ErrorCode.STATIC_FOR_KEY,
302
+ exprNode.loc,
303
+ ctx.source,
304
+ raw
305
+ ))
306
+ }
307
+ }
308
+
309
+ function _extractIdentifiers(expr) {
310
+ const stripped = expr
311
+ .replace(/'[^'\\]*(?:\\.[^'\\]*)*'/g, '""')
312
+ .replace(/"[^"\\]*(?:\\.[^"\\]*)*"/g, '""')
313
+ .replace(/`[^`\\]*(?:\\.[^`\\]*)*`/g, '""')
314
+
315
+ const found = new Set()
316
+ const RE = /(?<![a-zA-Z0-9_$])([a-zA-Z_$][a-zA-Z0-9_$]*)(?![a-zA-Z0-9_$])/g
317
+ let m
318
+
319
+ while ((m = RE.exec(stripped)) !== null) {
320
+ const id = m[1]
321
+ const idx = m.index
322
+
323
+ if (JS_KEYWORDS.has(id)) continue
324
+ if (idx > 0 && stripped[idx - 1] === '.') continue
325
+
326
+ found.add(id)
327
+ }
328
+
329
+ return found
330
+ }
331
+
332
+ function _extendScopeFromFor(parentScope, forArg) {
333
+ if (!forArg)
334
+ return parentScope
335
+
336
+ const extended = new Set(parentScope)
337
+
338
+ if (forArg.item)
339
+ extended.add(forArg.item)
340
+ if (forArg.index)
341
+ extended.add(forArg.index)
342
+
343
+ return extended
344
+ }
345
+
346
+ const JS_GLOBALS = new Set([
347
+ 'Math', 'Object', 'Array', 'String', 'Number', 'Boolean', 'Symbol',
348
+ 'Date', 'RegExp', 'Map', 'Set', 'WeakMap', 'WeakSet', 'WeakRef',
349
+ 'Promise', 'Proxy', 'Reflect', 'JSON', 'Error', 'TypeError',
350
+ 'RangeError', 'parseInt', 'parseFloat', 'isNaN', 'isFinite',
351
+ 'encodeURIComponent', 'decodeURIComponent', 'encodeURI', 'decodeURI',
352
+ 'console', 'window', 'document', 'globalThis', 'undefined', 'null',
353
+ 'Infinity', 'NaN', 'setTimeout', 'clearTimeout', 'setInterval',
354
+ 'clearInterval', 'queueMicrotask', 'fetch', 'true', 'false'
355
+ ])
356
+
357
+ const JS_KEYWORDS = new Set([
358
+ 'if', 'else', 'for', 'while', 'do', 'switch', 'case', 'break',
359
+ 'continue', 'return', 'throw', 'try', 'catch', 'finally',
360
+ 'new', 'delete', 'typeof', 'instanceof', 'in', 'of', 'void',
361
+ 'const', 'let', 'var', 'function', 'class', 'import', 'export',
362
+ 'default', 'extends', 'super', 'this', 'yield', 'await', 'async',
363
+ 'true', 'false', 'null', 'undefined'
364
+ ])
365
+
366
+ function walkChildren(node, visitor) {
367
+ if (!node) return
368
+ visitor(node)
369
+
370
+ if (node.children) {
371
+ for (const child of node.children)
372
+ walkChildren(child, visitor)
373
+ }
374
+
375
+ if (node.branches) {
376
+ for (const branch of node.branches)
377
+ walkChildren(branch.node, visitor)
378
+ }
379
+
380
+ if (node.slots) {
381
+ for (const slotChildren of Object.values(node.slots)) {
382
+ if (Array.isArray(slotChildren)) {
383
+ for (const c of slotChildren)
384
+ walkChildren(c, visitor)
385
+ }
386
+ }
387
+ }
388
+ }
389
+
390
+ function isElementOrComponent(node) {
391
+ return node?.type === NodeType.ELEMENT || node?.type === NodeType.COMPONENT
392
+ }
393
+
394
+ const LITERAL_RE = /^(?:true|false|null|undefined|-?\d[\d._]*(?:n)?|'[^']*'|"[^"]*"|`[^`]*`)$/
395
+
396
+ function isLiteralExpression(expr) {
397
+ return LITERAL_RE.test(expr.trim())
398
+ }
@@ -1,5 +1,7 @@
1
1
  import { effect, setEffectHook, batch } from './signal.js'
2
2
  import { mountTemplate } from './compiler.js'
3
+ import { rootInjector, runInContext, replaceActiveInjector, EnvironmentInjector } from './di.js'
4
+ export { inject } from './di.js'
3
5
 
4
6
  let currentInstance = null
5
7
 
@@ -129,20 +131,25 @@ export function onUnmount(fn) {
129
131
 
130
132
  export function defineComponent(options) {
131
133
  return function ComponentFactory(props = {}, { listeners = {}, slots = {}, parent = null } = {}) {
134
+ const parentInjector = (parent && parent._injector) || rootInjector;
135
+
132
136
  const instance = {
133
137
  props: options.props ? resolveProps(options.props, props) : props,
134
138
  _mountHooks: [],
135
139
  _unmountHooks: [],
136
140
  _effects: [],
137
141
  _element: null,
138
- _provides: {},
139
142
  _parent: parent,
143
+ _injector: parentInjector,
144
+ _ownsInjector: false
140
145
  }
141
146
 
142
147
  const emit = createEmit(listeners)
143
148
 
144
149
  currentInstance = instance
145
- const result = options.setup(instance.props, { emit, slots })
150
+ const result = runInContext(instance._injector, () => {
151
+ return options.setup(instance.props, { emit, slots });
152
+ });
146
153
  instance.state = result
147
154
  result._instance = instance
148
155
  currentInstance = null
@@ -150,17 +157,41 @@ export function defineComponent(options) {
150
157
  function render(container) {
151
158
  instance._element = container
152
159
 
153
- const resolvedTemplate = resolveSlots(result.template, slots)
154
-
155
- const { effects } = mountTemplate(
156
- resolvedTemplate,
157
- container,
158
- result,
159
- options.components || {}
160
- )
160
+ if (options.render) {
161
+ const nodes = options.render(result, options.components || {})
162
+ container.innerHTML = ''
163
+
164
+ function mountNode(fnode) {
165
+ if (!fnode || !fnode.node) return
166
+ container.appendChild(fnode.node)
167
+
168
+ if (fnode.effects) {
169
+ instance._effects.push(...fnode.effects)
170
+ }
171
+
172
+ if (fnode.children) {
173
+ for (const child of fnode.children) {
174
+ mountNode(child)
175
+ }
176
+ }
177
+ }
178
+
179
+ for (const node of nodes) {
180
+ mountNode(node)
181
+ }
182
+ } else {
183
+ const resolvedTemplate = resolveSlots(result.template, slots)
184
+ const { effects } = mountTemplate(
185
+ resolvedTemplate,
186
+ container,
187
+ result,
188
+ options.components || {}
189
+ )
190
+ instance._effects.push(...effects)
191
+ }
161
192
 
162
- instance._effects.push(...effects)
163
- for (const hook of instance._mountHooks) hook()
193
+ for (const hook of instance._mountHooks)
194
+ hook()
164
195
 
165
196
  return instance
166
197
  }
@@ -183,17 +214,21 @@ export function defineComponent(options) {
183
214
  }
184
215
 
185
216
  export function provide(key, value) {
186
- if (!currentInstance) return
187
- currentInstance._provides[key] = value
188
- }
189
-
190
- export function inject(key) {
191
- let parent = currentInstance?._parent
192
- while (parent) {
193
- if (key in parent._provides) return parent._provides[key];
194
- parent = parent._parent;
217
+ if (!currentInstance) return;
218
+
219
+ if (!currentInstance._ownsInjector) {
220
+ currentInstance._injector = new EnvironmentInjector([], currentInstance._injector);
221
+ currentInstance._ownsInjector = true;
222
+ replaceActiveInjector(currentInstance._injector);
223
+ }
224
+
225
+ if (value === undefined && key && key.provide) {
226
+ currentInstance._injector.provide(key);
227
+ } else if (value && typeof value === 'object' && (value.useClass || value.useFactory || value.useExisting)) {
228
+ currentInstance._injector.provide({ provide: key, ...value });
229
+ } else {
230
+ currentInstance._injector.provide({ provide: key, useValue: value });
195
231
  }
196
- return null;
197
232
  }
198
233
 
199
234
  export function defineAsyncComponent(loaderOrOptions) {
@@ -202,14 +237,17 @@ export function defineAsyncComponent(loaderOrOptions) {
202
237
  : loaderOrOptions
203
238
 
204
239
  return function AsyncComponentFactory(props = {}, { listeners = {}, slots = {}, parent = null } = {}) {
240
+ const parentInjector = (parent && parent._injector) || rootInjector;
241
+
205
242
  const instance = {
206
243
  props,
207
244
  _mountHooks: [],
208
245
  _unmountHooks: [],
209
246
  _effects: [],
210
247
  _element: null,
211
- _provides: {},
212
248
  _parent: parent,
249
+ _injector: parentInjector,
250
+ _ownsInjector: false
213
251
  }
214
252
 
215
253
  let childApi = null