fusee-framework 1.8.0 → 1.9.1

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 (109) hide show
  1. package/README.md +64 -3
  2. package/app/benchmark/vite.config.js +23 -0
  3. package/auto-imports.d.ts +4 -0
  4. package/bin/create-fusee-linux +0 -0
  5. package/bin/create-fusee-mac-arm64 +0 -0
  6. package/bin/create-fusee-mac-intel +0 -0
  7. package/bin/create-fusee-win.exe +0 -0
  8. package/bin/create-fusee.exe +0 -0
  9. package/create-fusee-go/internal/assets/embed/framework/auto-import-preset.js +15 -9
  10. package/create-fusee-go/internal/assets/embed/framework/core/async-context.js +27 -0
  11. package/create-fusee-go/internal/assets/embed/framework/core/compiler/ast.js +167 -0
  12. package/create-fusee-go/internal/assets/embed/framework/core/compiler/errors-list.js +47 -0
  13. package/create-fusee-go/internal/assets/embed/framework/core/compiler/errors.js +107 -0
  14. package/create-fusee-go/internal/assets/embed/framework/core/compiler/generator.js +363 -0
  15. package/create-fusee-go/internal/assets/embed/framework/core/compiler/lexer.js +347 -0
  16. package/create-fusee-go/internal/assets/embed/framework/core/compiler/main-compiler.js +115 -0
  17. package/create-fusee-go/internal/assets/embed/framework/core/compiler/parser.js +414 -0
  18. package/create-fusee-go/internal/assets/embed/framework/core/compiler/plugins/actions-plugin.js +263 -0
  19. package/create-fusee-go/internal/assets/embed/framework/core/compiler/plugins/compiler-plugin.d.ts +5 -0
  20. package/create-fusee-go/internal/assets/embed/framework/core/compiler/plugins/compiler-plugin.js +45 -0
  21. package/create-fusee-go/internal/assets/embed/framework/core/compiler/plugins/file-router-plugin.js +301 -0
  22. package/create-fusee-go/internal/assets/embed/framework/core/compiler/plugins/router-plugin.js +196 -0
  23. package/create-fusee-go/internal/assets/embed/framework/core/compiler/transformer.js +398 -0
  24. package/create-fusee-go/internal/assets/embed/framework/core/component.js +61 -23
  25. package/create-fusee-go/internal/assets/embed/framework/core/di.js +187 -0
  26. package/create-fusee-go/internal/assets/embed/framework/core/directives.js +40 -96
  27. package/create-fusee-go/internal/assets/embed/framework/core/evaluator.js +1 -1
  28. package/create-fusee-go/internal/assets/embed/framework/core/h.js +430 -0
  29. package/create-fusee-go/internal/assets/embed/framework/core/hydration.js +83 -0
  30. package/create-fusee-go/internal/assets/embed/framework/core/reconcile.js +151 -0
  31. package/create-fusee-go/internal/assets/embed/framework/core/signal.js +269 -0
  32. package/create-fusee-go/internal/assets/embed/framework/core/store.js +322 -81
  33. package/create-fusee-go/internal/assets/embed/framework/index.js +5 -3
  34. package/create-fusee-go/internal/assets/embed/framework/router/file-router.js +1 -1
  35. package/create-fusee-go/internal/assets/embed/framework/router/router.js +113 -21
  36. package/create-fusee-go/internal/assets/embed/framework/server/actions.js +173 -0
  37. package/create-fusee-go/internal/assets/embed/framework/server/actions.server.js +88 -0
  38. package/create-fusee-go/internal/assets/embed/framework/server/index.js +18 -0
  39. package/create-fusee-go/internal/assets/embed/framework/types/actions.d.ts +50 -0
  40. package/create-fusee-go/internal/assets/embed/framework/types/actions.server.d.ts +36 -0
  41. package/create-fusee-go/internal/assets/embed/framework/types/compiler/ast.d.ts +166 -0
  42. package/create-fusee-go/internal/assets/embed/framework/types/compiler/errors-codes.d.ts +23 -0
  43. package/create-fusee-go/internal/assets/embed/framework/types/compiler/errors.d.ts +43 -0
  44. package/create-fusee-go/internal/assets/embed/framework/types/compiler/generator.d.ts +8 -0
  45. package/create-fusee-go/internal/assets/embed/framework/types/compiler/h.d.ts +46 -0
  46. package/create-fusee-go/internal/assets/embed/framework/types/compiler/lexer.d.ts +27 -0
  47. package/create-fusee-go/internal/assets/embed/framework/types/compiler/main-compiler.d.ts +60 -0
  48. package/create-fusee-go/internal/assets/embed/framework/types/compiler/parser.d.ts +4 -0
  49. package/create-fusee-go/internal/assets/embed/framework/types/compiler/plugins/actions.d.ts +30 -0
  50. package/create-fusee-go/internal/assets/embed/framework/types/compiler/plugins/compiler-plugin.d.ts +5 -0
  51. package/create-fusee-go/internal/assets/embed/framework/types/compiler/plugins/file-router-plugins.d.ts +32 -0
  52. package/create-fusee-go/internal/assets/embed/framework/types/compiler/plugins/router-plugin.d.ts +24 -0
  53. package/create-fusee-go/internal/assets/embed/framework/types/compiler/transformer.d.ts +28 -0
  54. package/create-fusee-go/internal/assets/embed/framework/types/component.d.ts +89 -88
  55. package/create-fusee-go/internal/assets/embed/framework/types/di.d.ts +43 -0
  56. package/create-fusee-go/internal/assets/embed/framework/types/hydration.d.ts +30 -0
  57. package/create-fusee-go/internal/assets/embed/framework/types/index.d.ts +38 -22
  58. package/create-fusee-go/internal/assets/embed/framework/types/reconcile.d.ts +7 -0
  59. package/create-fusee-go/internal/assets/embed/framework/types/router.d.ts +11 -1
  60. package/create-fusee-go/internal/assets/embed/framework/types/server.d.ts +11 -0
  61. package/create-fusee-go/internal/assets/embed/framework/types/signal.d.ts +142 -79
  62. package/create-fusee-go/internal/assets/embed/framework/types/store.d.ts +66 -8
  63. package/framework/auto-import-preset.js +3 -1
  64. package/framework/core/async-context.js +27 -0
  65. package/framework/core/compiler/ast.js +167 -0
  66. package/framework/core/compiler/errors-list.js +47 -0
  67. package/framework/core/compiler/errors.js +107 -0
  68. package/framework/core/compiler/generator.js +363 -0
  69. package/framework/core/compiler/lexer.js +347 -0
  70. package/framework/core/compiler/main-compiler.js +115 -0
  71. package/framework/core/compiler/parser.js +414 -0
  72. package/framework/core/compiler/plugins/actions-plugin.js +263 -0
  73. package/framework/core/compiler/plugins/compiler-plugin.d.ts +5 -0
  74. package/framework/core/compiler/plugins/compiler-plugin.js +45 -0
  75. package/framework/core/compiler/plugins/file-router-plugin.js +301 -0
  76. package/framework/core/compiler/plugins/router-plugin.js +196 -0
  77. package/framework/core/compiler/transformer.js +398 -0
  78. package/framework/core/component.js +34 -10
  79. package/framework/core/evaluator.js +1 -1
  80. package/framework/core/h.js +430 -0
  81. package/framework/core/hydration.js +83 -0
  82. package/framework/core/signal.js +17 -15
  83. package/framework/server/actions.js +173 -0
  84. package/framework/server/actions.server.js +88 -0
  85. package/framework/server/index.js +18 -0
  86. package/framework/types/actions.d.ts +50 -0
  87. package/framework/types/actions.server.d.ts +36 -0
  88. package/framework/types/compiler/ast.d.ts +166 -0
  89. package/framework/types/compiler/errors-codes.d.ts +23 -0
  90. package/framework/types/compiler/errors.d.ts +43 -0
  91. package/framework/types/compiler/generator.d.ts +8 -0
  92. package/framework/types/compiler/h.d.ts +46 -0
  93. package/framework/types/compiler/lexer.d.ts +27 -0
  94. package/framework/types/compiler/main-compiler.d.ts +60 -0
  95. package/framework/types/compiler/parser.d.ts +4 -0
  96. package/framework/types/compiler/plugins/actions.d.ts +30 -0
  97. package/framework/types/compiler/plugins/compiler-plugin.d.ts +5 -0
  98. package/framework/types/compiler/plugins/file-router-plugins.d.ts +32 -0
  99. package/framework/types/compiler/plugins/router-plugin.d.ts +24 -0
  100. package/framework/types/compiler/transformer.d.ts +28 -0
  101. package/framework/types/hydration.d.ts +30 -0
  102. package/framework/types/index.d.ts +6 -0
  103. package/framework/types/server.d.ts +11 -0
  104. package/framework/types/signal.d.ts +2 -7
  105. package/ghid_go_fusee_integrat_go1_26.txt +276 -0
  106. package/ghid_rust_fusee_rust_1_95_0.txt +1118 -0
  107. package/package.json +44 -36
  108. package/vite.config.js +65 -53
  109. package/create-fusee-go/internal/assets/embed/framework/bin/create-fusee.js +0 -199
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Fusée Framework
2
2
 
