fusee-framework 1.5.3 → 1.7.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 (115) hide show
  1. package/README.md +30 -13
  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/Counter.ts +1 -1
  7. package/app/components/CounterSection.js +3 -4
  8. package/app/main.js +29 -62
  9. package/app/main.ts +72 -63
  10. package/app/pages/404.js +16 -0
  11. package/app/pages/[...all].js +35 -0
  12. package/app/pages/[...all].ts +35 -0
  13. package/app/pages/_layout.js +26 -0
  14. package/app/pages/_layout.ts +26 -0
  15. package/app/pages/about.js +23 -0
  16. package/app/pages/about.ts +29 -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 +117 -0
  22. package/app/pages/index.ts +78 -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 +56 -0
  29. package/app/pages/router-example.ts +56 -0
  30. package/app/pages/test-for.js +65 -0
  31. package/app/pages/test-for.ts +80 -0
  32. package/app/pages/users/[id].js +24 -0
  33. package/app/pages/users/[id].ts +46 -0
  34. package/app/pages/users/index.js +22 -0
  35. package/app/pages/users/index.ts +29 -0
  36. package/app/pages/users.js +43 -0
  37. package/app/pages/users.ts +78 -0
  38. package/app/routes/About.js +1 -1
  39. package/app/routes/About.ts +1 -1
  40. package/app/routes/AuthRoutes.ts +41 -0
  41. package/app/routes/Home.js +9 -0
  42. package/app/routes/NotFound.js +16 -0
  43. package/app/routes/NotFound.ts +16 -0
  44. package/app/routes/RouterExample.js +56 -0
  45. package/app/routes/RouterExample.ts +56 -0
  46. package/app/routes/WildcardDemo.js +35 -0
  47. package/app/routes/WildcardDemo.ts +35 -0
  48. package/auto-imports.d.ts +1 -0
  49. package/bin/create-fusee-linux +0 -0
  50. package/bin/create-fusee-mac-arm64 +0 -0
  51. package/bin/create-fusee-mac-intel +0 -0
  52. package/bin/create-fusee-win.exe +0 -0
  53. package/bin/create-fusee.exe +0 -0
  54. package/bin/index.js +38 -0
  55. package/create-fusee-go/cmd/generate.go +72 -0
  56. package/create-fusee-go/cmd/init.go +38 -0
  57. package/create-fusee-go/cmd/root.go +121 -0
  58. package/create-fusee-go/go.mod +10 -0
  59. package/create-fusee-go/go.sum +10 -0
  60. package/create-fusee-go/internal/assets/assets.go +78 -0
  61. package/create-fusee-go/internal/assets/embed/framework/auto-import-preset.js +9 -0
  62. package/create-fusee-go/internal/assets/embed/framework/core/compiler.js +253 -0
  63. package/create-fusee-go/internal/assets/embed/framework/core/component.js +266 -0
  64. package/create-fusee-go/internal/assets/embed/framework/core/composable.js +27 -0
  65. package/create-fusee-go/internal/assets/embed/framework/core/directives.js +875 -0
  66. package/create-fusee-go/internal/assets/embed/framework/core/evaluator.js +153 -0
  67. package/create-fusee-go/internal/assets/embed/framework/core/event-delegation.js +191 -0
  68. package/create-fusee-go/internal/assets/embed/framework/core/signal.js +412 -0
  69. package/create-fusee-go/internal/assets/embed/framework/core/store.js +81 -0
  70. package/create-fusee-go/internal/assets/embed/framework/index.d.ts +3 -0
  71. package/create-fusee-go/internal/assets/embed/framework/index.js +10 -0
  72. package/create-fusee-go/internal/assets/embed/framework/router/file-router.js +177 -0
  73. package/create-fusee-go/internal/assets/embed/framework/router/router.js +462 -0
  74. package/create-fusee-go/internal/assets/embed/framework/types/compiler.d.ts +15 -0
  75. package/create-fusee-go/internal/assets/embed/framework/types/component.d.ts +88 -0
  76. package/create-fusee-go/internal/assets/embed/framework/types/composable.d.ts +14 -0
  77. package/create-fusee-go/internal/assets/embed/framework/types/directives.d.ts +55 -0
  78. package/create-fusee-go/internal/assets/embed/framework/types/evaluator.d.ts +13 -0
  79. package/create-fusee-go/internal/assets/embed/framework/types/event-delegation.d.ts +32 -0
  80. package/create-fusee-go/internal/assets/embed/framework/types/index.d.ts +22 -0
  81. package/create-fusee-go/internal/assets/embed/framework/types/router.d.ts +32 -0
  82. package/create-fusee-go/internal/assets/embed/framework/types/signal.d.ts +79 -0
  83. package/create-fusee-go/internal/assets/embed/framework/types/store.d.ts +8 -0
  84. package/create-fusee-go/internal/assets/templates/Counter.tmpl +26 -0
  85. package/create-fusee-go/internal/assets/templates/component.tmpl +11 -0
  86. package/create-fusee-go/internal/assets/templates/components/Loading.tmpl +24 -0
  87. package/create-fusee-go/internal/assets/templates/index.html.tmpl +15 -0
  88. package/create-fusee-go/internal/assets/templates/main.tmpl +14 -0
  89. package/create-fusee-go/internal/assets/templates/package.json.tmpl +14 -0
  90. package/create-fusee-go/internal/assets/templates/page.tmpl +12 -0
  91. package/create-fusee-go/internal/assets/templates/pages/about.tmpl +20 -0
  92. package/create-fusee-go/internal/assets/templates/pages/index.tmpl +22 -0
  93. package/create-fusee-go/internal/assets/templates/pages/layout.tmpl +18 -0
  94. package/create-fusee-go/internal/assets/templates/tsconfig.json.tmpl +14 -0
  95. package/create-fusee-go/internal/assets/templates/vite.config.js.tmpl +18 -0
  96. package/create-fusee-go/main.go +7 -0
  97. package/framework/auto-import-preset.js +1 -1
  98. package/framework/core/compiler.js +252 -249
  99. package/framework/core/component.js +265 -260
  100. package/framework/core/directives.js +875 -632
  101. package/framework/core/evaluator.js +153 -70
  102. package/framework/core/event-delegation.js +191 -0
  103. package/framework/core/signal.js +412 -404
  104. package/framework/index.js +10 -11
  105. package/framework/router/file-router.js +177 -0
  106. package/framework/router/router.js +462 -80
  107. package/framework/types/component.d.ts +0 -4
  108. package/framework/types/event-delegation.d.ts +32 -0
  109. package/framework/types/index.d.ts +3 -0
  110. package/framework/types/router.d.ts +32 -15
  111. package/framework/types/signal.d.ts +1 -0
  112. package/index.html +638 -563
  113. package/package.json +5 -3
  114. package/tsconfig.json +1 -0
  115. /package/{framework → create-fusee-go/internal/assets/embed/framework}/bin/create-fusee.js +0 -0
