Skip to content

Commit

Permalink
feat: parse route custom block
Browse files Browse the repository at this point in the history
  • Loading branch information
posva committed Jul 2, 2022
1 parent cf59d24 commit 963d1ca
Show file tree
Hide file tree
Showing 10 changed files with 95 additions and 39 deletions.
6 changes: 6 additions & 0 deletions playground/src/routes/custom-name-and-path.vue
@@ -0,0 +1,6 @@
<route lang="json">
{
"name": "the most rebel",
"path": "/most-rebel"
}
</route>
5 changes: 5 additions & 0 deletions playground/src/routes/custom-name.vue
@@ -0,0 +1,5 @@
<route lang="json">
{
"name": "a rebel"
}
</route>
5 changes: 5 additions & 0 deletions playground/src/routes/custom-path.vue
@@ -0,0 +1,5 @@
<route lang="json">
{
"path": "/surprise-:id(\\d+)"
}
</route>
3 changes: 3 additions & 0 deletions playground/typed-router.d.ts
Expand Up @@ -23,11 +23,14 @@ declare module '@vue-router/routes' {
'/': RouteRecordInfo<'/', '/', Record<never, never>, Record<never, never>>,
'/[name]': RouteRecordInfo<'/[name]', '/:name', { name: _ParamValue<true> }, { name: _ParamValue<false> }>,
'/[...path]': RouteRecordInfo<'/[...path]', '/:path(.*)', { path: _ParamValue<true> }, { path: _ParamValue<false> }>,
'the most rebel': RouteRecordInfo<'the most rebel', '/most-rebel', Record<never, never>, Record<never, never>>,
'/custom-path': RouteRecordInfo<'/custom-path', '/surprise-:id(\d+)', Record<never, never>, Record<never, never>>,
'/about': RouteRecordInfo<'/about', '/about', Record<never, never>, Record<never, never>>,
'/articles': RouteRecordInfo<'/articles', '/articles', Record<never, never>, Record<never, never>>,
'/articles/': RouteRecordInfo<'/articles/', '/articles/', Record<never, never>, Record<never, never>>,
'/articles/[id]': RouteRecordInfo<'/articles/[id]', '/articles/:id', { id: _ParamValue<true> }, { id: _ParamValue<false> }>,
'/articles/[id]+': RouteRecordInfo<'/articles/[id]+', '/articles/:id+', { id: _ParamValueOneOrMore<true> }, { id: _ParamValueOneOrMore<false> }>,
'a rebel': RouteRecordInfo<'a rebel', '/custom-name', Record<never, never>, Record<never, never>>,
'/deep/nesting/works/[[files]]+': RouteRecordInfo<'/deep/nesting/works/[[files]]+', '/deep/nesting/works/:files*', { files?: _ParamValueZeroOrMore<true> }, { files?: _ParamValueZeroOrMore<false> }>,
'/deep/nesting/works/too': RouteRecordInfo<'/deep/nesting/works/too', '/deep/nesting/works/too', Record<never, never>, Record<never, never>>,
'/multiple-[a]-[b]-params': RouteRecordInfo<'/multiple-[a]-[b]-params', '/multiple-:a-:b-params', { a: _ParamValue<true>, b: _ParamValue<true> }, { a: _ParamValue<false>, b: _ParamValue<false> }>,
Expand Down
8 changes: 3 additions & 5 deletions src/codegen/generateRouteMap.ts
Expand Up @@ -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')
Expand All @@ -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)}>`
}

Expand Down
9 changes: 6 additions & 3 deletions src/codegen/generateRouteRecords.ts
Expand Up @@ -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)
Expand Down
33 changes: 22 additions & 11 deletions src/core/context.ts
Expand Up @@ -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<Options>) {
const { dts: preferDTS, root } = options
Expand Down Expand Up @@ -72,21 +73,30 @@ export function createRoutesContext(options: Required<Options>) {
)
).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) {
Expand All @@ -100,14 +110,15 @@ export function createRoutesContext(options: Required<Options>) {
.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()
})
}
Expand Down
19 changes: 16 additions & 3 deletions src/core/customBlock.ts
Expand Up @@ -19,12 +19,25 @@ export async function getRouteBlock(path: string, options: Required<Options>) {
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<RouteRecordRaw, 'components' | 'component' | 'children' | 'beforeEnter'>
>
export interface CustomRouteBlock
extends Partial<
Omit<
RouteRecordRaw,
'components' | 'component' | 'children' | 'beforeEnter' | 'name'
>
> {
name?: string
}

function parseCustomBlock(
block: SFCBlock,
Expand Down
26 changes: 18 additions & 8 deletions src/core/tree.ts
Expand Up @@ -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)
)
}

Expand All @@ -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}".`
)
}

Expand All @@ -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
}
Expand Down
20 changes: 11 additions & 9 deletions src/core/treeLeafValue.ts
Expand Up @@ -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.
Expand Down

0 comments on commit 963d1ca

Please sign in to comment.