diff --git a/playground/src/routes/custom-name-and-path.vue b/playground/src/routes/custom-name-and-path.vue
new file mode 100644
index 000000000..52ce11206
--- /dev/null
+++ b/playground/src/routes/custom-name-and-path.vue
@@ -0,0 +1,6 @@
+
+{
+ "name": "the most rebel",
+ "path": "/most-rebel"
+}
+
diff --git a/playground/src/routes/custom-name.vue b/playground/src/routes/custom-name.vue
new file mode 100644
index 000000000..4b261be07
--- /dev/null
+++ b/playground/src/routes/custom-name.vue
@@ -0,0 +1,5 @@
+
+{
+ "name": "a rebel"
+}
+
diff --git a/playground/src/routes/custom-path.vue b/playground/src/routes/custom-path.vue
new file mode 100644
index 000000000..44370200e
--- /dev/null
+++ b/playground/src/routes/custom-path.vue
@@ -0,0 +1,5 @@
+
+{
+ "path": "/surprise-:id(\\d+)"
+}
+
diff --git a/playground/typed-router.d.ts b/playground/typed-router.d.ts
index 79e29c8b8..f05c41c91 100644
--- a/playground/typed-router.d.ts
+++ b/playground/typed-router.d.ts
@@ -23,11 +23,14 @@ declare module '@vue-router/routes' {
'/': RouteRecordInfo<'/', '/', Record, Record>,
'/[name]': RouteRecordInfo<'/[name]', '/:name', { name: _ParamValue }, { name: _ParamValue }>,
'/[...path]': RouteRecordInfo<'/[...path]', '/:path(.*)', { path: _ParamValue }, { path: _ParamValue }>,
+ 'the most rebel': RouteRecordInfo<'the most rebel', '/most-rebel', Record, Record>,
+ '/custom-path': RouteRecordInfo<'/custom-path', '/surprise-:id(\d+)', Record, Record>,
'/about': RouteRecordInfo<'/about', '/about', Record, Record>,
'/articles': RouteRecordInfo<'/articles', '/articles', Record, Record>,
'/articles/': RouteRecordInfo<'/articles/', '/articles/', Record, Record>,
'/articles/[id]': RouteRecordInfo<'/articles/[id]', '/articles/:id', { id: _ParamValue }, { id: _ParamValue }>,
'/articles/[id]+': RouteRecordInfo<'/articles/[id]+', '/articles/:id+', { id: _ParamValueOneOrMore }, { id: _ParamValueOneOrMore }>,
+ 'a rebel': RouteRecordInfo<'a rebel', '/custom-name', Record, Record>,
'/deep/nesting/works/[[files]]+': RouteRecordInfo<'/deep/nesting/works/[[files]]+', '/deep/nesting/works/:files*', { files?: _ParamValueZeroOrMore }, { files?: _ParamValueZeroOrMore }>,
'/deep/nesting/works/too': RouteRecordInfo<'/deep/nesting/works/too', '/deep/nesting/works/too', Record, Record>,
'/multiple-[a]-[b]-params': RouteRecordInfo<'/multiple-[a]-[b]-params', '/multiple-:a-:b-params', { a: _ParamValue, b: _ParamValue }, { a: _ParamValue, b: _ParamValue }>,
diff --git a/src/codegen/generateRouteMap.ts b/src/codegen/generateRouteMap.ts
index 29fa11500..5fe10d8ff 100644
--- a/src/codegen/generateRouteMap.ts
+++ b/src/codegen/generateRouteMap.ts
@@ -18,9 +18,7 @@ ${node.getSortedChildren().map(generateRouteNamedMap).join('')}}`
// if the node has a filePath, it's a component, it has a routeName and it should be referenced in the RouteNamedMap
// otherwise it should be skipped to avoid navigating to a route that doesn't render anything
(node.value.filePaths.size
- ? ` '${node.options.getRouteName(node)}': ${generateRouteRecordInfo(
- node
- )},\n`
+ ? ` '${node.name}': ${generateRouteRecordInfo(node)},\n`
: '') +
(node.children.size > 0
? node.getSortedChildren().map(generateRouteNamedMap).join('\n')
@@ -29,8 +27,8 @@ ${node.getSortedChildren().map(generateRouteNamedMap).join('')}}`
}
export function generateRouteRecordInfo(node: TreeLeaf) {
- return `RouteRecordInfo<'${node.options.getRouteName(node)}', '${
- node.value.path
+ return `RouteRecordInfo<'${node.name}', '${
+ node.fullPath
}', ${generateRouteParams(node, true)}, ${generateRouteParams(node, false)}>`
}
diff --git a/src/codegen/generateRouteRecords.ts b/src/codegen/generateRouteRecords.ts
index eabccc03a..8acbbcf84 100644
--- a/src/codegen/generateRouteRecords.ts
+++ b/src/codegen/generateRouteRecords.ts
@@ -18,11 +18,14 @@ ${node
const startIndent = ' '.repeat(indent * 2)
const indentStr = ' '.repeat((indent + 1) * 2)
- const name = node.options.getRouteName(node)
+ // TODO: should meta be defined a different way to allow preserving imports?
+ // const meta = node.value.overrides.meta
return `${startIndent}{
-${indentStr}path: '${(parent ? '' : '/') + node.value.pathSegment}',
-${indentStr}${node.value.filePaths.size ? `name: '${name}',` : '/* no name */'}
+${indentStr}path: '${node.path}',
+${indentStr}${
+ node.value.filePaths.size ? `name: '${node.name}',` : '/* no name */'
+ }
${indentStr}${
node.value.filePaths.size
? generateRouteRecordComponent(node, indentStr)
diff --git a/src/core/context.ts b/src/core/context.ts
index 134829490..d22251b08 100644
--- a/src/core/context.ts
+++ b/src/core/context.ts
@@ -14,6 +14,7 @@ import { generateRouteRecord } from '../codegen/generateRouteRecords'
import fg from 'fast-glob'
import { resolve } from 'pathe'
import { ServerContext } from '../options'
+import { getRouteBlock, parseCustomBlock } from './customBlock'
export function createRoutesContext(options: Required) {
const { dts: preferDTS, root } = options
@@ -72,21 +73,30 @@ export function createRoutesContext(options: Required) {
)
).flat()
- for (const file of files) {
- addPage(file)
- }
+ await Promise.all(files.map((file) => addPage(file)))
+
await _writeConfigFiles()
}
- function addPage(path: string) {
+ async function addPage(path: string) {
+ const routePath = stripRouteFolder(path)
+ const routeBlock = await getRouteBlock(path, options)
log('added', path)
- const route = stripRouteFolder(path)
+ if (routeBlock) console.log(routeBlock)
// TODO: handle top level named view HMR
- routeTree.insert(
- route,
+ const node = routeTree.insert(
+ routePath,
// './' + path
resolve(root, path)
)
+ node.mergeCustomRouteBlock(routeBlock)
+ }
+
+ async function updatePage(path: string) {
+ const routeBlock = await getRouteBlock(path, options)
+ // TODO:
+ // const node = routeTree.findByPath(path)
+ // node.mergeCustomRouteBlock(routeBlock)
}
function removePage(path: string) {
@@ -100,14 +110,15 @@ export function createRoutesContext(options: Required) {
.on('change', (path) => {
// TODO: parse defineRoute macro?
log('change', path)
+ // updatePage(path)
// writeConfigFiles()
})
- .on('add', (path) => {
- addPage(path)
+ .on('add', async (path) => {
+ await addPage(path)
writeConfigFiles()
})
- .on('unlink', (path) => {
- removePage(path)
+ .on('unlink', async (path) => {
+ await removePage(path)
writeConfigFiles()
})
}
diff --git a/src/core/customBlock.ts b/src/core/customBlock.ts
index fa7ac9800..24171dbe4 100644
--- a/src/core/customBlock.ts
+++ b/src/core/customBlock.ts
@@ -19,12 +19,25 @@ export async function getRouteBlock(path: string, options: Required) {
result = parseCustomBlock(blockStr, path, options)
}
+ // validation
+ if (result) {
+ if (result.path != null && !result.path.startsWith('/')) {
+ console.error(`Overridden path must start with "/". Found in "${path}".`)
+ }
+ }
+
return result
}
-export type CustomRouteBlock = Partial<
- Omit
->
+export interface CustomRouteBlock
+ extends Partial<
+ Omit<
+ RouteRecordRaw,
+ 'components' | 'component' | 'children' | 'beforeEnter' | 'name'
+ >
+ > {
+ name?: string
+}
function parseCustomBlock(
block: SFCBlock,
diff --git a/src/core/tree.ts b/src/core/tree.ts
index e2cfd1424..1cff9b007 100644
--- a/src/core/tree.ts
+++ b/src/core/tree.ts
@@ -57,17 +57,12 @@ export class TreeLeaf {
mergeCustomRouteBlock(routeBlock: CustomRouteBlock | undefined) {
if (!routeBlock) return
- this.value.name = routeBlock.name
- if (routeBlock.path != null) {
- this.value.path = routeBlock.path
- this.value.pathSegment = routeBlock.path
- }
- this.value.meta = routeBlock.meta
+ this.value.overrides = routeBlock
}
getSortedChildren() {
return Array.from(this.children.values()).sort((a, b) =>
- a.value.path.localeCompare(b.value.path)
+ a.path.localeCompare(b.path)
)
}
@@ -77,7 +72,7 @@ export class TreeLeaf {
const child = this.children.get(segment)
if (!child) {
throw new Error(
- `Cannot Delete "${path}". "${segment}" not found at "${this.value.path}".`
+ `Cannot Delete "${path}". "${segment}" not found at "${this.path}".`
)
}
@@ -99,6 +94,21 @@ export class TreeLeaf {
}
}
+ get path() {
+ return (
+ this.value.overrides.path ??
+ (this.parent ? '' : '/') + this.value.pathSegment
+ )
+ }
+
+ get fullPath() {
+ return this.value.overrides.path ?? this.value.path
+ }
+
+ get name() {
+ return this.value.overrides.name || this.options.getRouteName(this)
+ }
+
isRoot() {
return this.value.path === '/' && !this.value.filePaths.size
}
diff --git a/src/core/treeLeafValue.ts b/src/core/treeLeafValue.ts
index 7d2f3cc15..fafe2dabf 100644
--- a/src/core/treeLeafValue.ts
+++ b/src/core/treeLeafValue.ts
@@ -32,15 +32,17 @@ class _TreeLeafValueBase {
*/
path: string
- /**
- * Overridden name for this route.
- */
- name?: RouteRecordName
-
- /**
- * Meta of the route
- */
- meta?: RouteMeta
+ overrides: {
+ path?: string
+ /**
+ * Overridden name for this route.
+ */
+ name?: string
+ /**
+ * Meta of the route
+ */
+ meta?: RouteMeta
+ } = {}
/**
* Component path that maps to a view name, which is used for vue-router's named view feature.