@@ -0,0 +1,153 @@
1
+ export const MUSTACHE_RE = /\{\{\s*(.+?)\s*\}\}/
2
+
3
+ const SENSITIVE_ATTRS = ['href', 'src', 'srcset', 'formaction', 'xlink:href', 'data']
4
+ const DANGEROUS_SCHEMES = /^(javascript|data|vbscript|file):/i
5
+
6
+ export function evaluateExpression(expr, context, extraContext = {}, unwrapSignals = true) {
7
+ const keys = []
8
+ const values = []
9
+
10
+ const exprWithoutStrings = expr.replace(/'[^']*'|"[^"]*"/g, ' ')
11
+ const identifiers = new Set(exprWithoutStrings.match(/[a-zA-Z_$][a-zA-Z0-9_$]*/g) || [])
12
+
13
+ for (const k in context) {
14
+ if (identifiers.has(k)) {
15
+ keys.push(k)
16
+ const val = context[k]
17
+ values.push(val)
18
+ }
19
+ }
20
+
21
+ for (const k in extraContext) {
22
+ if (!keys.includes(k)) {
23
+ keys.push(k)
24
+ values.push(extraContext[k])
25
+ }
26
+ }
27
+
28
+ try {
29
+ const isMultiStatement = expr.includes(';') || expr.includes('console.')
30
+ let body
31
+ if (isMultiStatement) {
32
+ body = expr
33
+ } else {
34
+ const keysToUnwrap = unwrapSignals ? keys : []
35
+ const unwrapStatements = keysToUnwrap.map(id =>
36
+ `const __${id} = typeof ${id} === 'function' && ${id}.isSignal ? ${id}() : ${id};`
37
+ ).join('\n')
38
+ let wrappedExpr = expr
39
+ for (const id of keysToUnwrap) {
40
+ wrappedExpr = wrappedExpr.replace(new RegExp('\\b' + id + '\\b', 'g'), '__' + id)
41
+ }
42
+ body = (unwrapStatements ? unwrapStatements + '\n' : '') + `return ${wrappedExpr}`
43
+ }
44
+ const fn = new Function(...keys, body)
45
+ return fn(...values)
46
+ } catch (e) {
47
+ console.warn(`[framework] Error evaluating "${expr}":`, e)
48
+ return ''
49
+ }
50
+ }
51
+
52
+ export function parseInterpolation(str) {
53
+ const parts = []
54
+ let i = 0
55
+ let lastIndex = 0
56
+
57
+ while (i < str.length) {
58
+ if (str[i] === '{' && str[i+1] === '{') {
59
+ if (i > 0 && str[i-1] === '\\') {
60
+ if (i - 1 > lastIndex) {
61
+ parts.push({ type: 'static', value: str.slice(lastIndex, i - 1) })
62
+ }
63
+ parts.push({ type: 'static', value: '{{' })
64
+ i += 2
65
+ lastIndex = i
66
+ continue
67
+ }
68
+
69
+ if (i > lastIndex) {
70
+ parts.push({ type: 'static', value: str.slice(lastIndex, i) })
71
+ }
72
+
73
+ let start = i + 2
74
+ let j = start
75
+ let braceLevel = 0
76
+ let inString = false
77
+ let stringChar = null
78
+
79
+ while (j < str.length) {
80
+ const char = str[j]
81
+
82
+ if (inString) {
83
+ if (char === '\\') {
84
+ j += 2
85
+ continue
86
+ }
87
+ if (char === stringChar) {
88
+ inString = false
89
+ }
90
+ j++
91
+ continue
92
+ }
93
+
94
+ if (char === "'" || char === '"' || char === '`') {
95
+ inString = true
96
+ stringChar = char
97
+ j++
98
+ continue
99
+ }
100
+
101
+ if (char === '{') {
102
+ braceLevel++
103
+ j++
104
+ continue
105
+ }
106
+
107
+ if (char === '}') {
108
+ if (braceLevel > 0) {
109
+ braceLevel--
110
+ j++
111
+ continue
112
+ } else if (j + 1 < str.length && str[j+1] === '}') {
113
+ const key = str.slice(start, j).trim()
114
+ parts.push({ type: 'dynamic', key })
115
+ i = j + 2
116
+ lastIndex = i
117
+ break
118
+ }
119
+ }
120
+
121
+ j++
122
+ }
123
+
124
+ if (j >= str.length) {
125
+ parts.push({ type: 'static', value: '{{' })
126
+ i += 2
127
+ lastIndex = i
128
+ continue
129
+ }
130
+
131
+ } else {
132
+ i++
133
+ }
134
+ }
135
+
136
+ if (lastIndex < str.length) {
137
+ parts.push({ type: 'static', value: str.slice(lastIndex) })
138
+ }
139
+
140
+ return parts
141
+ }
142
+
143
+ export function sanitizeAttr(name, value) {
144
+ const attrName = name.toLowerCase()
145
+ if (SENSITIVE_ATTRS.includes(attrName)) {
146
+ const trimmed = value.trim()
147
+ if (DANGEROUS_SCHEMES.test(trimmed)) {
148
+ console.warn(`[framework] Blocked potential XSS on ${name}: "${value}"`)
149
+ return 'about:blank'
150
+ }
151
+ }
152
+ return value
153
+ }
@@ -0,0 +1,191 @@
1
+ import { batch } from './signal.js'
2
+ import { evaluateExpression } from './evaluator.js'
3
+
4
+ export const DELEGATED_EVENTS = new Set([
5
+ 'beforeinput',
6
+ 'click',
7
+ 'dblclick',
8
+ 'contextmenu',
9
+ 'focusin',
10
+ 'focusout',
11
+ 'input',
12
+ 'keydown',
13
+ 'keyup',
14
+ 'mousedown',
15
+ 'mousemove',
16
+ 'mouseout',
17
+ 'mouseover',
18
+ 'mouseup',
19
+ 'pointerdown',
20
+ 'pointermove',
21
+ 'pointerout',
22
+ 'pointerover',
23
+ 'pointerup',
24
+ 'touchend',
25
+ 'touchmove',
26
+ 'touchstart'
27
+ ])
28
+
29
+ const delegatedListeners = new Map()
30
+ const documentListeners = new Map()
31
+
32
+ export function isDelegatedEvent(eventName) {
33
+ return DELEGATED_EVENTS.has(eventName.toLowerCase())
34
+ }
35
+
36
+ function ensureDocumentListener(eventType) {
37
+ if (documentListeners.has(eventType)) {
38
+ return
39
+ }
40
+
41
+ const handler = (e) => {
42
+ const listeners = delegatedListeners.get(eventType)
43
+ if (!listeners || listeners.size === 0) return
44
+
45
+ let target = e.target
46
+ while (target && target !== document.body && target !== document.documentElement) {
47
+ for (const entry of listeners) {
48
+ if (entry.element === target) {
49
+ entry.handler(e)
50
+ }
51
+ }
52
+ target = target.parentNode
53
+ }
54
+ }
55
+
56
+ document.addEventListener(eventType, handler, true)
57
+ documentListeners.set(eventType, handler)
58
+ }
59
+
60
+ export function registerDelegatedEvent(element, eventType, handler, options = {}) {
61
+ const { modifiers = [], context, expr, handlerState } = options
62
+ const normalizedEvent = eventType.toLowerCase()
63
+
64
+ ensureDocumentListener(normalizedEvent)
65
+
66
+ if (!delegatedListeners.has(normalizedEvent))
67
+ delegatedListeners.set(normalizedEvent, new Set())
68
+
69
+ const entry = {
70
+ element,
71
+ handler,
72
+ modifiers,
73
+ context,
74
+ expr,
75
+ cleanup: null,
76
+ handlerState: handlerState || { timeoutId: null, throttleTimeoutId: null, lastRun: 0 }
77
+ }
78
+
79
+ delegatedListeners.get(normalizedEvent).add(entry)
80
+
81
+ return function cleanup() {
82
+ const state = entry.handlerState
83
+ if (state.timeoutId) {
84
+ clearTimeout(state.timeoutId)
85
+ state.timeoutId = null
86
+ }
87
+ if (state.throttleTimeoutId) {
88
+ clearTimeout(state.throttleTimeoutId)
89
+ state.throttleTimeoutId = null
90
+ }
91
+
92
+ const listeners = delegatedListeners.get(normalizedEvent)
93
+ if (listeners) {
94
+ listeners.delete(entry)
95
+ if (listeners.size === 0) {
96
+ const docHandler = documentListeners.get(normalizedEvent)
97
+ if (docHandler) {
98
+ document.removeEventListener(normalizedEvent, docHandler, true)
99
+ documentListeners.delete(normalizedEvent)
100
+ }
101
+ delegatedListeners.delete(normalizedEvent)
102
+ }
103
+ }
104
+ }
105
+ }
106
+
107
+ export function createEventHandler(handler, modifiers, context, expr, state = null) {
108
+ const timeoutId = { value: null }
109
+ const throttleTimeoutId = { value: null }
110
+ const lastRun = { value: 0 }
111
+
112
+ let debounceMs = 0
113
+ let throttleMs = 0
114
+
115
+ const parseDuration = (idx) => {
116
+ const next = modifiers[idx + 1]
117
+ if (next && /^\d/.test(next)) {
118
+ if (next.endsWith('ms')) return parseInt(next) || 250
119
+ if (next.endsWith('s')) return (parseFloat(next) || 0.25) * 1000
120
+ }
121
+ return 250
122
+ }
123
+
124
+ const debounceIdx = modifiers.indexOf('debounce')
125
+ if (debounceIdx > -1) debounceMs = parseDuration(debounceIdx)
126
+
127
+ const throttleIdx = modifiers.indexOf('throttle')
128
+ if (throttleIdx > -1) throttleMs = parseDuration(throttleIdx)
129
+
130
+ const executeLogic = (e) => {
131
+ batch(() => {
132
+ if (typeof handler === 'function') {
133
+ handler(e)
134
+ } else {
135
+ evaluateExpression(expr, context, { $event: e }, false)
136
+ }
137
+ })
138
+ }
139
+
140
+ return (e) => {
141
+ if (modifiers.includes('prevent')) e.preventDefault()
142
+ if (modifiers.includes('stop')) e.stopPropagation()
143
+ if (modifiers.includes('self') && e.target !== e.currentTarget) return
144
+
145
+ if (e instanceof KeyboardEvent) {
146
+ const key = e.key.toLowerCase()
147
+ const keyModifiers = modifiers.filter(m => ['enter', 'escape', 'tab', 'space', 'arrowup', 'arrowdown', 'arrowleft', 'arrowright'].includes(m))
148
+ if (keyModifiers.length > 0) {
149
+ const match = keyModifiers.some(m => {
150
+ if (m === 'escape') return key === 'escape' || key === 'esc'
151
+ if (m === 'space') return key === ' ' || key === 'spacebar'
152
+ return key === m
153
+ })
154
+ if (!match) return
155
+ }
156
+ }
157
+
158
+ if (throttleMs > 0) {
159
+ const now = Date.now()
160
+ const remaining = throttleMs - (now - lastRun.value)
161
+
162
+ if (remaining <= 0) {
163
+ if (state && state.throttleTimeoutId) clearTimeout(state.throttleTimeoutId)
164
+ else clearTimeout(throttleTimeoutId.value)
165
+ lastRun.value = now
166
+ executeLogic(e)
167
+ } else {
168
+ if (state && state.throttleTimeoutId) clearTimeout(state.throttleTimeoutId)
169
+ else clearTimeout(throttleTimeoutId.value)
170
+ const newTimeoutId = setTimeout(() => {
171
+ lastRun.value = Date.now()
172
+ executeLogic(e)
173
+ }, remaining)
174
+ if (state) state.throttleTimeoutId = newTimeoutId
175
+ else throttleTimeoutId.value = newTimeoutId
176
+ }
177
+ if (state) state.lastRun = lastRun.value
178
+ return
179
+ }
180
+
181
+ if (debounceMs > 0) {
182
+ if (state && state.timeoutId) clearTimeout(state.timeoutId)
183
+ else clearTimeout(timeoutId.value)
184
+ const newTimeoutId = setTimeout(() => executeLogic(e), debounceMs)
185
+ if (state) state.timeoutId = newTimeoutId
186
+ else timeoutId.value = newTimeoutId
187
+ } else {
188
+ executeLogic(e)
189
+ }
190
+ }
191
+ }