fusee-framework 2.7.0 → 2.7.2
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 +98 -26
- 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/framework/auto-import-preset.js +20 -12
- package/framework/auto-imports.d.ts +68 -39
- package/framework/core/compiler.js +310 -310
- package/framework/core/component-core.js +445 -0
- package/framework/core/component.js +11 -440
- package/framework/core/composable.js +29 -29
- package/framework/core/h.js +119 -44
- package/framework/core/jsx-helpers.js +11 -2
- package/framework/core/jsx-runtime.js +73 -73
- package/framework/core/rust-compiler/pkg/fusee_compiler_bg.wasm +0 -0
- package/framework/core/store.js +333 -333
- package/framework/globals.js +42 -0
- package/framework/index.js +22 -55
- package/framework/router/file-router.js +184 -184
- package/framework/server/actions.js +183 -183
- package/framework/server/generate-manifest.js +3 -3
- package/framework/types/component.d.ts +97 -86
- package/framework/types/index.d.ts +118 -117
- package/package.json +83 -80
- package/framework/core/rust-compiler/Cargo.toml +0 -31
- package/framework/core/rust-compiler/README.md +0 -69
- package/framework/core/rust-compiler/pkg/README.md +0 -69
- package/framework/core/rust-compiler/pkg/fusee_compiler.d.ts +0 -6
- package/framework/core/rust-compiler/pkg/fusee_compiler.js +0 -9
- package/framework/core/rust-compiler/pkg/fusee_compiler_bg.js +0 -137
- package/framework/core/rust-compiler/pkg/fusee_compiler_bg.wasm.d.ts +0 -11
- package/framework/core/rust-compiler/pkg/package.json +0 -18
- package/framework/core/rust-compiler/src/ast.rs +0 -274
- package/framework/core/rust-compiler/src/errors.rs +0 -204
- package/framework/core/rust-compiler/src/generator.rs +0 -583
- package/framework/core/rust-compiler/src/lexer.rs +0 -414
- package/framework/core/rust-compiler/src/lib.rs +0 -87
- package/framework/core/rust-compiler/src/main_compiler.rs +0 -165
- package/framework/core/rust-compiler/src/parser.rs +0 -516
- package/framework/core/rust-compiler/src/ssr_generator.rs +0 -688
- package/framework/core/rust-compiler/src/transformer.rs +0 -562
- package/framework/core/rust-compiler/src/utils.rs +0 -10
- package/framework/engine-go/actions.go +0 -112
- package/framework/engine-go/cmd/server/main.go +0 -233
- package/framework/engine-go/comet/comet.go +0 -41
- package/framework/engine-go/comet/fragment.go +0 -51
- package/framework/engine-go/dispatcher.go +0 -120
- package/framework/engine-go/elements.go +0 -248
- package/framework/engine-go/evalexpr/evalexpr.go +0 -733
- package/framework/engine-go/go.mod +0 -3
- package/framework/engine-go/hydration.go +0 -43
- package/framework/engine-go/load-helpers.go +0 -29
- package/framework/engine-go/manifest.go +0 -41
- package/framework/engine-go/reactive.go +0 -123
- package/framework/engine-go/render.go +0 -635
- package/framework/engine-go/reqcontext.go +0 -48
- package/framework/engine-go/router.go +0 -85
- package/framework/engine-go/rustast/rustast.go +0 -317
- package/framework/engine-go/ssr.go +0 -103
- package/framework/engine-go/static-assets.go +0 -139
- package/framework/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +0 -1
|
@@ -1,311 +1,311 @@
|
|
|
1
|
-
import { effect, batch } from './signal.js'
|
|
2
|
-
import { processDirectives } from './directives.js'
|
|
3
|
-
import { parseSlots } from './component.js'
|
|
4
|
-
import { resolveElementProperty } from './element-props.js'
|
|
5
|
-
import {
|
|
6
|
-
evaluateExpression,
|
|
7
|
-
parseInterpolation,
|
|
8
|
-
sanitizeAttr,
|
|
9
|
-
MUSTACHE_RE
|
|
10
|
-
} from './evaluator.js'
|
|
11
|
-
|
|
12
|
-
export function mountTemplate(template, container, context, components) {
|
|
13
|
-
const effects = []
|
|
14
|
-
|
|
15
|
-
// Lower component tags before the browser parses the template as HTML.
|
|
16
|
-
let processed = transformComponentTags(template, components)
|
|
17
|
-
|
|
18
|
-
for (const name of Object.keys(components)) {
|
|
19
|
-
const escapedName = escapeRegExp(name)
|
|
20
|
-
const reWithSlot = new RegExp(
|
|
21
|
-
`\\{\\{\\s*${escapedName}(\\s[^}]*?|\\s*)\\}\\}([\\s\\S]*?)\\{\\{\\s*\\/${escapedName}\\s*\\}\\}`,
|
|
22
|
-
'g'
|
|
23
|
-
)
|
|
24
|
-
const reNoSlot = new RegExp(`\\{\\{\\s*${escapedName}(\\s+.*?|\\s*)\\}\\}`, 'g')
|
|
25
|
-
|
|
26
|
-
processed = processed.replace(reWithSlot, (match, propsStr, slotContent) => {
|
|
27
|
-
const attrs = parseComponentAttrs(propsStr || '')
|
|
28
|
-
const encodedSlot = encodeURIComponent(slotContent.trim())
|
|
29
|
-
return `<div data-component="${name}"${attrs} data-slot="${encodedSlot}"></div>`
|
|
30
|
-
})
|
|
31
|
-
|
|
32
|
-
processed = processed.replace(reNoSlot, (match, propsStr) => {
|
|
33
|
-
const attrs = parseComponentAttrs(propsStr || '')
|
|
34
|
-
return `<div data-component="${name}"${attrs}></div>`
|
|
35
|
-
})
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
container.innerHTML = rewriteBindingAttrs(processed)
|
|
39
|
-
compileNode(container, context, components, effects)
|
|
40
|
-
|
|
41
|
-
return { effects }
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
const TAG_RE = /<[a-zA-Z][a-zA-Z0-9-]*(?:"[^"]*"|'[^']*'|[^'">])*>/g
|
|
45
|
-
const BINDING_IN_TAG_RE = /("[^"]*"|'[^']*')|(\s):([a-zA-Z][a-zA-Z0-9-]*)=/g
|
|
46
|
-
|
|
47
|
-
/**
|
|
48
|
-
* Rewrites every `:attr=` inside an element tag to `data-bind-attr=`, since `:` is not a
|
|
49
|
-
* valid attribute name for the HTML parser. Quoted values are skipped, so expressions
|
|
50
|
-
* containing ` :x=` or `>` are left intact. Runs after component tags are lowered, so
|
|
51
|
-
* component props keep their own `data-bind-prop-` encoding.
|
|
52
|
-
*/
|
|
53
|
-
function rewriteBindingAttrs(template) {
|
|
54
|
-
return template.replace(TAG_RE, tag => tag.replace(BINDING_IN_TAG_RE, (match, quoted, space, name) =>
|
|
55
|
-
quoted ? quoted : `${space}data-bind-${name}=`
|
|
56
|
-
))
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
function transformComponentTags(template, components) {
|
|
60
|
-
let processed = template
|
|
61
|
-
const componentNames = Object.keys(components).sort((a, b) => b.length - a.length)
|
|
62
|
-
|
|
63
|
-
for (const name of componentNames) {
|
|
64
|
-
const escapedName = escapeRegExp(name)
|
|
65
|
-
const withChildren = new RegExp(
|
|
66
|
-
`<${escapedName}(\\s[^>]*)?>([\\s\\S]*?)<\\/${escapedName}\\s*>`,
|
|
67
|
-
'gi'
|
|
68
|
-
)
|
|
69
|
-
const selfClosing = new RegExp(
|
|
70
|
-
`<${escapedName}(\\s[^>]*?)?\\s*/>`,
|
|
71
|
-
'gi'
|
|
72
|
-
)
|
|
73
|
-
|
|
74
|
-
processed = processed.replace(withChildren, (match, propsStr, slotContent) => {
|
|
75
|
-
const attrs = parseComponentAttrs(propsStr || '')
|
|
76
|
-
const encodedSlot = encodeURIComponent(slotContent.trim())
|
|
77
|
-
return `<div data-component="${name}"${attrs} data-slot="${encodedSlot}"></div>`
|
|
78
|
-
})
|
|
79
|
-
|
|
80
|
-
processed = processed.replace(selfClosing, (match, propsStr) => {
|
|
81
|
-
const attrs = parseComponentAttrs(propsStr || '')
|
|
82
|
-
return `<div data-component="${name}"${attrs}></div>`
|
|
83
|
-
})
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
return processed
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
function escapeRegExp(value) {
|
|
90
|
-
return value.replace(/[.*+?^${}()|[\\]\\]/g, '\\$&')
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
function parseComponentAttrs(propsStr) {
|
|
94
|
-
let attrs = ''
|
|
95
|
-
if (!propsStr || !propsStr.trim()) return attrs
|
|
96
|
-
|
|
97
|
-
const attrRegex = /(@?:?[a-zA-Z0-9-]+)="([^"]*)"/g
|
|
98
|
-
let attrMatch
|
|
99
|
-
while ((attrMatch = attrRegex.exec(propsStr)) !== null) {
|
|
100
|
-
const attrName = attrMatch[1]
|
|
101
|
-
const attrValue = attrMatch[2]
|
|
102
|
-
|
|
103
|
-
if (attrName.startsWith('@')) {
|
|
104
|
-
attrs += ` data-on-${attrName.slice(1).toLowerCase()}="${attrValue}"`
|
|
105
|
-
} else if (attrName.startsWith(':')) {
|
|
106
|
-
attrs += ` data-bind-prop-${attrName.slice(1).toLowerCase()}="${attrValue}"`
|
|
107
|
-
} else {
|
|
108
|
-
attrs += ` data-prop-${attrName.toLowerCase()}="${attrValue}"`
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
return attrs
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
export function compileNode(node, context, components, effects) {
|
|
115
|
-
if (node.nodeType === 1 && node.hasAttribute('f-once')) {
|
|
116
|
-
node.removeAttribute('f-once')
|
|
117
|
-
const temporaryEffects = []
|
|
118
|
-
|
|
119
|
-
processDirectives(node, context, components, temporaryEffects)
|
|
120
|
-
processBindingAttrs(node, context, temporaryEffects)
|
|
121
|
-
processMustaches(node, context, temporaryEffects)
|
|
122
|
-
bindComponents(node, components, context, temporaryEffects)
|
|
123
|
-
|
|
124
|
-
let child = node.firstChild
|
|
125
|
-
while (child) {
|
|
126
|
-
compileOnce(child, context, components, temporaryEffects)
|
|
127
|
-
child = child.nextSibling
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
for (let i = temporaryEffects.length - 1; i >= 0; i--) {
|
|
131
|
-
const cleanup = temporaryEffects[i]
|
|
132
|
-
if (typeof cleanup === 'function') cleanup()
|
|
133
|
-
}
|
|
134
|
-
return
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
if (node.nodeType === 1) {
|
|
138
|
-
if (processDirectives(node, context, components, effects)) return
|
|
139
|
-
processBindingAttrs(node, context, effects)
|
|
140
|
-
bindComponents(node, components, context, effects)
|
|
141
|
-
} else if (node.nodeType === 3) {
|
|
142
|
-
processMustaches(node, context, effects)
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
let child = node.firstChild
|
|
146
|
-
while (child) {
|
|
147
|
-
compileNode(child, context, components, effects)
|
|
148
|
-
child = child.nextSibling
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
function compileOnce(node, context, components, effects) {
|
|
153
|
-
if (node.nodeType === 1) {
|
|
154
|
-
if (processDirectives(node, context, components, effects)) return
|
|
155
|
-
processBindingAttrs(node, context, effects)
|
|
156
|
-
bindComponents(node, components, context, effects)
|
|
157
|
-
} else if (node.nodeType === 3) {
|
|
158
|
-
processMustaches(node, context, effects)
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
let child = node.firstChild
|
|
162
|
-
while (child) {
|
|
163
|
-
compileOnce(child, context, components, effects)
|
|
164
|
-
child = child.nextSibling
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
function processMustaches(node, context, effects) {
|
|
169
|
-
if (node.nodeType !== 3) return
|
|
170
|
-
const text = node.textContent
|
|
171
|
-
if (!MUSTACHE_RE.test(text)) return
|
|
172
|
-
|
|
173
|
-
const parts = parseInterpolation(text)
|
|
174
|
-
const fragment = document.createDocumentFragment()
|
|
175
|
-
|
|
176
|
-
for (const part of parts) {
|
|
177
|
-
if (part.type === 'static') {
|
|
178
|
-
fragment.appendChild(document.createTextNode(part.value))
|
|
179
|
-
} else {
|
|
180
|
-
const reactiveNode = document.createTextNode('')
|
|
181
|
-
fragment.appendChild(reactiveNode)
|
|
182
|
-
const key = part.key
|
|
183
|
-
const e = effect(() => {
|
|
184
|
-
const resolved = evaluateExpression(key, context)
|
|
185
|
-
reactiveNode.textContent = String(resolved ?? '')
|
|
186
|
-
})
|
|
187
|
-
effects.push(e)
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
node.parentNode.replaceChild(fragment, node)
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
function processBindingAttrs(el, context, effects) {
|
|
194
|
-
if (el.nodeType !== 1) return
|
|
195
|
-
for (const attr of [...el.attributes]) {
|
|
196
|
-
if (attr.name.startsWith('data-bind-') && !attr.name.startsWith('data-bind-prop-')) {
|
|
197
|
-
const realAttr = attr.name.slice(10)
|
|
198
|
-
const expr = attr.value
|
|
199
|
-
el.removeAttribute(attr.name)
|
|
200
|
-
|
|
201
|
-
const e = effect(() => {
|
|
202
|
-
let resolved = evaluateExpression(expr, context)
|
|
203
|
-
const elementProp = realAttr !== 'class' && realAttr !== 'style'
|
|
204
|
-
? resolveElementProperty(el, realAttr, resolved)
|
|
205
|
-
: null
|
|
206
|
-
if (elementProp) {
|
|
207
|
-
el[elementProp] = resolved
|
|
208
|
-
} else if (realAttr === 'class') {
|
|
209
|
-
if (!el._staticClass) el._staticClass = el.getAttribute('class') || ''
|
|
210
|
-
let dynamicClass = ''
|
|
211
|
-
if (Array.isArray(resolved)) dynamicClass = resolved.filter(Boolean).join(' ')
|
|
212
|
-
else if (resolved !== null && typeof resolved === 'object') {
|
|
213
|
-
dynamicClass = Object.entries(resolved).filter(([_, v]) => !!v).map(([k]) => k).join(' ')
|
|
214
|
-
} else dynamicClass = String(resolved ?? '')
|
|
215
|
-
el.className = (el._staticClass + ' ' + dynamicClass).trim()
|
|
216
|
-
} else if (realAttr === 'style') {
|
|
217
|
-
if (resolved !== null && typeof resolved === 'object') {
|
|
218
|
-
for (const k in resolved) el.style[k] = resolved[k]
|
|
219
|
-
} else el.style.cssText = String(resolved ?? '')
|
|
220
|
-
} else {
|
|
221
|
-
if (typeof resolved === 'boolean') {
|
|
222
|
-
resolved ? el.setAttribute(realAttr, '') : el.removeAttribute(realAttr)
|
|
223
|
-
} else el.setAttribute(realAttr, sanitizeAttr(realAttr, String(resolved)))
|
|
224
|
-
}
|
|
225
|
-
})
|
|
226
|
-
effects.push(e)
|
|
227
|
-
continue
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
if (MUSTACHE_RE.test(attr.value)) {
|
|
231
|
-
const attrName = attr.name
|
|
232
|
-
const parts = parseInterpolation(attr.value)
|
|
233
|
-
el.removeAttribute(attrName)
|
|
234
|
-
const e = effect(() => {
|
|
235
|
-
let result = ''
|
|
236
|
-
for (const part of parts) {
|
|
237
|
-
result += part.type === 'static' ? part.value : String(evaluateExpression(part.key, context) ?? '')
|
|
238
|
-
}
|
|
239
|
-
el.setAttribute(attrName, sanitizeAttr(attrName, result))
|
|
240
|
-
})
|
|
241
|
-
effects.push(e)
|
|
242
|
-
}
|
|
243
|
-
}
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
function bindComponents(el, components, context, effects) {
|
|
247
|
-
if (el.nodeType !== 1) return
|
|
248
|
-
const name = el.getAttribute('data-component')
|
|
249
|
-
if (!name) return
|
|
250
|
-
|
|
251
|
-
el.removeAttribute('data-component')
|
|
252
|
-
|
|
253
|
-
const ComponentFn = components[name]
|
|
254
|
-
if (!ComponentFn) return
|
|
255
|
-
|
|
256
|
-
const props = {}
|
|
257
|
-
const listeners = {}
|
|
258
|
-
|
|
259
|
-
for (const attr of [...el.attributes]) {
|
|
260
|
-
const attrName = attr.name.toLowerCase()
|
|
261
|
-
|
|
262
|
-
if (attrName.startsWith('data-prop-')) {
|
|
263
|
-
props[attrName.slice(10)] = attr.value
|
|
264
|
-
|
|
265
|
-
} else if (attrName.startsWith('data-bind-prop-')) {
|
|
266
|
-
const propName = attrName.slice(15)
|
|
267
|
-
const expression = attr.value.trim()
|
|
268
|
-
|
|
269
|
-
if (expression in context) {
|
|
270
|
-
Object.defineProperty(props, propName, {
|
|
271
|
-
get() {
|
|
272
|
-
const val = context[expression]
|
|
273
|
-
return typeof val === 'function' && val.isSignal ? val() : val
|
|
274
|
-
},
|
|
275
|
-
enumerable: true,
|
|
276
|
-
configurable: true
|
|
277
|
-
})
|
|
278
|
-
} else {
|
|
279
|
-
Object.defineProperty(props, propName, {
|
|
280
|
-
get() { return evaluateExpression(expression, context) },
|
|
281
|
-
enumerable: true,
|
|
282
|
-
configurable: true
|
|
283
|
-
})
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
} else if (attrName.startsWith('data-on-')) {
|
|
287
|
-
const eventName = attrName.slice(8)
|
|
288
|
-
const handlerExpr = attr.value.trim()
|
|
289
|
-
|
|
290
|
-
listeners[eventName] = (...args) => {
|
|
291
|
-
const handler = context[handlerExpr]
|
|
292
|
-
if (typeof handler === 'function') {
|
|
293
|
-
handler(...args)
|
|
294
|
-
} else {
|
|
295
|
-
evaluateExpression(handlerExpr, context)
|
|
296
|
-
}
|
|
297
|
-
}
|
|
298
|
-
}
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
const rawSlot = el.getAttribute('data-slot')
|
|
302
|
-
const slots = rawSlot ? parseSlots(decodeURIComponent(rawSlot)) : {}
|
|
303
|
-
el.removeAttribute('data-slot')
|
|
304
|
-
|
|
305
|
-
const childComponent = ComponentFn(props, { listeners, slots, parent: context._instance })
|
|
306
|
-
childComponent.render(el)
|
|
307
|
-
|
|
308
|
-
if (effects) {
|
|
309
|
-
effects.push(() => childComponent.unmount())
|
|
310
|
-
}
|
|
1
|
+
import { effect, batch } from './signal.js'
|
|
2
|
+
import { processDirectives } from './directives.js'
|
|
3
|
+
import { parseSlots } from './component-core.js'
|
|
4
|
+
import { resolveElementProperty } from './element-props.js'
|
|
5
|
+
import {
|
|
6
|
+
evaluateExpression,
|
|
7
|
+
parseInterpolation,
|
|
8
|
+
sanitizeAttr,
|
|
9
|
+
MUSTACHE_RE
|
|
10
|
+
} from './evaluator.js'
|
|
11
|
+
|
|
12
|
+
export function mountTemplate(template, container, context, components) {
|
|
13
|
+
const effects = []
|
|
14
|
+
|
|
15
|
+
// Lower component tags before the browser parses the template as HTML.
|
|
16
|
+
let processed = transformComponentTags(template, components)
|
|
17
|
+
|
|
18
|
+
for (const name of Object.keys(components)) {
|
|
19
|
+
const escapedName = escapeRegExp(name)
|
|
20
|
+
const reWithSlot = new RegExp(
|
|
21
|
+
`\\{\\{\\s*${escapedName}(\\s[^}]*?|\\s*)\\}\\}([\\s\\S]*?)\\{\\{\\s*\\/${escapedName}\\s*\\}\\}`,
|
|
22
|
+
'g'
|
|
23
|
+
)
|
|
24
|
+
const reNoSlot = new RegExp(`\\{\\{\\s*${escapedName}(\\s+.*?|\\s*)\\}\\}`, 'g')
|
|
25
|
+
|
|
26
|
+
processed = processed.replace(reWithSlot, (match, propsStr, slotContent) => {
|
|
27
|
+
const attrs = parseComponentAttrs(propsStr || '')
|
|
28
|
+
const encodedSlot = encodeURIComponent(slotContent.trim())
|
|
29
|
+
return `<div data-component="${name}"${attrs} data-slot="${encodedSlot}"></div>`
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
processed = processed.replace(reNoSlot, (match, propsStr) => {
|
|
33
|
+
const attrs = parseComponentAttrs(propsStr || '')
|
|
34
|
+
return `<div data-component="${name}"${attrs}></div>`
|
|
35
|
+
})
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
container.innerHTML = rewriteBindingAttrs(processed)
|
|
39
|
+
compileNode(container, context, components, effects)
|
|
40
|
+
|
|
41
|
+
return { effects }
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const TAG_RE = /<[a-zA-Z][a-zA-Z0-9-]*(?:"[^"]*"|'[^']*'|[^'">])*>/g
|
|
45
|
+
const BINDING_IN_TAG_RE = /("[^"]*"|'[^']*')|(\s):([a-zA-Z][a-zA-Z0-9-]*)=/g
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Rewrites every `:attr=` inside an element tag to `data-bind-attr=`, since `:` is not a
|
|
49
|
+
* valid attribute name for the HTML parser. Quoted values are skipped, so expressions
|
|
50
|
+
* containing ` :x=` or `>` are left intact. Runs after component tags are lowered, so
|
|
51
|
+
* component props keep their own `data-bind-prop-` encoding.
|
|
52
|
+
*/
|
|
53
|
+
function rewriteBindingAttrs(template) {
|
|
54
|
+
return template.replace(TAG_RE, tag => tag.replace(BINDING_IN_TAG_RE, (match, quoted, space, name) =>
|
|
55
|
+
quoted ? quoted : `${space}data-bind-${name}=`
|
|
56
|
+
))
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function transformComponentTags(template, components) {
|
|
60
|
+
let processed = template
|
|
61
|
+
const componentNames = Object.keys(components).sort((a, b) => b.length - a.length)
|
|
62
|
+
|
|
63
|
+
for (const name of componentNames) {
|
|
64
|
+
const escapedName = escapeRegExp(name)
|
|
65
|
+
const withChildren = new RegExp(
|
|
66
|
+
`<${escapedName}(\\s[^>]*)?>([\\s\\S]*?)<\\/${escapedName}\\s*>`,
|
|
67
|
+
'gi'
|
|
68
|
+
)
|
|
69
|
+
const selfClosing = new RegExp(
|
|
70
|
+
`<${escapedName}(\\s[^>]*?)?\\s*/>`,
|
|
71
|
+
'gi'
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
processed = processed.replace(withChildren, (match, propsStr, slotContent) => {
|
|
75
|
+
const attrs = parseComponentAttrs(propsStr || '')
|
|
76
|
+
const encodedSlot = encodeURIComponent(slotContent.trim())
|
|
77
|
+
return `<div data-component="${name}"${attrs} data-slot="${encodedSlot}"></div>`
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
processed = processed.replace(selfClosing, (match, propsStr) => {
|
|
81
|
+
const attrs = parseComponentAttrs(propsStr || '')
|
|
82
|
+
return `<div data-component="${name}"${attrs}></div>`
|
|
83
|
+
})
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return processed
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function escapeRegExp(value) {
|
|
90
|
+
return value.replace(/[.*+?^${}()|[\\]\\]/g, '\\$&')
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function parseComponentAttrs(propsStr) {
|
|
94
|
+
let attrs = ''
|
|
95
|
+
if (!propsStr || !propsStr.trim()) return attrs
|
|
96
|
+
|
|
97
|
+
const attrRegex = /(@?:?[a-zA-Z0-9-]+)="([^"]*)"/g
|
|
98
|
+
let attrMatch
|
|
99
|
+
while ((attrMatch = attrRegex.exec(propsStr)) !== null) {
|
|
100
|
+
const attrName = attrMatch[1]
|
|
101
|
+
const attrValue = attrMatch[2]
|
|
102
|
+
|
|
103
|
+
if (attrName.startsWith('@')) {
|
|
104
|
+
attrs += ` data-on-${attrName.slice(1).toLowerCase()}="${attrValue}"`
|
|
105
|
+
} else if (attrName.startsWith(':')) {
|
|
106
|
+
attrs += ` data-bind-prop-${attrName.slice(1).toLowerCase()}="${attrValue}"`
|
|
107
|
+
} else {
|
|
108
|
+
attrs += ` data-prop-${attrName.toLowerCase()}="${attrValue}"`
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
return attrs
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export function compileNode(node, context, components, effects) {
|
|
115
|
+
if (node.nodeType === 1 && node.hasAttribute('f-once')) {
|
|
116
|
+
node.removeAttribute('f-once')
|
|
117
|
+
const temporaryEffects = []
|
|
118
|
+
|
|
119
|
+
processDirectives(node, context, components, temporaryEffects)
|
|
120
|
+
processBindingAttrs(node, context, temporaryEffects)
|
|
121
|
+
processMustaches(node, context, temporaryEffects)
|
|
122
|
+
bindComponents(node, components, context, temporaryEffects)
|
|
123
|
+
|
|
124
|
+
let child = node.firstChild
|
|
125
|
+
while (child) {
|
|
126
|
+
compileOnce(child, context, components, temporaryEffects)
|
|
127
|
+
child = child.nextSibling
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
for (let i = temporaryEffects.length - 1; i >= 0; i--) {
|
|
131
|
+
const cleanup = temporaryEffects[i]
|
|
132
|
+
if (typeof cleanup === 'function') cleanup()
|
|
133
|
+
}
|
|
134
|
+
return
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (node.nodeType === 1) {
|
|
138
|
+
if (processDirectives(node, context, components, effects)) return
|
|
139
|
+
processBindingAttrs(node, context, effects)
|
|
140
|
+
bindComponents(node, components, context, effects)
|
|
141
|
+
} else if (node.nodeType === 3) {
|
|
142
|
+
processMustaches(node, context, effects)
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
let child = node.firstChild
|
|
146
|
+
while (child) {
|
|
147
|
+
compileNode(child, context, components, effects)
|
|
148
|
+
child = child.nextSibling
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function compileOnce(node, context, components, effects) {
|
|
153
|
+
if (node.nodeType === 1) {
|
|
154
|
+
if (processDirectives(node, context, components, effects)) return
|
|
155
|
+
processBindingAttrs(node, context, effects)
|
|
156
|
+
bindComponents(node, components, context, effects)
|
|
157
|
+
} else if (node.nodeType === 3) {
|
|
158
|
+
processMustaches(node, context, effects)
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
let child = node.firstChild
|
|
162
|
+
while (child) {
|
|
163
|
+
compileOnce(child, context, components, effects)
|
|
164
|
+
child = child.nextSibling
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function processMustaches(node, context, effects) {
|
|
169
|
+
if (node.nodeType !== 3) return
|
|
170
|
+
const text = node.textContent
|
|
171
|
+
if (!MUSTACHE_RE.test(text)) return
|
|
172
|
+
|
|
173
|
+
const parts = parseInterpolation(text)
|
|
174
|
+
const fragment = document.createDocumentFragment()
|
|
175
|
+
|
|
176
|
+
for (const part of parts) {
|
|
177
|
+
if (part.type === 'static') {
|
|
178
|
+
fragment.appendChild(document.createTextNode(part.value))
|
|
179
|
+
} else {
|
|
180
|
+
const reactiveNode = document.createTextNode('')
|
|
181
|
+
fragment.appendChild(reactiveNode)
|
|
182
|
+
const key = part.key
|
|
183
|
+
const e = effect(() => {
|
|
184
|
+
const resolved = evaluateExpression(key, context)
|
|
185
|
+
reactiveNode.textContent = String(resolved ?? '')
|
|
186
|
+
})
|
|
187
|
+
effects.push(e)
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
node.parentNode.replaceChild(fragment, node)
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function processBindingAttrs(el, context, effects) {
|
|
194
|
+
if (el.nodeType !== 1) return
|
|
195
|
+
for (const attr of [...el.attributes]) {
|
|
196
|
+
if (attr.name.startsWith('data-bind-') && !attr.name.startsWith('data-bind-prop-')) {
|
|
197
|
+
const realAttr = attr.name.slice(10)
|
|
198
|
+
const expr = attr.value
|
|
199
|
+
el.removeAttribute(attr.name)
|
|
200
|
+
|
|
201
|
+
const e = effect(() => {
|
|
202
|
+
let resolved = evaluateExpression(expr, context)
|
|
203
|
+
const elementProp = realAttr !== 'class' && realAttr !== 'style'
|
|
204
|
+
? resolveElementProperty(el, realAttr, resolved)
|
|
205
|
+
: null
|
|
206
|
+
if (elementProp) {
|
|
207
|
+
el[elementProp] = resolved
|
|
208
|
+
} else if (realAttr === 'class') {
|
|
209
|
+
if (!el._staticClass) el._staticClass = el.getAttribute('class') || ''
|
|
210
|
+
let dynamicClass = ''
|
|
211
|
+
if (Array.isArray(resolved)) dynamicClass = resolved.filter(Boolean).join(' ')
|
|
212
|
+
else if (resolved !== null && typeof resolved === 'object') {
|
|
213
|
+
dynamicClass = Object.entries(resolved).filter(([_, v]) => !!v).map(([k]) => k).join(' ')
|
|
214
|
+
} else dynamicClass = String(resolved ?? '')
|
|
215
|
+
el.className = (el._staticClass + ' ' + dynamicClass).trim()
|
|
216
|
+
} else if (realAttr === 'style') {
|
|
217
|
+
if (resolved !== null && typeof resolved === 'object') {
|
|
218
|
+
for (const k in resolved) el.style[k] = resolved[k]
|
|
219
|
+
} else el.style.cssText = String(resolved ?? '')
|
|
220
|
+
} else {
|
|
221
|
+
if (typeof resolved === 'boolean') {
|
|
222
|
+
resolved ? el.setAttribute(realAttr, '') : el.removeAttribute(realAttr)
|
|
223
|
+
} else el.setAttribute(realAttr, sanitizeAttr(realAttr, String(resolved)))
|
|
224
|
+
}
|
|
225
|
+
})
|
|
226
|
+
effects.push(e)
|
|
227
|
+
continue
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
if (MUSTACHE_RE.test(attr.value)) {
|
|
231
|
+
const attrName = attr.name
|
|
232
|
+
const parts = parseInterpolation(attr.value)
|
|
233
|
+
el.removeAttribute(attrName)
|
|
234
|
+
const e = effect(() => {
|
|
235
|
+
let result = ''
|
|
236
|
+
for (const part of parts) {
|
|
237
|
+
result += part.type === 'static' ? part.value : String(evaluateExpression(part.key, context) ?? '')
|
|
238
|
+
}
|
|
239
|
+
el.setAttribute(attrName, sanitizeAttr(attrName, result))
|
|
240
|
+
})
|
|
241
|
+
effects.push(e)
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
function bindComponents(el, components, context, effects) {
|
|
247
|
+
if (el.nodeType !== 1) return
|
|
248
|
+
const name = el.getAttribute('data-component')
|
|
249
|
+
if (!name) return
|
|
250
|
+
|
|
251
|
+
el.removeAttribute('data-component')
|
|
252
|
+
|
|
253
|
+
const ComponentFn = components[name]
|
|
254
|
+
if (!ComponentFn) return
|
|
255
|
+
|
|
256
|
+
const props = {}
|
|
257
|
+
const listeners = {}
|
|
258
|
+
|
|
259
|
+
for (const attr of [...el.attributes]) {
|
|
260
|
+
const attrName = attr.name.toLowerCase()
|
|
261
|
+
|
|
262
|
+
if (attrName.startsWith('data-prop-')) {
|
|
263
|
+
props[attrName.slice(10)] = attr.value
|
|
264
|
+
|
|
265
|
+
} else if (attrName.startsWith('data-bind-prop-')) {
|
|
266
|
+
const propName = attrName.slice(15)
|
|
267
|
+
const expression = attr.value.trim()
|
|
268
|
+
|
|
269
|
+
if (expression in context) {
|
|
270
|
+
Object.defineProperty(props, propName, {
|
|
271
|
+
get() {
|
|
272
|
+
const val = context[expression]
|
|
273
|
+
return typeof val === 'function' && val.isSignal ? val() : val
|
|
274
|
+
},
|
|
275
|
+
enumerable: true,
|
|
276
|
+
configurable: true
|
|
277
|
+
})
|
|
278
|
+
} else {
|
|
279
|
+
Object.defineProperty(props, propName, {
|
|
280
|
+
get() { return evaluateExpression(expression, context) },
|
|
281
|
+
enumerable: true,
|
|
282
|
+
configurable: true
|
|
283
|
+
})
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
} else if (attrName.startsWith('data-on-')) {
|
|
287
|
+
const eventName = attrName.slice(8)
|
|
288
|
+
const handlerExpr = attr.value.trim()
|
|
289
|
+
|
|
290
|
+
listeners[eventName] = (...args) => {
|
|
291
|
+
const handler = context[handlerExpr]
|
|
292
|
+
if (typeof handler === 'function') {
|
|
293
|
+
handler(...args)
|
|
294
|
+
} else {
|
|
295
|
+
evaluateExpression(handlerExpr, context)
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
const rawSlot = el.getAttribute('data-slot')
|
|
302
|
+
const slots = rawSlot ? parseSlots(decodeURIComponent(rawSlot)) : {}
|
|
303
|
+
el.removeAttribute('data-slot')
|
|
304
|
+
|
|
305
|
+
const childComponent = ComponentFn(props, { listeners, slots, parent: context._instance })
|
|
306
|
+
childComponent.render(el)
|
|
307
|
+
|
|
308
|
+
if (effects) {
|
|
309
|
+
effects.push(() => childComponent.unmount())
|
|
310
|
+
}
|
|
311
311
|
}
|