3
- **v1.8.0 — Signals-First JS Framework | Atomic Reactivity | Peak Performance**
3
+ **v1.9.1 — Signals-First JS Framework | Atomic Reactivity | Peak Performance**
4
4
 
5
5
  Fusée is a custom, high-performance fine-grained reactive JavaScript framework built for speed and simplicity. It features a recursive, non-greedy compiler, a signals-based reactivity engine, Dependency Injection for components and a comprehensive CLI for instant application scaffolding.
6
6
 
@@ -39,6 +39,67 @@ fusee generate page contact
39
39
 
40
40
  ---
41
41
 
42
+ ## Build-Time HTML Template Compiler (New in v1.9)
43
+
44
+ Fusée now features a fully integrated **Build-Time Template Compiler** powered by the `fuseeCompilerPlugin` for Vite. Instead of parsing and compiling HTML strings at runtime, Fusée compiles external HTML templates into highly optimized virtual DOM creation calls (`h`, `hText`, `hIf`, `hFor`) during the build/dev stage.
45
+
46
+ ### Why use the Build-Time Compiler?
47
+
48
+ - **Zero Runtime Overhead**: No template parsing in the browser, leading to smaller bundles and faster page load speeds.
49
+ - **Separation of Concerns**: Write clean HTML in `.template.html` files with full editor autocomplete, syntax highlighting, and formatting support.
50
+ - **Compile-Time Warnings**: Errors in template syntax are caught immediately in your terminal or browser console during development.
51
+
52
+ ### How to use it:
53
+
54
+ 1. **Create an HTML template file** (e.g., `Welcome.template.html`):
55
+
56
+ ```html
57
+ <div class="card">
58
+ <h2>Hello, {{ name }}!</h2>
59
+ <input f-model="name" placeholder="Type a name..." />
60
+
61
+ <button @click="increment">Clicked {{ count }} times</button>
62
+
63
+ <p f-if="count() > 0">Double: <strong>{{ double }}</strong></p>
64
+ </div>
65
+ ```
66
+
67
+ 2. **Import and bind the `render` function** in your component file (e.g., `Welcome.js`):
68
+
69
+ ```javascript
70
+ import { render } from "./Welcome.template.html";
71
+
72
+ export const Welcome = defineComponent({
73
+ render,
74
+ setup() {
75
+ const name = signal("World");
76
+ const count = signal(0);
77
+ const double = computed(() => count() * 2);
78
+
79
+ return {
80
+ name,
81
+ count,
82
+ double,
83
+ increment: () => count(count() + 1),
84
+ };
85
+ },
86
+ });
87
+ ```
88
+
89
+ Vite intercepts the `.template.html` import, runs the compiler, and inlines the generated JavaScript render code seamlessly!
90
+
91
+ Benchmark results for specific stress tests (from https://github.com/codegenixdev/js-frontend-frameworks-benchmark):
92
+
93
+ | Action | Avg Duration (ms) |
94
+ | :--------------------------------- | :---------------- |
95
+ | Create 50,000 Rows | 12.60 |
96
+ | Update Every 10th Row (Salary +50) | 15.90 |
97
+ | Swap 2nd and 9th-to-last Rows | 4.60 |
98
+ | Clear All Rows | 0.70 |
99
+ | **Total Average** | **33.80** |
100
+
101
+ ---
102
+
42
103
  ## Key Features
43
104
 
44
105
  - **New Go-Powered CLI**: Blazing fast project scaffolding and resource generation.
@@ -102,7 +163,7 @@ export const Welcome = defineComponent({
102
163
 
103
164
  Fusée is built with a test-driven mindset to ensure the reliability of its reactivity engine and component lifecycle.
104
165
 
105
- - **Total Tests:** `527`
166
+ - **Total Tests:** `550`
106
167
  - **Status:** `All Passed`
107
168
  - **Framework:** [Vitest](https://vitest.dev/)
108
169
  - **Environment:** JSDOM (Browser simulation)
@@ -139,7 +200,7 @@ npx vitest
139
200
  ## Future Roadmap
140
201
 
141
202
  - [ ] Advanced state management and SSR support
142
- - [ ] Lightweight compiler which transforms declarative HTML into optimized runtime instructions
203
+ - [x] Optimising the existing HTML compiler and remove useless DOM trasversals in lists
143
204
  - [ ] Integrated backend development support
144
205
 
145
206
  Built by me, Sebi Somu, a forward-thinking JavaScript Architect.
@@ -0,0 +1,23 @@
1
+ import { defineConfig } from 'vite'
2
+ import path from 'path'
3
+
4
+ export default defineConfig({
5
+ root: './app/benchmark',
6
+
7
+ resolve: {
8
+ alias: {
9
+ 'fusee-framework': path.resolve(__dirname, '../../framework'),
10
+ }
11
+ },
12
+
13
+ server: {
14
+ port: 5173,
15
+ open: true,
16
+ hmr: true,
17
+ },
18
+
19
+ build: {
20
+ outDir: '../../dist-benchmark',
21
+ emptyOutDir: true,
22
+ }
23
+ })
package/auto-imports.d.ts CHANGED
@@ -6,11 +6,14 @@
6
6
  // biome-ignore lint: disable
7
7
  export {}
8
8
  declare global {
9
+ const InjectionToken: typeof import('fusee-framework').InjectionToken
9
10
  const MutationType: typeof import('fusee-framework').MutationType
11
+ const assertSetupContext: typeof import('fusee-framework').assertSetupContext
10
12
  const batch: typeof import('fusee-framework').batch
11
13
  const clearStores: typeof import('fusee-framework').clearStores
12
14
  const computed: typeof import('fusee-framework').computed
13
15
  const defineComponent: typeof import('fusee-framework').defineComponent
16
+ const defineComposable: typeof import('fusee-framework').defineComposable
14
17
  const defineStore: typeof import('fusee-framework').defineStore
15
18
  const directive: typeof import('fusee-framework').directive
16
19
  const effect: typeof import('fusee-framework').effect
@@ -23,6 +26,7 @@ declare global {
23
26
  const onMount: typeof import('fusee-framework').onMount
24
27
  const onUnmount: typeof import('fusee-framework').onUnmount
25
28
  const provide: typeof import('fusee-framework').provide
29
+ const provideGlobal: typeof import('fusee-framework').provideGlobal
26
30
  const registerStorePlugin: typeof import('fusee-framework').registerStorePlugin
27
31
  const resetStore: typeof import('fusee-framework').resetStore
28
32
  const signal: typeof import('fusee-framework').signal
Binary file
Binary file
Binary file
Binary file
Binary file
@@ -1,9 +1,15 @@
1
- export const FuseePreset = {
2
- 'fusee-framework': [
3
- 'signal', 'computed', 'effect', 'watch',
4
- 'batch', 'untrack', 'inspect', 'onCleanup',
5
- 'defineComponent', 'onMount', 'onUnmount',
6
- 'directive',
7
- 'mountOutlet', 'mountTemplate', 'emit'
8
- ]
9
- }
1
+ export const FuseePreset = {
2
+ 'fusee-framework': [
3
+ 'signal', 'computed', 'effect', 'watch',
4
+ 'batch', 'untrack', 'inspect', 'onCleanup',
5
+ 'defineComponent', 'onMount', 'onUnmount',
6
+ 'directive',
7
+ 'mountOutlet', 'mountTemplate', 'emit',
8
+ 'defineStore', 'resetStore', 'clearStores',
9
+ 'registerStorePlugin', 'useNestedStore',
10
+ 'storeToRefs', 'storeToState', 'storeToGetters',
11
+ 'MutationType',
12
+ 'provide', 'inject', 'provideGlobal', 'InjectionToken',
13
+ 'defineComposable', 'assertSetupContext'
14
+ ]
15
+ }
@@ -0,0 +1,27 @@
1
+ let _als = null
2
+
3
+ export function _registerALS(als) {
4
+ _als = als
5
+ }
6
+
7
+ export function _getALS() {
8
+ return _als
9
+ }
10
+
11
+ export const _globalInFlightMaps = {
12
+ byKey: new Map(),
13
+ byFetcher: new WeakMap()
14
+ }
15
+
16
+ export function getInFlightStore() {
17
+ if (_als) {
18
+ const ctx = _als.getStore()
19
+ if (ctx) return ctx.inFlightMaps
20
+ }
21
+ return _globalInFlightMaps
22
+ }
23
+
24
+ export function isSSRContext() {
25
+ if (!_als) return false
26
+ return _als.getStore() !== undefined
27
+ }
@@ -0,0 +1,167 @@
1
+ export const NodeType = {
2
+ ROOT: 'Root',
3
+ ELEMENT: 'Element',
4
+ COMPONENT: 'Component',
5
+ TEXT: 'Text',
6
+ INTERPOLATION: 'Interpolation',
7
+ EXPRESSION: 'Expression',
8
+ ATTRIBUTE: 'Attribute',
9
+ BINDING: 'Binding',
10
+ EVENT: 'Event',
11
+ DIRECTIVE: 'Directive',
12
+ SLOT_OUTLET: 'SlotOutlet',
13
+ SLOT_CONTENT: 'SlotContent'
14
+ }
15
+
16
+ export function createLoc(start, end, source) {
17
+ return { start, end, source }
18
+ }
19
+
20
+ export function createPos(line, col, offset) {
21
+ return { line, col, offset }
22
+ }
23
+
24
+ export function createRoot(children = [], loc = null) {
25
+ return { type: NodeType.ROOT, children, loc }
26
+ }
27
+
28
+ export function createElement(tag, props = [], children = [], selfClosing = false, loc = null) {
29
+ return {
30
+ type: NodeType.ELEMENT,
31
+ tag,
32
+ props,
33
+ children,
34
+ selfClosing,
35
+ isStatic: false,
36
+ hoisted: false,
37
+ loc
38
+ }
39
+ }
40
+
41
+ export function createComponent(name, props = [], slots = {}, loc = null) {
42
+ return {
43
+ type: NodeType.COMPONENT,
44
+ name,
45
+ props,
46
+ slots,
47
+ loc
48
+ }
49
+ }
50
+
51
+ export function createText(content, loc = null) {
52
+ return { type: NodeType.TEXT, content, loc }
53
+ }
54
+
55
+ export function createInterpolation(expression, loc = null) {
56
+ return {
57
+ type: NodeType.INTERPOLATION,
58
+ expression,
59
+ loc
60
+ }
61
+ }
62
+
63
+ export function createExpression(content, isStatic = false, loc = null) {
64
+ return {
65
+ type: NodeType.EXPRESSION,
66
+ content,
67
+ isStatic,
68
+ loc
69
+ }
70
+ }
71
+
72
+ export function createAttribute(name, value, loc = null) {
73
+ return {
74
+ type: NodeType.ATTRIBUTE,
75
+ name,
76
+ value,
77
+ loc
78
+ }
79
+ }
80
+
81
+ export function createBinding(name, expression, isProp = false, loc = null) {
82
+ return {
83
+ type: NodeType.BINDING,
84
+ name,
85
+ expression,
86
+ isProp,
87
+ loc
88
+ }
89
+ }
90
+
91
+ export function createEvent(name, expression, modifiers = [], loc = null) {
92
+ return {
93
+ type: NodeType.EVENT,
94
+ name,
95
+ expression,
96
+ modifiers,
97
+ loc
98
+ }
99
+ }
100
+
101
+ export function createDirective(name, expression = null, arg = null, loc = null) {
102
+ return {
103
+ type: NodeType.DIRECTIVE,
104
+ name,
105
+ expression,
106
+ arg,
107
+ loc
108
+ }
109
+ }
110
+
111
+ export function createSlotOutlet(slotName = 'default', fallback = [], loc = null) {
112
+ return {
113
+ type: NodeType.SLOT_OUTLET,
114
+ slotName,
115
+ fallback,
116
+ loc
117
+ }
118
+ }
119
+
120
+ export function createSlotContent(slotName = 'default', children = [], loc = null) {
121
+ return {
122
+ type: NodeType.SLOT_CONTENT,
123
+ slotName,
124
+ children,
125
+ loc
126
+ }
127
+ }
128
+
129
+ export function createForArg(item, source, index = null, loc = null) {
130
+ return { item, source, index, loc }
131
+ }
132
+
133
+ export function isElement(node) {
134
+ return node?.type === NodeType.ELEMENT
135
+ }
136
+
137
+ export function isComponent(node) {
138
+ return node?.type === NodeType.COMPONENT
139
+ }
140
+
141
+ export function isText(node) {
142
+ return node?.type === NodeType.TEXT
143
+ }
144
+
145
+ export function isInterpolation(node) {
146
+ return node?.type === NodeType.INTERPOLATION
147
+ }
148
+
149
+ export function isDirective(node, name) {
150
+ return node?.type === NodeType.DIRECTIVE && (name == null || node.name === name)
151
+ }
152
+
153
+ export function isStaticText(node) {
154
+ return node?.type === NodeType.TEXT || (node?.type === NodeType.INTERPOLATION && node.expression?.isStatic)
155
+ }
156
+
157
+ export function findDirective(element, name) {
158
+ return element.props.find(p => isDirective(p, name)) ?? null
159
+ }
160
+
161
+ export function getEvents(element) {
162
+ return element.props.filter(p => p.type === NodeType.EVENT)
163
+ }
164
+
165
+ export function cloneNode(node) {
166
+ return { ...node, props: node.props ? [...node.props] : undefined, children: node.children ? [...node.children] : undefined }
167
+ }
@@ -0,0 +1,47 @@
1
+ export const ErrorCode = {
2
+ UNEXPECTED_CHAR: 'E001',
3
+ UNTERMINATED_STRING: 'E002',
4
+ UNTERMINATED_MUSTACHE: 'E003',
5
+ UNTERMINATED_TAG: 'E004',
6
+ MALFORMED_COMMENT: 'E005',
7
+ UNEXPECTED_TOKEN: 'P001',
8
+ MISSING_CLOSING_TAG: 'P002',
9
+ UNEXPECTED_CLOSING_TAG: 'P003',
10
+ INVALID_DIRECTIVE_SYNTAX: 'P004',
11
+ INVALID_FOR_SYNTAX: 'P005',
12
+ DUPLICATE_KEY: 'P006',
13
+ V_ELSE_NO_IF: 'P007',
14
+ EMPTY_EXPRESSION: 'P008',
15
+ INVALID_SLOT_NAME: 'P009',
16
+ DUPLICATE_DIRECTIVE: 'T001',
17
+ FOR_MISSING_KEY: 'T002',
18
+ MODEL_ON_NON_INPUT: 'T003',
19
+ COMPONENT_NOT_REGISTERED: 'T004',
20
+ UNKNOWN_IDENTIFIER: 'S001',
21
+ STATIC_FOR_KEY: 'S002',
22
+ UNKNOWN_NODE_TYPE: 'G001',
23
+ }
24
+
25
+ export const ERROR_MESSAGES = {
26
+ [ErrorCode.UNEXPECTED_CHAR]: 'Unexpected character "{0}"',
27
+ [ErrorCode.UNTERMINATED_STRING]: 'Unterminated string literal',
28
+ [ErrorCode.UNTERMINATED_MUSTACHE]: 'Unterminated mustache expression "{0}"',
29
+ [ErrorCode.UNTERMINATED_TAG]: 'Unterminated tag "<{0}"',
30
+ [ErrorCode.MALFORMED_COMMENT]: 'Malformed HTML comment',
31
+ [ErrorCode.UNEXPECTED_TOKEN]: 'Unexpected token "{0}", expected "{1}"',
32
+ [ErrorCode.MISSING_CLOSING_TAG]: 'Missing closing tag for <{0}>',
33
+ [ErrorCode.UNEXPECTED_CLOSING_TAG]: 'Unexpected closing tag </{0}>',
34
+ [ErrorCode.INVALID_DIRECTIVE_SYNTAX]: 'Invalid directive syntax: "{0}"',
35
+ [ErrorCode.INVALID_FOR_SYNTAX]: 'Invalid f-for syntax: "{0}". Expected "(item, index) in source" or "item in source"',
36
+ [ErrorCode.DUPLICATE_KEY]: 'Duplicate attribute "{0}" on element <{1}>',
37
+ [ErrorCode.V_ELSE_NO_IF]: 'f-else / f-else-if has no matching f-if',
38
+ [ErrorCode.EMPTY_EXPRESSION]: 'Expression in "{0}" must not be empty',
39
+ [ErrorCode.INVALID_SLOT_NAME]: 'Invalid slot name "{0}"',
40
+ [ErrorCode.DUPLICATE_DIRECTIVE]: 'Duplicate directive "f-{0}" on element <{1}>',
41
+ [ErrorCode.FOR_MISSING_KEY]: 'f-for on <{0}> is missing a :key binding — add :key="<unique expr>"',
42
+ [ErrorCode.MODEL_ON_NON_INPUT]: 'f-model can only be used on <input>, <textarea> or <select>, got <{0}>',
43
+ [ErrorCode.COMPONENT_NOT_REGISTERED]: 'Component "{0}" is used in the template but not registered',
44
+ [ErrorCode.UNKNOWN_NODE_TYPE]: 'Unknown AST node type "{0}" encountered in generator',
45
+ [ErrorCode.UNKNOWN_IDENTIFIER]: 'Unknown identifier "{0}" in expression "{1}" — not found in component scope',
46
+ [ErrorCode.STATIC_FOR_KEY]: ':key="{0}" is a static literal — keys must be unique per item (use a dynamic expression like :key="item.id")',
47
+ }
@@ -0,0 +1,107 @@
1
+ import { ErrorCode, ERROR_MESSAGES } from './errors-list.js'
2
+
3
+ export class CompileError extends Error {
4
+ constructor(code, loc, source, ...args) {
5
+ const template = ERROR_MESSAGES[code] ?? `Unknown compiler error (${code})`
6
+ const message = _interpolate(template, args)
7
+ super(message)
8
+ this.name = 'CompileError'
9
+ this.code = code
10
+ this.loc = loc
11
+ this.source = source
12
+ this._snippet = null
13
+ }
14
+
15
+ format(filePath = '<template>') {
16
+ const { line, col } = this.loc?.start ?? { line: '?', col: '?' }
17
+ const header = `[fusée] ${this.name} ${this.code} at ${filePath}:${line}:${col}`
18
+ const snippet = this._buildSnippet()
19
+ return snippet ? `${header}\n${this.message}\n\n${snippet}` : `${header}\n${this.message}`
20
+ }
21
+
22
+ _buildSnippet() {
23
+ if (this._snippet !== null) return this._snippet
24
+ if (!this.source || !this.loc?.start) {
25
+ this._snippet = ''
26
+ return ''
27
+ }
28
+
29
+ const lines = this.source.split('\n')
30
+ const lineIdx = this.loc.start.line - 1
31
+ const col = this.loc.start.col
32
+ const CONTEXT = 2
33
+ const firstLine = Math.max(0, lineIdx - CONTEXT)
34
+ const lastLine = Math.min(lines.length - 1, lineIdx + CONTEXT)
35
+ const width = String(lastLine + 1).length
36
+ const parts = []
37
+
38
+ for (let i = firstLine; i <= lastLine; i++) {
39
+ const lineNo = String(i + 1).padStart(width, ' ')
40
+ const prefix = i === lineIdx ? '> ' : ' '
41
+ parts.push(`${prefix}${lineNo} | ${lines[i]}`)
42
+ if (i === lineIdx) {
43
+ const caretPad = ' '.repeat(width + 4 + (col - 1))
44
+ const caretLen = Math.max(1, (this.loc.end?.col ?? col) - col)
45
+ parts.push(`${caretPad}${'~'.repeat(caretLen)}`)
46
+ }
47
+ }
48
+
49
+ this._snippet = parts.join('\n')
50
+ return this._snippet
51
+ }
52
+ }
53
+
54
+ export class CompileWarning {
55
+ constructor(code, loc, source, ...args) {
56
+ const template = ERROR_MESSAGES[code] ?? `Unknown compiler warning (${code})`
57
+ this.message = _interpolate(template, args)
58
+ this.code = code
59
+ this.loc = loc
60
+ this.source = source
61
+ }
62
+
63
+ format(filePath = '<template>') {
64
+ const { line, col } = this.loc?.start ?? { line: '?', col: '?' }
65
+ return `[fusée] Warning ${this.code} at ${filePath}:${line}:${col}\n${this.message}`
66
+ }
67
+ }
68
+
69
+ export class ErrorCollector {
70
+ constructor(source) {
71
+ this.source = source
72
+ this.errors = []
73
+ this.warnings = []
74
+ }
75
+
76
+ error(code, loc, ...args) {
77
+ const err = new CompileError(code, loc, this.source, ...args)
78
+ this.errors.push(err)
79
+ return err
80
+ }
81
+
82
+ warn(code, loc, ...args) {
83
+ const w = new CompileWarning(code, loc, this.source, ...args)
84
+ this.warnings.push(w)
85
+ return w
86
+ }
87
+
88
+ throwIfAny() {
89
+ if (this.errors.length > 0) throw this.errors[0]
90
+ }
91
+
92
+ get hasErrors() {
93
+ return this.errors.length > 0
94
+ }
95
+
96
+ get hasWarnings() {
97
+ return this.warnings.length > 0
98
+ }
99
+ }
100
+
101
+ function _interpolate(template, args) {
102
+ return template.replace(/\{(\d+)\}/g, (_, i) => args[i] ?? '')
103
+ }
104
+
105
+ export function throwError(code, loc, source, ...args) {
106
+ throw new CompileError(code, loc, source, ...args)
107
+ }