metaowl 0.4.0 → 0.5.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/CHANGELOG.md +52 -0
- package/README.md +13 -15
- package/build/runtime/bin/metaowl-build.js +10 -0
- package/{bin → build/runtime/bin}/metaowl-create.js +96 -177
- package/build/runtime/bin/metaowl-dev.js +10 -0
- package/build/runtime/bin/metaowl-generate.js +231 -0
- package/build/runtime/bin/metaowl-lint.js +58 -0
- package/build/runtime/bin/utils.js +68 -0
- package/build/runtime/index.js +141 -0
- package/build/runtime/modules/app-mounter.js +65 -0
- package/build/runtime/modules/auto-import.js +140 -0
- package/build/runtime/modules/cache.js +49 -0
- package/build/runtime/modules/composables.js +353 -0
- package/build/runtime/modules/error-boundary.js +116 -0
- package/build/runtime/modules/fetch.js +31 -0
- package/build/runtime/modules/file-router.js +205 -0
- package/build/runtime/modules/forms.js +193 -0
- package/build/runtime/modules/i18n.js +167 -0
- package/build/runtime/modules/layouts.js +163 -0
- package/build/runtime/modules/link.js +141 -0
- package/build/runtime/modules/meta.js +117 -0
- package/build/runtime/modules/odoo-rpc.js +264 -0
- package/build/runtime/modules/pwa.js +262 -0
- package/build/runtime/modules/router.js +389 -0
- package/build/runtime/modules/seo.js +186 -0
- package/build/runtime/modules/store.js +196 -0
- package/build/runtime/modules/templates-manager.js +52 -0
- package/build/runtime/modules/test-utils.js +238 -0
- package/build/runtime/vite/plugin.js +183 -0
- package/eslint.js +29 -0
- package/package.json +29 -11
- package/CONTRIBUTING.md +0 -49
- package/bin/metaowl-build.js +0 -12
- package/bin/metaowl-dev.js +0 -12
- package/bin/metaowl-generate.js +0 -339
- package/bin/metaowl-lint.js +0 -71
- package/bin/utils.js +0 -82
- package/index.js +0 -328
- package/modules/app-mounter.js +0 -104
- package/modules/auto-import.js +0 -225
- package/modules/cache.js +0 -59
- package/modules/composables.js +0 -600
- package/modules/error-boundary.js +0 -228
- package/modules/fetch.js +0 -51
- package/modules/file-router.js +0 -478
- package/modules/forms.js +0 -353
- package/modules/i18n.js +0 -333
- package/modules/layouts.js +0 -431
- package/modules/link.js +0 -255
- package/modules/meta.js +0 -119
- package/modules/odoo-rpc.js +0 -511
- package/modules/pwa.js +0 -515
- package/modules/router.js +0 -769
- package/modules/seo.js +0 -501
- package/modules/store.js +0 -409
- package/modules/templates-manager.js +0 -89
- package/modules/test-utils.js +0 -532
- package/test/auto-import.test.js +0 -110
- package/test/cache.test.js +0 -55
- package/test/composables.test.js +0 -103
- package/test/dynamic-routes.test.js +0 -469
- package/test/error-boundary.test.js +0 -126
- package/test/fetch.test.js +0 -100
- package/test/file-router.test.js +0 -55
- package/test/forms.test.js +0 -203
- package/test/i18n.test.js +0 -188
- package/test/layouts.test.js +0 -395
- package/test/link.test.js +0 -189
- package/test/meta.test.js +0 -146
- package/test/odoo-rpc.test.js +0 -547
- package/test/pwa.test.js +0 -154
- package/test/router-guards.test.js +0 -229
- package/test/router.test.js +0 -77
- package/test/seo.test.js +0 -353
- package/test/store.test.js +0 -476
- package/test/templates-manager.test.js +0 -83
- package/test/test-utils.test.js +0 -314
- package/vite/plugin.js +0 -277
- package/vitest.config.js +0 -8
package/modules/file-router.js
DELETED
|
@@ -1,478 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @module FileRouter
|
|
3
|
-
*
|
|
4
|
-
* File-based routing with dynamic route parameter support.
|
|
5
|
-
*
|
|
6
|
-
* Convention (mirrors Nuxt/Next.js):
|
|
7
|
-
* File: pages/index/Index.js → URL: /
|
|
8
|
-
* File: pages/about/About.js → URL: /about
|
|
9
|
-
* File: pages/blog/post/Post.js → URL: /blog/post
|
|
10
|
-
*
|
|
11
|
-
* Dynamic Routes:
|
|
12
|
-
* File: pages/user/[id]/User.js → URL: /user/:id
|
|
13
|
-
* File: pages/product/[category]/[slug].js → URL: /product/:category/:slug
|
|
14
|
-
* File: pages/docs/[...path].js → URL: /docs/:path(.*)
|
|
15
|
-
*
|
|
16
|
-
* Optional Parameters:
|
|
17
|
-
* File: pages/blog/[id]/[slug]?/Blog.js → URL: /blog/:id/:slug?
|
|
18
|
-
*
|
|
19
|
-
* Catch-all Routes:
|
|
20
|
-
* File: pages/[...404].js → URL: /:path(.*)
|
|
21
|
-
*/
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* Pattern types for route segments.
|
|
25
|
-
*/
|
|
26
|
-
const PATTERNS = {
|
|
27
|
-
// [param] → required parameter
|
|
28
|
-
PARAM: /^\[([^?]+)\]$/,
|
|
29
|
-
// [param]? → optional parameter
|
|
30
|
-
OPTIONAL: /^\[([^?]+)\?\]$/,
|
|
31
|
-
// [...param] → catch-all parameter
|
|
32
|
-
CATCH_ALL: /^\.\.\.(.*)$/,
|
|
33
|
-
// Regular segment
|
|
34
|
-
NORMAL: /^[^\[]+$/
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* Converts a file path segment to a route pattern segment.
|
|
39
|
-
*
|
|
40
|
-
* @param {string} segment - Path segment (e.g., [id], about, [...slug])
|
|
41
|
-
* @returns {string} Route pattern segment (e.g., :id, about, :slug(.*))
|
|
42
|
-
*/
|
|
43
|
-
function segmentToPattern(segment) {
|
|
44
|
-
// Check for catch-all [...something]
|
|
45
|
-
const insideBrackets = segment.match(/^\[(.+)\]$/)
|
|
46
|
-
if (insideBrackets) {
|
|
47
|
-
const content = insideBrackets[1]
|
|
48
|
-
|
|
49
|
-
// Check for spread: [...param]
|
|
50
|
-
if (content.startsWith('...')) {
|
|
51
|
-
const paramName = content.slice(3) || 'path'
|
|
52
|
-
return `:${paramName}(.*)`
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
// Check for optional: [param?]
|
|
56
|
-
if (content.endsWith('?')) {
|
|
57
|
-
const paramName = content.slice(0, -1)
|
|
58
|
-
return `:${paramName}?`
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
// Regular parameter: [param]
|
|
62
|
-
return `:${content}`
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
// Normal segment
|
|
66
|
-
return segment
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
/**
|
|
70
|
-
* Checks if a segment is a dynamic parameter.
|
|
71
|
-
*
|
|
72
|
-
* @param {string} segment - Path segment
|
|
73
|
-
* @returns {boolean}
|
|
74
|
-
*/
|
|
75
|
-
function isDynamicSegment(segment) {
|
|
76
|
-
return segment.startsWith('[') && segment.endsWith(']')
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
/**
|
|
80
|
-
* Checks if a segment is an optional parameter.
|
|
81
|
-
*
|
|
82
|
-
* @param {string} segment - Path segment
|
|
83
|
-
* @returns {boolean}
|
|
84
|
-
*/
|
|
85
|
-
function isOptionalSegment(segment) {
|
|
86
|
-
return segment.startsWith('[') && segment.endsWith('?]')
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
/**
|
|
90
|
-
* Extracts parameter names from a file path.
|
|
91
|
-
*
|
|
92
|
-
* @param {string} filePath - Relative file path
|
|
93
|
-
* @returns {string[]} Parameter names in order
|
|
94
|
-
*/
|
|
95
|
-
function extractParamNames(filePath) {
|
|
96
|
-
const params = []
|
|
97
|
-
const parts = filePath.split('/')
|
|
98
|
-
|
|
99
|
-
for (const part of parts) {
|
|
100
|
-
const match = part.match(/^\[([^?\]]+)\??\]$|^\[\.\.\.([^\]]+)\]$/)
|
|
101
|
-
if (match) {
|
|
102
|
-
params.push(match[1] || match[2] || 'path')
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
return params
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
/**
|
|
110
|
-
* Derives a URL path from an import.meta.glob key.
|
|
111
|
-
*
|
|
112
|
-
* Convention (mirrors Nuxt/Next.js file-based routing):
|
|
113
|
-
* Key: ./pages/index/Index.js → URL: /
|
|
114
|
-
* Key: ./pages/about/About.js → URL: /about
|
|
115
|
-
* Key: ./pages/about/bla/Bla.js → URL: /about/bla
|
|
116
|
-
* Key: ./pages/user/[id]/User.js → URL: /user/:id
|
|
117
|
-
* Key: ./pages/[...404].js → URL: /:path(.*)
|
|
118
|
-
*
|
|
119
|
-
* Rule: the *directory* path relative to pages/ becomes the URL.
|
|
120
|
-
* A top-level directory named 'index' maps to '/'.
|
|
121
|
-
* Dynamic segments use [param] syntax and become route parameters.
|
|
122
|
-
*
|
|
123
|
-
* @param {string} key - import.meta.glob key, e.g. './pages/about/About.js'
|
|
124
|
-
* @returns {string} URL pattern
|
|
125
|
-
*/
|
|
126
|
-
export function pathFromKey(key) {
|
|
127
|
-
// Strip leading './' and 'pages/' prefix
|
|
128
|
-
const rel = key.replace(/^\.\/pages\//, '')
|
|
129
|
-
// Get all segments
|
|
130
|
-
const parts = rel.split('/')
|
|
131
|
-
// Remove filename (last segment)
|
|
132
|
-
parts.pop()
|
|
133
|
-
|
|
134
|
-
if (parts.length === 0) {
|
|
135
|
-
return '/'
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
// Check for single 'index'
|
|
139
|
-
if (parts.length === 1 && parts[0] === 'index') {
|
|
140
|
-
return '/'
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
// Convert segments to route patterns
|
|
144
|
-
const routeParts = parts.map(segmentToPattern)
|
|
145
|
-
|
|
146
|
-
return '/' + routeParts.join('/')
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
/**
|
|
150
|
-
* Builds a display path for documentation (without parameter syntax).
|
|
151
|
-
*
|
|
152
|
-
* @param {string} pattern - Route pattern like '/user/:id'
|
|
153
|
-
* @returns {string} Display path like '/user/[id]'
|
|
154
|
-
*/
|
|
155
|
-
function patternToDisplay(pattern) {
|
|
156
|
-
return pattern
|
|
157
|
-
.replace(/:([^/(]+)\?/g, '[$1?]')
|
|
158
|
-
.replace(/:\(([^)]+)\)/g, '[]')
|
|
159
|
-
.replace(/:([^/(]+)(?:\([^)]*\))?/g, '[$1]')
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
/**
|
|
163
|
-
* Builds a regex pattern string from a route path.
|
|
164
|
-
*
|
|
165
|
-
* Supports:
|
|
166
|
-
* - Static segments: /about
|
|
167
|
-
* - Required params: /user/:id
|
|
168
|
-
* - Optional params: /user/:id?
|
|
169
|
-
* - Catch-all: /docs/:path(.*)
|
|
170
|
-
*
|
|
171
|
-
* @param {string} path - Route path pattern
|
|
172
|
-
* @returns {string} Regex pattern string
|
|
173
|
-
*/
|
|
174
|
-
function buildRegexPattern(path) {
|
|
175
|
-
// Escape forward slashes
|
|
176
|
-
let pattern = path.replace(/\//g, '\\/')
|
|
177
|
-
|
|
178
|
-
// Replace catch-all :name(.*) → capture everything
|
|
179
|
-
pattern = pattern.replace(/:([^/(]+)\(\.\*\)/g, '([^/]+(?:/[^/]+)*)')
|
|
180
|
-
|
|
181
|
-
// Replace optional params :name? → optional capture
|
|
182
|
-
pattern = pattern.replace(/\/:([^/(]+)\?/g, '(?:/([^/]+))?')
|
|
183
|
-
|
|
184
|
-
// Replace required params :name → capture
|
|
185
|
-
pattern = pattern.replace(/:([^/(\s]+)/g, '([^/]+)')
|
|
186
|
-
|
|
187
|
-
return '^' + pattern + '$'
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
/**
|
|
191
|
-
* Matches a URL path against a route pattern.
|
|
192
|
-
*
|
|
193
|
-
* @param {string} pattern - Route pattern (e.g., '/user/:id')
|
|
194
|
-
* @param {string} path - URL path (e.g., '/user/123')
|
|
195
|
-
* @returns {object|null} Matched params or null
|
|
196
|
-
*/
|
|
197
|
-
export function matchRoute(pattern, path) {
|
|
198
|
-
// Extract parameter names
|
|
199
|
-
const paramNames = []
|
|
200
|
-
const paramRegex = /:([^/?(]+)/g
|
|
201
|
-
let match
|
|
202
|
-
while ((match = paramRegex.exec(pattern)) !== null) {
|
|
203
|
-
paramNames.push(match[1])
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
// Build regex pattern
|
|
207
|
-
const regexPattern = buildRegexPattern(pattern)
|
|
208
|
-
const regex = new RegExp(regexPattern)
|
|
209
|
-
|
|
210
|
-
const matches = path.match(regex)
|
|
211
|
-
if (!matches) {
|
|
212
|
-
return null
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
// Extract parameter values
|
|
216
|
-
const params = {}
|
|
217
|
-
for (let i = 0; i < paramNames.length; i++) {
|
|
218
|
-
if (matches[i + 1] !== undefined) {
|
|
219
|
-
params[paramNames[i]] = matches[i + 1]
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
return { params, pattern }
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
/**
|
|
227
|
-
* Checks if a route path is dynamic.
|
|
228
|
-
*
|
|
229
|
-
* @param {string} path - Route path
|
|
230
|
-
* @returns {boolean}
|
|
231
|
-
*/
|
|
232
|
-
export function isDynamicRoute(path) {
|
|
233
|
-
return path.includes(':')
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
/**
|
|
237
|
-
* Extracts the page component from an eagerly-imported module.
|
|
238
|
-
* Prefers default export, falls back to the first function/class export.
|
|
239
|
-
*
|
|
240
|
-
* @param {object} mod - Eagerly imported module
|
|
241
|
-
* @param {string} key - Glob key (for error messages)
|
|
242
|
-
* @returns {Function}
|
|
243
|
-
*/
|
|
244
|
-
function componentFromModule(mod, key) {
|
|
245
|
-
if (typeof mod.default === 'function') return mod.default
|
|
246
|
-
const named = Object.values(mod).find(v => typeof v === 'function')
|
|
247
|
-
if (!named) throw new Error(`[metaowl] No component export found in "${key}"`)
|
|
248
|
-
return named
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
/**
|
|
252
|
-
* Builds a metaowl route table from an import.meta.glob result.
|
|
253
|
-
*
|
|
254
|
-
* @param {Record<string, object>} modules - Result of import.meta.glob with eager: true
|
|
255
|
-
* @returns {object[]} Route table for processRoutes()
|
|
256
|
-
*/
|
|
257
|
-
export function buildRoutes(modules) {
|
|
258
|
-
const routes = []
|
|
259
|
-
|
|
260
|
-
for (const [key, mod] of Object.entries(modules)) {
|
|
261
|
-
const path = pathFromKey(key)
|
|
262
|
-
const name = path === '/' ? 'index' : path.slice(1).replace(/[^a-zA-Z0-9]/g, '-').replace(/-+/g, '-').replace(/^-|-$/g, '')
|
|
263
|
-
const component = componentFromModule(mod, key)
|
|
264
|
-
const params = extractParamNames(key)
|
|
265
|
-
|
|
266
|
-
const route = {
|
|
267
|
-
name,
|
|
268
|
-
path: [path],
|
|
269
|
-
component,
|
|
270
|
-
params,
|
|
271
|
-
meta: component.route?.meta || {}
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
// Copy any route configuration from component
|
|
275
|
-
if (component.route) {
|
|
276
|
-
Object.assign(route, component.route)
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
routes.push(route)
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
// Sort routes: static first, then dynamic (fewer params first), catch-all last
|
|
283
|
-
routes.sort((a, b) => {
|
|
284
|
-
const aPath = a.path[0]
|
|
285
|
-
const bPath = b.path[0]
|
|
286
|
-
|
|
287
|
-
// Catch-all :name(.*) routes always come last
|
|
288
|
-
const aIsCatchAll = aPath.includes('(.*)')
|
|
289
|
-
const bIsCatchAll = bPath.includes('(.*)')
|
|
290
|
-
if (!aIsCatchAll && bIsCatchAll) return -1
|
|
291
|
-
if (aIsCatchAll && !bIsCatchAll) return 1
|
|
292
|
-
|
|
293
|
-
// Static routes before dynamic routes
|
|
294
|
-
const aIsDynamic = isDynamicRoute(aPath)
|
|
295
|
-
const bIsDynamic = isDynamicRoute(bPath)
|
|
296
|
-
|
|
297
|
-
if (!aIsDynamic && bIsDynamic) return -1
|
|
298
|
-
if (aIsDynamic && !bIsDynamic) return 1
|
|
299
|
-
|
|
300
|
-
// Among dynamic routes, more specific (longer path) comes first
|
|
301
|
-
if (aIsDynamic && bIsDynamic) {
|
|
302
|
-
const aSegments = aPath.split('/').length
|
|
303
|
-
const bSegments = bPath.split('/').length
|
|
304
|
-
if (aSegments !== bSegments) return bSegments - aSegments
|
|
305
|
-
|
|
306
|
-
const aParamCount = a.params?.length || 0
|
|
307
|
-
const bParamCount = b.params?.length || 0
|
|
308
|
-
return aParamCount - bParamCount
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
return 0
|
|
312
|
-
})
|
|
313
|
-
|
|
314
|
-
return routes
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
/**
|
|
318
|
-
* Finds a matching route for a given URL path.
|
|
319
|
-
*
|
|
320
|
-
* @param {object[]} routes - Route table
|
|
321
|
-
* @param {string} path - URL path
|
|
322
|
-
* @returns {object|null} Matched route with params
|
|
323
|
-
*/
|
|
324
|
-
export function findRoute(routes, path) {
|
|
325
|
-
for (const route of routes) {
|
|
326
|
-
for (const routePath of route.path) {
|
|
327
|
-
const match = matchRoute(routePath, path)
|
|
328
|
-
if (match) {
|
|
329
|
-
return {
|
|
330
|
-
...route,
|
|
331
|
-
matchedPath: routePath,
|
|
332
|
-
params: match.params
|
|
333
|
-
}
|
|
334
|
-
}
|
|
335
|
-
}
|
|
336
|
-
}
|
|
337
|
-
return null
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
/**
|
|
341
|
-
* Generates URL from route name and params.
|
|
342
|
-
*
|
|
343
|
-
* @param {string} name - Route name
|
|
344
|
-
* @param {object} [params] - Route parameters
|
|
345
|
-
* @returns {string} Generated URL
|
|
346
|
-
* @throws {Error} If route not found
|
|
347
|
-
*
|
|
348
|
-
* Example:
|
|
349
|
-
* generateUrl(routes, 'user', { id: '123' }) // returns '/user/123'
|
|
350
|
-
* generateUrl(routes, 'blog-post', { category: 'tech', slug: 'hello' }) // returns '/blog/tech/hello'
|
|
351
|
-
*/
|
|
352
|
-
export function generateUrl(routes, name, params = {}) {
|
|
353
|
-
const route = routes.find(r => r.name === name)
|
|
354
|
-
if (!route) {
|
|
355
|
-
throw new Error(`[metaowl] Route "${name}" not found`)
|
|
356
|
-
}
|
|
357
|
-
|
|
358
|
-
let path = route.path[0]
|
|
359
|
-
|
|
360
|
-
// Replace params in path
|
|
361
|
-
for (const [key, value] of Object.entries(params)) {
|
|
362
|
-
path = path.replace(`:${key}`, value)
|
|
363
|
-
path = path.replace(`:${key}?`, value)
|
|
364
|
-
}
|
|
365
|
-
|
|
366
|
-
// Remove remaining optional params and trailing ?
|
|
367
|
-
path = path.replace(/\/:[^/]+\?/g, '').replace(/\?$/, '')
|
|
368
|
-
|
|
369
|
-
return path
|
|
370
|
-
}
|
|
371
|
-
|
|
372
|
-
/**
|
|
373
|
-
* Validates route parameters.
|
|
374
|
-
*
|
|
375
|
-
* @param {object} route - Route definition
|
|
376
|
-
* @param {object} params - Parameters to validate
|
|
377
|
-
* @returns {object} Validation result { valid: boolean, missing: string[], extra: string[] }
|
|
378
|
-
*/
|
|
379
|
-
export function validateRouteParams(route, params) {
|
|
380
|
-
const required = route.params || []
|
|
381
|
-
const provided = Object.keys(params)
|
|
382
|
-
|
|
383
|
-
const missing = required.filter(p => !provided.includes(p))
|
|
384
|
-
const extra = provided.filter(p => !required.includes(p))
|
|
385
|
-
|
|
386
|
-
return {
|
|
387
|
-
valid: missing.length === 0,
|
|
388
|
-
missing,
|
|
389
|
-
extra
|
|
390
|
-
}
|
|
391
|
-
}
|
|
392
|
-
|
|
393
|
-
/**
|
|
394
|
-
* Parses current URL and returns route info.
|
|
395
|
-
*
|
|
396
|
-
* @param {object[]} routes - Route table
|
|
397
|
-
* @returns {object|null} Current route info
|
|
398
|
-
*/
|
|
399
|
-
export function parseCurrentRoute(routes) {
|
|
400
|
-
const path = document.location.pathname
|
|
401
|
-
return findRoute(routes, path)
|
|
402
|
-
}
|
|
403
|
-
|
|
404
|
-
/**
|
|
405
|
-
* Route configuration helper for components.
|
|
406
|
-
*
|
|
407
|
-
* @param {object} config - Route configuration
|
|
408
|
-
* @param {string} [config.path] - Route path override
|
|
409
|
-
* @param {object} [config.meta] - Route metadata
|
|
410
|
-
* @param {Function} [config.beforeEnter] - Per-route guard
|
|
411
|
-
* @returns {object} Route configuration
|
|
412
|
-
*
|
|
413
|
-
* Example in a component file:
|
|
414
|
-
* export class UserPage extends Component {
|
|
415
|
-
* static route = defineRoute({
|
|
416
|
-
* path: '/custom/:id',
|
|
417
|
-
* meta: { requiresAuth: true },
|
|
418
|
-
* beforeEnter: (to, from, next) => { ... }
|
|
419
|
-
* })
|
|
420
|
-
* }
|
|
421
|
-
*/
|
|
422
|
-
export function defineRoute(config) {
|
|
423
|
-
return config
|
|
424
|
-
}
|
|
425
|
-
|
|
426
|
-
/**
|
|
427
|
-
* Route decorator (works with class decorator syntax).
|
|
428
|
-
*
|
|
429
|
-
* @param {object} config - Route configuration
|
|
430
|
-
* @returns {Function} Class decorator
|
|
431
|
-
*
|
|
432
|
-
* Example:
|
|
433
|
-
* @route({ meta: { requiresAuth: true } })
|
|
434
|
-
* export class UserPage extends Component {
|
|
435
|
-
* // ...
|
|
436
|
-
* }
|
|
437
|
-
*/
|
|
438
|
-
export function route(config) {
|
|
439
|
-
return function decorator(ComponentClass) {
|
|
440
|
-
ComponentClass.route = config
|
|
441
|
-
return ComponentClass
|
|
442
|
-
}
|
|
443
|
-
}
|
|
444
|
-
|
|
445
|
-
/**
|
|
446
|
-
* Helper to create a catch-all route.
|
|
447
|
-
*
|
|
448
|
-
* @param {Function} component - 404 component
|
|
449
|
-
* @param {object} [options] - Additional options
|
|
450
|
-
* @returns {object} Catch-all route definition
|
|
451
|
-
*/
|
|
452
|
-
export function createCatchAllRoute(component, options = {}) {
|
|
453
|
-
return {
|
|
454
|
-
name: options.name || '404',
|
|
455
|
-
path: ['/:path(.*)'],
|
|
456
|
-
component,
|
|
457
|
-
params: ['path'],
|
|
458
|
-
meta: { ...options.meta, catchAll: true }
|
|
459
|
-
}
|
|
460
|
-
}
|
|
461
|
-
|
|
462
|
-
/**
|
|
463
|
-
* Helper to create a redirect route.
|
|
464
|
-
*
|
|
465
|
-
* @param {string} from - From path
|
|
466
|
-
* @param {string} to - To path (can contain params)
|
|
467
|
-
* @returns {object} Redirect route definition
|
|
468
|
-
*/
|
|
469
|
-
export function createRedirectRoute(from, to) {
|
|
470
|
-
// Remove leading slash and convert to dash-separated name
|
|
471
|
-
const name = from.replace(/^\//, '').replace(/[^a-zA-Z0-9]/g, '-').replace(/-+/g, '-')
|
|
472
|
-
return {
|
|
473
|
-
name: `redirect-${name}`,
|
|
474
|
-
path: [from],
|
|
475
|
-
redirect: to,
|
|
476
|
-
component: null
|
|
477
|
-
}
|
|
478
|
-
}
|