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.
- package/README.md +64 -3
- package/app/benchmark/vite.config.js +23 -0
- package/auto-imports.d.ts +4 -0
- package/bin/create-fusee-linux +0 -0
- package/bin/create-fusee-mac-arm64 +0 -0
- package/bin/create-fusee-mac-intel +0 -0
- package/bin/create-fusee-win.exe +0 -0
- package/bin/create-fusee.exe +0 -0
- package/create-fusee-go/internal/assets/embed/framework/auto-import-preset.js +15 -9
- package/create-fusee-go/internal/assets/embed/framework/core/async-context.js +27 -0
- package/create-fusee-go/internal/assets/embed/framework/core/compiler/ast.js +167 -0
- package/create-fusee-go/internal/assets/embed/framework/core/compiler/errors-list.js +47 -0
- package/create-fusee-go/internal/assets/embed/framework/core/compiler/errors.js +107 -0
- package/create-fusee-go/internal/assets/embed/framework/core/compiler/generator.js +363 -0
- package/create-fusee-go/internal/assets/embed/framework/core/compiler/lexer.js +347 -0
- package/create-fusee-go/internal/assets/embed/framework/core/compiler/main-compiler.js +115 -0
- package/create-fusee-go/internal/assets/embed/framework/core/compiler/parser.js +414 -0
- package/create-fusee-go/internal/assets/embed/framework/core/compiler/plugins/actions-plugin.js +263 -0
- package/create-fusee-go/internal/assets/embed/framework/core/compiler/plugins/compiler-plugin.d.ts +5 -0
- package/create-fusee-go/internal/assets/embed/framework/core/compiler/plugins/compiler-plugin.js +45 -0
- package/create-fusee-go/internal/assets/embed/framework/core/compiler/plugins/file-router-plugin.js +301 -0
- package/create-fusee-go/internal/assets/embed/framework/core/compiler/plugins/router-plugin.js +196 -0
- package/create-fusee-go/internal/assets/embed/framework/core/compiler/transformer.js +398 -0
- package/create-fusee-go/internal/assets/embed/framework/core/component.js +61 -23
- package/create-fusee-go/internal/assets/embed/framework/core/di.js +187 -0
- package/create-fusee-go/internal/assets/embed/framework/core/directives.js +40 -96
- package/create-fusee-go/internal/assets/embed/framework/core/evaluator.js +1 -1
- package/create-fusee-go/internal/assets/embed/framework/core/h.js +430 -0
- package/create-fusee-go/internal/assets/embed/framework/core/hydration.js +83 -0
- package/create-fusee-go/internal/assets/embed/framework/core/reconcile.js +151 -0
- package/create-fusee-go/internal/assets/embed/framework/core/signal.js +269 -0
- package/create-fusee-go/internal/assets/embed/framework/core/store.js +322 -81
- package/create-fusee-go/internal/assets/embed/framework/index.js +5 -3
- package/create-fusee-go/internal/assets/embed/framework/router/file-router.js +1 -1
- package/create-fusee-go/internal/assets/embed/framework/router/router.js +113 -21
- package/create-fusee-go/internal/assets/embed/framework/server/actions.js +173 -0
- package/create-fusee-go/internal/assets/embed/framework/server/actions.server.js +88 -0
- package/create-fusee-go/internal/assets/embed/framework/server/index.js +18 -0
- package/create-fusee-go/internal/assets/embed/framework/types/actions.d.ts +50 -0
- package/create-fusee-go/internal/assets/embed/framework/types/actions.server.d.ts +36 -0
- package/create-fusee-go/internal/assets/embed/framework/types/compiler/ast.d.ts +166 -0
- package/create-fusee-go/internal/assets/embed/framework/types/compiler/errors-codes.d.ts +23 -0
- package/create-fusee-go/internal/assets/embed/framework/types/compiler/errors.d.ts +43 -0
- package/create-fusee-go/internal/assets/embed/framework/types/compiler/generator.d.ts +8 -0
- package/create-fusee-go/internal/assets/embed/framework/types/compiler/h.d.ts +46 -0
- package/create-fusee-go/internal/assets/embed/framework/types/compiler/lexer.d.ts +27 -0
- package/create-fusee-go/internal/assets/embed/framework/types/compiler/main-compiler.d.ts +60 -0
- package/create-fusee-go/internal/assets/embed/framework/types/compiler/parser.d.ts +4 -0
- package/create-fusee-go/internal/assets/embed/framework/types/compiler/plugins/actions.d.ts +30 -0
- package/create-fusee-go/internal/assets/embed/framework/types/compiler/plugins/compiler-plugin.d.ts +5 -0
- package/create-fusee-go/internal/assets/embed/framework/types/compiler/plugins/file-router-plugins.d.ts +32 -0
- package/create-fusee-go/internal/assets/embed/framework/types/compiler/plugins/router-plugin.d.ts +24 -0
- package/create-fusee-go/internal/assets/embed/framework/types/compiler/transformer.d.ts +28 -0
- package/create-fusee-go/internal/assets/embed/framework/types/component.d.ts +89 -88
- package/create-fusee-go/internal/assets/embed/framework/types/di.d.ts +43 -0
- package/create-fusee-go/internal/assets/embed/framework/types/hydration.d.ts +30 -0
- package/create-fusee-go/internal/assets/embed/framework/types/index.d.ts +38 -22
- package/create-fusee-go/internal/assets/embed/framework/types/reconcile.d.ts +7 -0
- package/create-fusee-go/internal/assets/embed/framework/types/router.d.ts +11 -1
- package/create-fusee-go/internal/assets/embed/framework/types/server.d.ts +11 -0
- package/create-fusee-go/internal/assets/embed/framework/types/signal.d.ts +142 -79
- package/create-fusee-go/internal/assets/embed/framework/types/store.d.ts +66 -8
- package/framework/auto-import-preset.js +3 -1
- package/framework/core/async-context.js +27 -0
- package/framework/core/compiler/ast.js +167 -0
- package/framework/core/compiler/errors-list.js +47 -0
- package/framework/core/compiler/errors.js +107 -0
- package/framework/core/compiler/generator.js +363 -0
- package/framework/core/compiler/lexer.js +347 -0
- package/framework/core/compiler/main-compiler.js +115 -0
- package/framework/core/compiler/parser.js +414 -0
- package/framework/core/compiler/plugins/actions-plugin.js +263 -0
- package/framework/core/compiler/plugins/compiler-plugin.d.ts +5 -0
- package/framework/core/compiler/plugins/compiler-plugin.js +45 -0
- package/framework/core/compiler/plugins/file-router-plugin.js +301 -0
- package/framework/core/compiler/plugins/router-plugin.js +196 -0
- package/framework/core/compiler/transformer.js +398 -0
- package/framework/core/component.js +34 -10
- package/framework/core/evaluator.js +1 -1
- package/framework/core/h.js +430 -0
- package/framework/core/hydration.js +83 -0
- package/framework/core/signal.js +17 -15
- package/framework/server/actions.js +173 -0
- package/framework/server/actions.server.js +88 -0
- package/framework/server/index.js +18 -0
- package/framework/types/actions.d.ts +50 -0
- package/framework/types/actions.server.d.ts +36 -0
- package/framework/types/compiler/ast.d.ts +166 -0
- package/framework/types/compiler/errors-codes.d.ts +23 -0
- package/framework/types/compiler/errors.d.ts +43 -0
- package/framework/types/compiler/generator.d.ts +8 -0
- package/framework/types/compiler/h.d.ts +46 -0
- package/framework/types/compiler/lexer.d.ts +27 -0
- package/framework/types/compiler/main-compiler.d.ts +60 -0
- package/framework/types/compiler/parser.d.ts +4 -0
- package/framework/types/compiler/plugins/actions.d.ts +30 -0
- package/framework/types/compiler/plugins/compiler-plugin.d.ts +5 -0
- package/framework/types/compiler/plugins/file-router-plugins.d.ts +32 -0
- package/framework/types/compiler/plugins/router-plugin.d.ts +24 -0
- package/framework/types/compiler/transformer.d.ts +28 -0
- package/framework/types/hydration.d.ts +30 -0
- package/framework/types/index.d.ts +6 -0
- package/framework/types/server.d.ts +11 -0
- package/framework/types/signal.d.ts +2 -7
- package/ghid_go_fusee_integrat_go1_26.txt +276 -0
- package/ghid_rust_fusee_rust_1_95_0.txt +1118 -0
- package/package.json +44 -36
- package/vite.config.js +65 -53
- 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.
|
|
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:** `
|
|
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
|
-
- [
|
|
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
|
package/bin/create-fusee-linux
CHANGED
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/bin/create-fusee-win.exe
CHANGED
|
Binary file
|
package/bin/create-fusee.exe
CHANGED
|
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
|
+
}
|