fusee-framework 1.6.0 → 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.
- package/README.md +29 -13
- package/app/benchmark/benchmark.js +633 -0
- package/app/benchmark/index.html +102 -0
- package/app/benchmark/styles.css +249 -0
- package/app/components/Counter.js +17 -7
- package/app/components/CounterSection.js +3 -4
- package/app/main.js +29 -62
- package/app/main.ts +72 -63
- package/app/pages/404.js +16 -0
- package/app/pages/[...all].js +35 -0
- package/app/pages/[...all].ts +35 -0
- package/app/pages/_layout.js +26 -0
- package/app/pages/_layout.ts +26 -0
- package/app/pages/about.js +23 -0
- package/app/pages/about.ts +29 -0
- package/app/pages/composables.js +158 -0
- package/app/pages/composables.ts +158 -0
- package/app/pages/dynamic.js +40 -0
- package/app/pages/dynamic.ts +40 -0
- package/app/pages/index.js +117 -0
- package/app/pages/index.ts +78 -0
- package/app/pages/login.ts +21 -0
- package/app/pages/objects/[id].js +56 -0
- package/app/pages/objects/[id].ts +56 -0
- package/app/pages/posts/[category]/[slug].js +56 -0
- package/app/pages/posts/[category]/[slug].ts +56 -0
- package/app/pages/router-example.js +56 -0
- package/app/pages/router-example.ts +56 -0
- package/app/pages/test-for.js +65 -0
- package/app/pages/test-for.ts +80 -0
- package/app/pages/users/[id].js +24 -0
- package/app/pages/users/[id].ts +46 -0
- package/app/pages/users/index.js +22 -0
- package/app/pages/users/index.ts +29 -0
- package/app/pages/users.js +43 -0
- package/app/pages/users.ts +78 -0
- package/app/routes/About.js +1 -1
- package/app/routes/About.ts +1 -1
- package/app/routes/AuthRoutes.ts +41 -0
- package/app/routes/Home.js +9 -0
- package/app/routes/NotFound.js +16 -0
- package/app/routes/NotFound.ts +16 -0
- package/app/routes/RouterExample.js +56 -0
- package/app/routes/RouterExample.ts +56 -0
- package/app/routes/WildcardDemo.js +35 -0
- package/app/routes/WildcardDemo.ts +35 -0
- package/auto-imports.d.ts +1 -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/bin/index.js +38 -0
- package/create-fusee-go/cmd/generate.go +72 -0
- package/create-fusee-go/cmd/init.go +38 -0
- package/create-fusee-go/cmd/root.go +121 -0
- package/create-fusee-go/go.mod +10 -0
- package/create-fusee-go/go.sum +10 -0
- package/create-fusee-go/internal/assets/assets.go +78 -0
- package/create-fusee-go/internal/assets/embed/framework/auto-import-preset.js +9 -0
- package/create-fusee-go/internal/assets/embed/framework/core/compiler.js +253 -0
- package/create-fusee-go/internal/assets/embed/framework/core/component.js +266 -0
- package/create-fusee-go/internal/assets/embed/framework/core/composable.js +27 -0
- package/create-fusee-go/internal/assets/embed/framework/core/directives.js +875 -0
- package/create-fusee-go/internal/assets/embed/framework/core/evaluator.js +153 -0
- package/create-fusee-go/internal/assets/embed/framework/core/event-delegation.js +191 -0
- package/create-fusee-go/internal/assets/embed/framework/core/signal.js +412 -0
- package/create-fusee-go/internal/assets/embed/framework/core/store.js +81 -0
- package/create-fusee-go/internal/assets/embed/framework/index.d.ts +3 -0
- package/create-fusee-go/internal/assets/embed/framework/index.js +10 -0
- package/create-fusee-go/internal/assets/embed/framework/router/file-router.js +177 -0
- package/create-fusee-go/internal/assets/embed/framework/router/router.js +462 -0
- package/create-fusee-go/internal/assets/embed/framework/types/compiler.d.ts +15 -0
- package/create-fusee-go/internal/assets/embed/framework/types/component.d.ts +88 -0
- package/create-fusee-go/internal/assets/embed/framework/types/composable.d.ts +14 -0
- package/create-fusee-go/internal/assets/embed/framework/types/directives.d.ts +55 -0
- package/create-fusee-go/internal/assets/embed/framework/types/evaluator.d.ts +13 -0
- package/create-fusee-go/internal/assets/embed/framework/types/event-delegation.d.ts +32 -0
- package/create-fusee-go/internal/assets/embed/framework/types/index.d.ts +22 -0
- package/create-fusee-go/internal/assets/embed/framework/types/router.d.ts +32 -0
- package/create-fusee-go/internal/assets/embed/framework/types/signal.d.ts +79 -0
- package/create-fusee-go/internal/assets/embed/framework/types/store.d.ts +8 -0
- package/create-fusee-go/internal/assets/templates/Counter.tmpl +26 -0
- package/create-fusee-go/internal/assets/templates/component.tmpl +11 -0
- package/create-fusee-go/internal/assets/templates/components/Loading.tmpl +24 -0
- package/create-fusee-go/internal/assets/templates/index.html.tmpl +15 -0
- package/create-fusee-go/internal/assets/templates/main.tmpl +14 -0
- package/create-fusee-go/internal/assets/templates/package.json.tmpl +14 -0
- package/create-fusee-go/internal/assets/templates/page.tmpl +12 -0
- package/create-fusee-go/internal/assets/templates/pages/about.tmpl +20 -0
- package/create-fusee-go/internal/assets/templates/pages/index.tmpl +22 -0
- package/create-fusee-go/internal/assets/templates/pages/layout.tmpl +18 -0
- package/create-fusee-go/internal/assets/templates/tsconfig.json.tmpl +14 -0
- package/create-fusee-go/internal/assets/templates/vite.config.js.tmpl +18 -0
- package/create-fusee-go/main.go +7 -0
- package/framework/auto-import-preset.js +1 -1
- package/framework/core/compiler.js +252 -249
- package/framework/core/component.js +265 -260
- package/framework/core/directives.js +875 -786
- package/framework/core/evaluator.js +153 -70
- package/framework/core/event-delegation.js +33 -14
- package/framework/core/signal.js +412 -403
- package/framework/index.js +10 -11
- package/framework/router/file-router.js +177 -0
- package/framework/router/router.js +462 -80
- package/framework/types/event-delegation.d.ts +0 -58
- package/framework/types/router.d.ts +32 -15
- package/framework/types/signal.d.ts +1 -0
- package/index.html +638 -626
- package/package.json +5 -3
- package/tsconfig.json +1 -0
- /package/{framework → create-fusee-go/internal/assets/embed/framework}/bin/create-fusee.js +0 -0
|
@@ -0,0 +1,462 @@
|
|
|
1
|
+
import { signal } from '../core/signal.js'
|
|
2
|
+
|
|
3
|
+
export const currentRoute = signal('/')
|
|
4
|
+
export const routeParams = signal({})
|
|
5
|
+
export const matchedRoutes = signal([])
|
|
6
|
+
|
|
7
|
+
let _routes = []
|
|
8
|
+
let _rootOutlet = null
|
|
9
|
+
let _clickHandler = null
|
|
10
|
+
let _activeChain = []
|
|
11
|
+
let _routeCache = new Map()
|
|
12
|
+
let _cacheMaxSize = 100
|
|
13
|
+
let _routerViewTimeout = 10000
|
|
14
|
+
|
|
15
|
+
function _getPath() {
|
|
16
|
+
return window.location.pathname || '/'
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function _getRoutePaths(routePath) {
|
|
20
|
+
return Array.isArray(routePath) ? routePath : [routePath]
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function _updateRoute() {
|
|
24
|
+
currentRoute(_getPath())
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function _matchSegments(routePath, urlSegments) {
|
|
28
|
+
const cleanPath = routePath.startsWith('/') ? routePath.slice(1) : routePath
|
|
29
|
+
|
|
30
|
+
if (cleanPath === '') {
|
|
31
|
+
return { matched: true, consumed: 0, params: {} }
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (cleanPath === '*') {
|
|
35
|
+
return { matched: true, consumed: urlSegments.length, params: {} }
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const routeSegs = cleanPath.split('/').filter(Boolean)
|
|
39
|
+
const hasWildcard = routeSegs[routeSegs.length - 1] === '*'
|
|
40
|
+
const checkSegs = hasWildcard ? routeSegs.slice(0, -1) : routeSegs
|
|
41
|
+
|
|
42
|
+
if (hasWildcard) {
|
|
43
|
+
if (urlSegments.length < checkSegs.length) return null
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (urlSegments.length < checkSegs.length) return null
|
|
47
|
+
|
|
48
|
+
const params = {}
|
|
49
|
+
for (let i = 0; i < checkSegs.length; i++) {
|
|
50
|
+
const rSeg = checkSegs[i]
|
|
51
|
+
const uSeg = urlSegments[i]
|
|
52
|
+
if (rSeg.startsWith(':')) {
|
|
53
|
+
params[rSeg.slice(1)] = uSeg
|
|
54
|
+
} else if (rSeg !== uSeg) {
|
|
55
|
+
return null
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const consumed = hasWildcard ? urlSegments.length : checkSegs.length
|
|
60
|
+
return { matched: true, consumed, params }
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function _matchRouteTree(routes, urlSegments) {
|
|
64
|
+
for (const route of routes) {
|
|
65
|
+
const paths = _getRoutePaths(route.path)
|
|
66
|
+
for (const path of paths) {
|
|
67
|
+
const result = _matchSegments(path, urlSegments)
|
|
68
|
+
if (!result) continue
|
|
69
|
+
|
|
70
|
+
const remaining = urlSegments.slice(result.consumed)
|
|
71
|
+
|
|
72
|
+
if (route.children && route.children.length > 0) {
|
|
73
|
+
const childChain = _matchRouteTree(route.children, remaining)
|
|
74
|
+
if (childChain) {
|
|
75
|
+
return [
|
|
76
|
+
{ route, params: result.params, matchedSegments: urlSegments.slice(0, result.consumed), matchedPath: path },
|
|
77
|
+
...childChain
|
|
78
|
+
]
|
|
79
|
+
}
|
|
80
|
+
continue
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (remaining.length === 0 || path === '*' || path.endsWith('/*')) {
|
|
84
|
+
return [{ route, params: result.params, matchedSegments: urlSegments.slice(0, result.consumed), matchedPath: path }]
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return null
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function _matchSingleFlat(routePath, actualPath) {
|
|
93
|
+
if (routePath === '*') return true
|
|
94
|
+
|
|
95
|
+
const routeParts = routePath.split('/').filter(Boolean)
|
|
96
|
+
const actualParts = actualPath.split('/').filter(Boolean)
|
|
97
|
+
|
|
98
|
+
const hasWildcard = routeParts.length > 0 && routeParts[routeParts.length - 1] === '*'
|
|
99
|
+
|
|
100
|
+
if (hasWildcard) {
|
|
101
|
+
const baseParts = routeParts.slice(0, -1)
|
|
102
|
+
if (actualParts.length < baseParts.length) return false
|
|
103
|
+
return baseParts.every((part, i) => part.startsWith(':') || part === actualParts[i])
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (routeParts.length !== actualParts.length) return false
|
|
107
|
+
return routeParts.every((part, i) => part.startsWith(':') || part === actualParts[i])
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function _matchFlat(routePath, actualPath) {
|
|
111
|
+
const paths = _getRoutePaths(routePath)
|
|
112
|
+
return paths.some(p => _matchSingleFlat(p, actualPath))
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function _findMatchingPath(routePath, actualPath) {
|
|
116
|
+
const paths = _getRoutePaths(routePath)
|
|
117
|
+
return paths.find(p => _matchSingleFlat(p, actualPath)) || null
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function _extractParamsFromPath(singlePath, actualPath) {
|
|
121
|
+
const params = {}
|
|
122
|
+
if (singlePath === '*') return params
|
|
123
|
+
|
|
124
|
+
const routeParts = singlePath.split('/').filter(Boolean)
|
|
125
|
+
const actualParts = actualPath.split('/').filter(Boolean)
|
|
126
|
+
|
|
127
|
+
const hasWildcard = routeParts.length > 0 && routeParts[routeParts.length - 1] === '*'
|
|
128
|
+
const checkLen = hasWildcard ? routeParts.length - 1 : routeParts.length
|
|
129
|
+
|
|
130
|
+
for (let i = 0; i < checkLen; i++) {
|
|
131
|
+
if (routeParts[i] && routeParts[i].startsWith(':')) {
|
|
132
|
+
params[routeParts[i].slice(1)] = actualParts[i] || ''
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
return params
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function _extractParamsFlat(routePath, actualPath) {
|
|
139
|
+
const matchingPath = _findMatchingPath(routePath, actualPath)
|
|
140
|
+
if (!matchingPath) return {}
|
|
141
|
+
return _extractParamsFromPath(matchingPath, actualPath)
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function _findMatchingChain(path) {
|
|
145
|
+
if (_routeCache.has(path)) {
|
|
146
|
+
const cached = _routeCache.get(path)
|
|
147
|
+
_routeCache.delete(path)
|
|
148
|
+
_routeCache.set(path, cached)
|
|
149
|
+
return cached
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const urlSegments = path.split('/').filter(Boolean)
|
|
153
|
+
const hasNested = _routes.some(r => r.children && r.children.length > 0)
|
|
154
|
+
|
|
155
|
+
let chain = null
|
|
156
|
+
|
|
157
|
+
if (hasNested) {
|
|
158
|
+
chain = _matchRouteTree(_routes, urlSegments)
|
|
159
|
+
if (chain) {
|
|
160
|
+
_cacheResult(path, chain)
|
|
161
|
+
return chain
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const paths = _routes.filter(r => {
|
|
166
|
+
const routePaths = _getRoutePaths(r.path)
|
|
167
|
+
return !routePaths.includes('*') && !routePaths.some(p => p.endsWith('/*')) && !r.children
|
|
168
|
+
})
|
|
169
|
+
const exactMatch = paths.find(r => _matchFlat(r.path, path))
|
|
170
|
+
if (exactMatch) {
|
|
171
|
+
const matchedPath = _findMatchingPath(exactMatch.path, path)
|
|
172
|
+
chain = [{ route: exactMatch, params: _extractParamsFlat(exactMatch.path, path), matchedSegments: urlSegments, matchedPath }]
|
|
173
|
+
_cacheResult(path, chain)
|
|
174
|
+
return chain
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
const wildcardPaths = _routes.filter(r => {
|
|
178
|
+
const routePaths = _getRoutePaths(r.path)
|
|
179
|
+
return !r.children && routePaths.some(p => p.endsWith('/*'))
|
|
180
|
+
})
|
|
181
|
+
const wildcardMatch = wildcardPaths.find(r => _matchFlat(r.path, path))
|
|
182
|
+
if (wildcardMatch) {
|
|
183
|
+
const matchedPath = _findMatchingPath(wildcardMatch.path, path)
|
|
184
|
+
chain = [{ route: wildcardMatch, params: _extractParamsFlat(wildcardMatch.path, path), matchedSegments: urlSegments, matchedPath }]
|
|
185
|
+
_cacheResult(path, chain)
|
|
186
|
+
return chain
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const catchAll = _routes.find(r => {
|
|
190
|
+
const routePaths = _getRoutePaths(r.path)
|
|
191
|
+
return routePaths.includes('*')
|
|
192
|
+
})
|
|
193
|
+
if (catchAll) {
|
|
194
|
+
chain = [{ route: catchAll, params: {}, matchedSegments: urlSegments, matchedPath: '*' }]
|
|
195
|
+
_cacheResult(path, chain)
|
|
196
|
+
return chain
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
return null
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function _cacheResult(path, chain) {
|
|
203
|
+
if (_routeCache.size >= _cacheMaxSize) {
|
|
204
|
+
const firstKey = _routeCache.keys().next().value
|
|
205
|
+
_routeCache.delete(firstKey)
|
|
206
|
+
}
|
|
207
|
+
_routeCache.set(path, chain)
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function _clearRouteCache() {
|
|
211
|
+
_routeCache.clear()
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
function _unmountFromLevel(level) {
|
|
215
|
+
for (let i = _activeChain.length - 1; i >= level; i--) {
|
|
216
|
+
const entry = _activeChain[i]
|
|
217
|
+
if (entry && entry.instance) {
|
|
218
|
+
try { entry.instance.unmount() } catch { }
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
_activeChain.length = level
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
function _getRoutePathCount(route) {
|
|
225
|
+
return Array.isArray(route.path) ? route.path.length : 1
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
function _renderChain(chain) {
|
|
229
|
+
try {
|
|
230
|
+
let reuseUntil = 0
|
|
231
|
+
for (let i = 0; i < Math.min(_activeChain.length, chain.length); i++) {
|
|
232
|
+
const active = _activeChain[i]
|
|
233
|
+
const incoming = chain[i]
|
|
234
|
+
|
|
235
|
+
if (
|
|
236
|
+
active.route === incoming.route &&
|
|
237
|
+
_segmentsEqual(active.matchedSegments, incoming.matchedSegments)
|
|
238
|
+
) {
|
|
239
|
+
reuseUntil = i + 1
|
|
240
|
+
}
|
|
241
|
+
else if (
|
|
242
|
+
active.route === incoming.route &&
|
|
243
|
+
_getRoutePathCount(active.route) > 1
|
|
244
|
+
) {
|
|
245
|
+
reuseUntil = i + 1
|
|
246
|
+
}
|
|
247
|
+
else {
|
|
248
|
+
break
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
_unmountFromLevel(reuseUntil)
|
|
253
|
+
|
|
254
|
+
const allParams = {}
|
|
255
|
+
for (const entry of chain) {
|
|
256
|
+
Object.assign(allParams, entry.params)
|
|
257
|
+
}
|
|
258
|
+
routeParams(allParams)
|
|
259
|
+
matchedRoutes(chain.map(e => e.route))
|
|
260
|
+
_renderFromLevel(chain, reuseUntil)
|
|
261
|
+
} catch (error) {
|
|
262
|
+
console.error('[framework] Error rendering route chain:', error)
|
|
263
|
+
_unmountFromLevel(0)
|
|
264
|
+
if (_rootOutlet) {
|
|
265
|
+
_rootOutlet.innerHTML = `<p style="color:red">[framework] Error rendering route: ${error.message}</p>`
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
function _renderFromLevel(chain, level) {
|
|
271
|
+
if (level >= chain.length) return
|
|
272
|
+
|
|
273
|
+
const entry = chain[level]
|
|
274
|
+
const outlet = _getOutletForLevel(level)
|
|
275
|
+
|
|
276
|
+
if (!outlet) {
|
|
277
|
+
_waitForRouterView(level, () => _renderFromLevel(chain, level))
|
|
278
|
+
return
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
outlet.innerHTML = ''
|
|
282
|
+
|
|
283
|
+
const componentFn = entry.route.component
|
|
284
|
+
const instance = componentFn()
|
|
285
|
+
instance.render(outlet)
|
|
286
|
+
|
|
287
|
+
_activeChain[level] = {
|
|
288
|
+
route: entry.route,
|
|
289
|
+
matchedSegments: entry.matchedSegments,
|
|
290
|
+
params: entry.params,
|
|
291
|
+
instance,
|
|
292
|
+
outlet
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
if (level + 1 < chain.length) _scheduleNextLevel(chain, level + 1)
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
function _scheduleNextLevel(chain, level) {
|
|
299
|
+
const outlet = _getOutletForLevel(level)
|
|
300
|
+
if (outlet) {
|
|
301
|
+
_renderFromLevel(chain, level)
|
|
302
|
+
return
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
_waitForRouterView(level, () => _renderFromLevel(chain, level))
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
function _getOutletForLevel(level) {
|
|
309
|
+
if (level === 0) return _rootOutlet
|
|
310
|
+
|
|
311
|
+
const parentEntry = _activeChain[level - 1]
|
|
312
|
+
if (!parentEntry || !parentEntry.outlet) return null
|
|
313
|
+
|
|
314
|
+
return parentEntry.outlet.querySelector('[data-router-view]')
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
function _waitForRouterView(level, callback) {
|
|
318
|
+
const parentEntry = _activeChain[level - 1]
|
|
319
|
+
if (!parentEntry || !parentEntry.outlet) return
|
|
320
|
+
|
|
321
|
+
const parentOutlet = parentEntry.outlet
|
|
322
|
+
|
|
323
|
+
const existing = parentOutlet.querySelector('[data-router-view]')
|
|
324
|
+
if (existing) {
|
|
325
|
+
callback()
|
|
326
|
+
return
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
const observer = new MutationObserver(() => {
|
|
330
|
+
const el = parentOutlet.querySelector('[data-router-view]')
|
|
331
|
+
if (el) {
|
|
332
|
+
observer.disconnect()
|
|
333
|
+
callback()
|
|
334
|
+
}
|
|
335
|
+
})
|
|
336
|
+
|
|
337
|
+
observer.observe(parentOutlet, { childList: true, subtree: true })
|
|
338
|
+
|
|
339
|
+
setTimeout(() => observer.disconnect(), _routerViewTimeout)
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
function _segmentsEqual(a, b) {
|
|
343
|
+
if (a.length !== b.length) return false
|
|
344
|
+
for (let i = 0; i < a.length; i++) {
|
|
345
|
+
if (a[i] !== b[i]) return false
|
|
346
|
+
}
|
|
347
|
+
return true
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
function _resolveRoute() {
|
|
351
|
+
if (!_rootOutlet || _routes.length === 0) return
|
|
352
|
+
|
|
353
|
+
const path = _getPath()
|
|
354
|
+
const chain = _findMatchingChain(path)
|
|
355
|
+
|
|
356
|
+
if (!chain) {
|
|
357
|
+
_unmountFromLevel(0)
|
|
358
|
+
_rootOutlet.innerHTML = `<p style="color:red">[framework] No route matched for "${path}"</p>`
|
|
359
|
+
routeParams({})
|
|
360
|
+
matchedRoutes([])
|
|
361
|
+
return
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
_renderChain(chain)
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
export function navigate(path) {
|
|
368
|
+
if (path === _getPath()) return
|
|
369
|
+
window.history.pushState({}, '', path)
|
|
370
|
+
_updateRoute()
|
|
371
|
+
_resolveRoute()
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
export function mountOutlet(el) {
|
|
375
|
+
_rootOutlet = el
|
|
376
|
+
_resolveRoute()
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
function _isInternalLink(anchor) {
|
|
380
|
+
const href = anchor.getAttribute('href')
|
|
381
|
+
if (!href) return false
|
|
382
|
+
if (href.startsWith('#')) return false
|
|
383
|
+
if (href.startsWith('http://') || href.startsWith('https://') || href.startsWith('//')) {
|
|
384
|
+
const url = new URL(href, window.location.origin)
|
|
385
|
+
return url.origin === window.location.origin
|
|
386
|
+
}
|
|
387
|
+
return href.startsWith('/')
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
function _getAnchorFromEvent(e) {
|
|
391
|
+
if (e.composedPath) {
|
|
392
|
+
return e.composedPath().find(el => el.tagName === 'A')
|
|
393
|
+
}
|
|
394
|
+
return e.target.tagName === 'A' ? e.target : null
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
function _handleLinkClick(e) {
|
|
398
|
+
const anchor = _getAnchorFromEvent(e)
|
|
399
|
+
if (!anchor) return
|
|
400
|
+
|
|
401
|
+
if (anchor.hasAttribute('f-link') || _isInternalLink(anchor)) {
|
|
402
|
+
e.preventDefault()
|
|
403
|
+
const href = anchor.getAttribute('href')
|
|
404
|
+
if (href && href !== _getPath()) {
|
|
405
|
+
navigate(href)
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
function _setupPopstateHandler() {
|
|
411
|
+
const handler = () => {
|
|
412
|
+
_updateRoute()
|
|
413
|
+
_resolveRoute()
|
|
414
|
+
}
|
|
415
|
+
window.addEventListener('popstate', handler)
|
|
416
|
+
return handler
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
function _setupClickHandler() {
|
|
420
|
+
_clickHandler = _handleLinkClick
|
|
421
|
+
document.addEventListener('click', _clickHandler)
|
|
422
|
+
return _clickHandler
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
function _setupInitialRoute() {
|
|
426
|
+
if (document.readyState === 'loading') {
|
|
427
|
+
window.addEventListener('DOMContentLoaded', () => {
|
|
428
|
+
_updateRoute()
|
|
429
|
+
_resolveRoute()
|
|
430
|
+
}, { once: true })
|
|
431
|
+
} else {
|
|
432
|
+
setTimeout(() => {
|
|
433
|
+
if (_activeChain.length === 0) _resolveRoute()
|
|
434
|
+
}, 0)
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
export function createRouter(routes, options = {}) {
|
|
439
|
+
_routes = routes
|
|
440
|
+
_activeChain = []
|
|
441
|
+
_cacheMaxSize = options.cacheSize || 100
|
|
442
|
+
_routerViewTimeout = options.routerViewTimeout || 10000
|
|
443
|
+
|
|
444
|
+
_updateRoute()
|
|
445
|
+
|
|
446
|
+
const popstateHandler = _setupPopstateHandler()
|
|
447
|
+
_setupClickHandler()
|
|
448
|
+
_setupInitialRoute()
|
|
449
|
+
|
|
450
|
+
return {
|
|
451
|
+
navigate,
|
|
452
|
+
destroy() {
|
|
453
|
+
window.removeEventListener('popstate', popstateHandler)
|
|
454
|
+
document.removeEventListener('click', _clickHandler)
|
|
455
|
+
_unmountFromLevel(0)
|
|
456
|
+
_routes = []
|
|
457
|
+
_rootOutlet = null
|
|
458
|
+
_activeChain = []
|
|
459
|
+
_clearRouteCache()
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { ComponentResult, ComponentFactory } from './component'
|
|
2
|
+
|
|
3
|
+
export function mountTemplate(
|
|
4
|
+
template: string,
|
|
5
|
+
container: HTMLElement,
|
|
6
|
+
context: ComponentResult,
|
|
7
|
+
components: Record<string, ComponentFactory>
|
|
8
|
+
): { effects: (() => void)[] }
|
|
9
|
+
|
|
10
|
+
export function compileNode(
|
|
11
|
+
node: HTMLElement,
|
|
12
|
+
context: ComponentResult,
|
|
13
|
+
components: Record<string, ComponentFactory>,
|
|
14
|
+
effects: (() => void)[]
|
|
15
|
+
): void
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
export type ComponentProps = Record<string, any>
|
|
2
|
+
export type EmptyProps = Record<string, never>
|
|
3
|
+
export type PropSchema = string[] | Record<string, PropConfig>
|
|
4
|
+
|
|
5
|
+
export type PropConfig = {
|
|
6
|
+
type?: FunctionConstructor | StringConstructor | NumberConstructor | BooleanConstructor
|
|
7
|
+
default?: any | (() => any)
|
|
8
|
+
required?: boolean
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export type EmitFn = (eventName: string, ...args: any[]) => void
|
|
12
|
+
|
|
13
|
+
export type Slots = Record<string, string>
|
|
14
|
+
|
|
15
|
+
export type SetupContext = {
|
|
16
|
+
emit: EmitFn
|
|
17
|
+
slots: Slots
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export type ComponentInstance = {
|
|
21
|
+
props: ComponentProps
|
|
22
|
+
_mountHooks: (() => void)[]
|
|
23
|
+
_unmountHooks: (() => void)[]
|
|
24
|
+
_effects: import('./signal').EffectRunner[]
|
|
25
|
+
_element: HTMLElement | null
|
|
26
|
+
_provides: Record<string, any>
|
|
27
|
+
_parent: ComponentInstance | null
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export type ComponentResult = {
|
|
31
|
+
template: string
|
|
32
|
+
[key: string]: any
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export type ComponentOptions<TProps = ComponentProps, P = PropSchema> = {
|
|
36
|
+
props?: P
|
|
37
|
+
components?: Record<string, ComponentFactory<any>>
|
|
38
|
+
setup: (props: TProps, ctx: SetupContext) => ComponentResult
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export type ComponentFactory<TProps = ComponentProps> = (
|
|
42
|
+
props?: TProps,
|
|
43
|
+
options?: { listeners?: Record<string, Function>, slots?: Slots, parent?: ComponentInstance }
|
|
44
|
+
) => ComponentApi
|
|
45
|
+
|
|
46
|
+
export type ComponentApi = {
|
|
47
|
+
render: (container: HTMLElement) => ComponentInstance
|
|
48
|
+
unmount: () => void
|
|
49
|
+
instance: ComponentInstance
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export declare function onMount(fn: () => void): void
|
|
53
|
+
export declare function onUnmount(fn: () => void): void
|
|
54
|
+
export declare function parseSlots(slotHTML: string): Slots
|
|
55
|
+
export declare function getCurrentInstance(): ComponentInstance | null
|
|
56
|
+
|
|
57
|
+
export declare function provide<T>(key: string, value: T): void
|
|
58
|
+
export declare function inject<T>(key: string): T | null
|
|
59
|
+
|
|
60
|
+
type InferPropType<T> = T extends StringConstructor ? string :
|
|
61
|
+
T extends NumberConstructor ? number :
|
|
62
|
+
T extends BooleanConstructor ? boolean :
|
|
63
|
+
any;
|
|
64
|
+
|
|
65
|
+
type InferProps<P> = P extends string[]
|
|
66
|
+
? Record<P[number], any>
|
|
67
|
+
: P extends Record<string, PropConfig>
|
|
68
|
+
? {
|
|
69
|
+
[K in keyof P]: P[K]['required'] extends true
|
|
70
|
+
? InferPropType<P[K]['type']>
|
|
71
|
+
: InferPropType<P[K]['type']> | (P[K] extends { default: any } ? never : undefined)
|
|
72
|
+
}
|
|
73
|
+
: Record<string, any>;
|
|
74
|
+
|
|
75
|
+
export declare function defineComponent<P extends PropSchema = any, TProps = InferProps<P>>(
|
|
76
|
+
options: ComponentOptions<TProps, P>
|
|
77
|
+
): ComponentFactory<TProps>
|
|
78
|
+
|
|
79
|
+
export type AsyncComponentLoader<TProps = ComponentProps> = () => Promise<ComponentFactory<TProps> | { default: ComponentFactory<TProps> } | Record<string, any>>
|
|
80
|
+
|
|
81
|
+
export type AsyncComponentOptions<TProps = ComponentProps> = {
|
|
82
|
+
loader: AsyncComponentLoader<TProps>
|
|
83
|
+
loadingComponent?: ComponentFactory<any>
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export declare function defineAsyncComponent<TProps = ComponentProps>(
|
|
87
|
+
loaderOrOptions: AsyncComponentLoader<TProps> | AsyncComponentOptions<TProps>
|
|
88
|
+
): ComponentFactory<TProps>
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export interface Composable<T = any> {
|
|
2
|
+
(...args: any[]): T
|
|
3
|
+
readonly _isComposable: true
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
declare global {
|
|
7
|
+
interface Composable<T = any> {
|
|
8
|
+
(...args: any[]): T
|
|
9
|
+
readonly _isComposable: true
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export declare function defineComposable<T extends (...args: any[]) => any>(fn: T): T;
|
|
14
|
+
export declare function assertSetupContext(name?: string): void;
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import type { ComponentResult, ComponentFactory } from './component'
|
|
2
|
+
|
|
3
|
+
export function processDirectives(
|
|
4
|
+
root: HTMLElement,
|
|
5
|
+
context: ComponentResult,
|
|
6
|
+
components: Record<string, ComponentFactory>,
|
|
7
|
+
effects: (() => void)[]
|
|
8
|
+
): void
|
|
9
|
+
|
|
10
|
+
export function processRefs(root: HTMLElement, context: ComponentResult): void
|
|
11
|
+
|
|
12
|
+
export function processModel(root: HTMLElement, context: ComponentResult, effects: (() => void)[]): void
|
|
13
|
+
|
|
14
|
+
export function processFor(
|
|
15
|
+
root: HTMLElement,
|
|
16
|
+
context: ComponentResult,
|
|
17
|
+
components: Record<string, ComponentFactory>,
|
|
18
|
+
effects: (() => void)[]
|
|
19
|
+
): void
|
|
20
|
+
|
|
21
|
+
export function processIf(
|
|
22
|
+
root: HTMLElement,
|
|
23
|
+
context: ComponentResult,
|
|
24
|
+
components: Record<string, ComponentFactory>,
|
|
25
|
+
effects: (() => void)[]
|
|
26
|
+
): void
|
|
27
|
+
|
|
28
|
+
export function processIs(
|
|
29
|
+
root: HTMLElement,
|
|
30
|
+
context: ComponentResult,
|
|
31
|
+
components: Record<string, ComponentFactory>,
|
|
32
|
+
effects: (() => void)[]
|
|
33
|
+
): void
|
|
34
|
+
|
|
35
|
+
export function processShow(root: HTMLElement, context: ComponentResult, effects: (() => void)[]): void
|
|
36
|
+
export function processHtml(root: HTMLElement, context: ComponentResult, effects: (() => void)[]): void
|
|
37
|
+
export function processText(root: HTMLElement, context: ComponentResult, effects: (() => void)[]): void
|
|
38
|
+
export function processEvents(root: HTMLElement, context: ComponentResult): void
|
|
39
|
+
export function processClassList(root: HTMLElement, context: ComponentResult, effects: (() => void)[]): void
|
|
40
|
+
export function processCloak(root: HTMLElement): void
|
|
41
|
+
|
|
42
|
+
export interface DirectiveBinding<V = any> {
|
|
43
|
+
value: V
|
|
44
|
+
expression: string
|
|
45
|
+
arg: string | null
|
|
46
|
+
modifiers: string[]
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export interface DirectiveHooks<V = any> {
|
|
50
|
+
mounted?: (el: HTMLElement, binding: DirectiveBinding<V>) => void
|
|
51
|
+
updated?: (el: HTMLElement, binding: DirectiveBinding<V>) => void
|
|
52
|
+
unmounted?: (el: HTMLElement, binding: DirectiveBinding<V>) => void
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function directive<V = any>(name: string, hooks: DirectiveHooks<V>): void
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { ComponentResult } from './component'
|
|
2
|
+
|
|
3
|
+
export type InterpolationPart = {
|
|
4
|
+
type: 'static' | 'dynamic'
|
|
5
|
+
value?: string
|
|
6
|
+
key?: string
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export const MUSTACHE_RE: RegExp
|
|
10
|
+
|
|
11
|
+
export function evaluateExpression(expr: string, context: Record<string, any>, extraContext?: Record<string, any>): any
|
|
12
|
+
export function parseInterpolation(str: string): InterpolationPart[]
|
|
13
|
+
export function sanitizeAttr(name: string, value: string): string
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
export const DELEGATED_EVENTS: Set<string>
|
|
2
|
+
|
|
3
|
+
export function isDelegatedEvent(eventName: string): boolean
|
|
4
|
+
|
|
5
|
+
export interface DelegatedEventOptions {
|
|
6
|
+
modifiers?: string[]
|
|
7
|
+
context?: Record<string, any>
|
|
8
|
+
expr?: string
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function registerDelegatedEvent(
|
|
12
|
+
element: Element,
|
|
13
|
+
eventType: string,
|
|
14
|
+
handler: (event: Event) => void,
|
|
15
|
+
options?: DelegatedEventOptions
|
|
16
|
+
): () => void
|
|
17
|
+
|
|
18
|
+
export function createEventHandler(
|
|
19
|
+
handler: Function | string,
|
|
20
|
+
modifiers: string[],
|
|
21
|
+
context: Record<string, any>,
|
|
22
|
+
expr: string
|
|
23
|
+
): (event: Event) => void
|
|
24
|
+
|
|
25
|
+
export interface EventHandlerSyntax {
|
|
26
|
+
delegated: `@${string}`
|
|
27
|
+
native: `on:${string}`
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
declare global {
|
|
31
|
+
const isDelegatedEvent: typeof import('./event-delegation').isDelegatedEvent
|
|
32
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export * from './signal'
|
|
2
|
+
export * from './component'
|
|
3
|
+
export * from './composable'
|
|
4
|
+
export * from './router'
|
|
5
|
+
export * from './compiler'
|
|
6
|
+
export * from './directives'
|
|
7
|
+
export * from './store'
|
|
8
|
+
export * from './event-delegation'
|
|
9
|
+
|
|
10
|
+
declare global {
|
|
11
|
+
type Signal<T = any> = import('./signal').Signal<T>
|
|
12
|
+
type Computed<T = any> = import('./signal').Computed<T>
|
|
13
|
+
type SignalAccessor<T> = import('./signal').SignalAccessor<T>
|
|
14
|
+
type Composable<T extends (...args: any[]) => any> = import('./composable').Composable<T>
|
|
15
|
+
type DirectiveBinding<V = any> = import('./directives').DirectiveBinding<V>
|
|
16
|
+
type DirectiveHooks<V = any> = import('./directives').DirectiveHooks<V>
|
|
17
|
+
type DelegatedEventOptions = import('./event-delegation').DelegatedEventOptions
|
|
18
|
+
const defineStore: typeof import('./store').defineStore
|
|
19
|
+
const isDelegatedEvent: typeof import('./event-delegation').isDelegatedEvent
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export as namespace Fusee
|