Skip to content

Commit

Permalink
fix: keep parent override in children
Browse files Browse the repository at this point in the history
Fix #189
  • Loading branch information
posva committed Jun 23, 2023
1 parent 3ba54ca commit f651961
Show file tree
Hide file tree
Showing 4 changed files with 116 additions and 30 deletions.
14 changes: 14 additions & 0 deletions src/codegen/generateRouteMap.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,20 @@ describe('generateRouteNamedMap', () => {
}"
`)
})

it('keeps parent path overrides', () => {
const tree = createPrefixTree(DEFAULT_OPTIONS)
const parent = tree.insert('parent.vue')
const child = tree.insert('parent/child.vue')
parent.value.setOverride('parent.vue', { path: '/' })
expect(child.fullPath).toBe('/child')
expect(formatExports(generateRouteNamedMap(tree))).toMatchInlineSnapshot(`
"export interface RouteNamedMap {
'/parent': RouteRecordInfo<'/parent', '/', Record<never, never>, Record<never, never>>,
'/parent/child': RouteRecordInfo<'/parent/child', '/child', Record<never, never>, Record<never, never>>,
}"
`)
})
})

/**
Expand Down
15 changes: 9 additions & 6 deletions src/core/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,19 +105,22 @@ export function createRoutesContext(options: ResolvedOptions) {
await _writeConfigFiles()
}

async function writeRouteInfoToNode(node: TreeNode, path: string) {
const content = await fs.readFile(path, 'utf8')
async function writeRouteInfoToNode(node: TreeNode, filePath: string) {
const content = await fs.readFile(filePath, 'utf8')
// TODO: cache the result of parsing the SFC so the transform can reuse the parsing
node.hasDefinePage = content.includes('definePage')
const [definedPageNameAndPath, routeBlock] = await Promise.all([
extractDefinePageNameAndPath(content, path),
getRouteBlock(path, options),
extractDefinePageNameAndPath(content, filePath),
getRouteBlock(filePath, options),
])
// TODO: should warn if hasDefinePage and customRouteBlock
// if (routeBlock) log(routeBlock)
node.setCustomRouteBlock(path, { ...routeBlock, ...definedPageNameAndPath })
node.setCustomRouteBlock(filePath, {
...routeBlock,
...definedPageNameAndPath,
})
node.value.includeLoaderGuard =
options.dataFetching && (await hasNamedExports(path))
options.dataFetching && (await hasNamedExports(filePath))
}

async function addPage(
Expand Down
45 changes: 38 additions & 7 deletions src/core/tree.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,11 +39,22 @@ export class TreeNode {
*/
hasDefinePage: boolean = false

constructor(options: TreeNodeOptions, filePath: string, parent?: TreeNode) {
/**
* Creates a new tree node.
*
* @param options - TreeNodeOptions shared by all nodes
* @param pathSegment - path segment of this node e.g. `users` or `:id`
* @param parent
*/
constructor(
options: TreeNodeOptions,
pathSegment: string,
parent?: TreeNode
) {
this.options = options
this.parent = parent
this.value = createTreeNodeValue(
filePath,
pathSegment,
parent?.value,
options.treeNodeOptions || options.pathParser
)
Expand Down Expand Up @@ -78,7 +89,8 @@ export class TreeNode {
}

/**
* Adds a path to the tree. `path` cannot start with a `/`.
* Adds a path that has already been parsed to the tree. `path` cannot start with a `/`. This method is similar to
* `insert` but the path argument should be already parsed. e.g. `users/:id` for a file named `users/[id].vue`.
*
* @param path - path segment to insert, already parsed (e.g. users/:id)
* @param filePath - file path, defaults to path for convenience and testing
Expand Down Expand Up @@ -109,8 +121,18 @@ export class TreeNode {
return node
}

setCustomRouteBlock(path: string, routeBlock: CustomRouteBlock | undefined) {
this.value.setOverride(path, routeBlock)
/**
* Saves a custom route block for a specific file path. The file path is used as a key. Some special file paths will
* have a lower or higher priority.
*
* @param filePath - file path where the custom block is located
* @param routeBlock - custom block to set
*/
setCustomRouteBlock(
filePath: string,
routeBlock: CustomRouteBlock | undefined
) {
this.value.setOverride(filePath, routeBlock)
}

getSortedChildren() {
Expand All @@ -123,7 +145,6 @@ export class TreeNode {
* Delete and detach itself from the tree.
*/
delete() {
// TODO: rename remove to removeChild
if (!this.parent) {
throw new Error('Cannot delete the root node.')
}
Expand All @@ -139,6 +160,7 @@ export class TreeNode {
* @param path - path segment of the file
*/
remove(path: string) {
// TODO: rename remove to removeChild
const { tail, segment, viewName, isComponent } = splitFilePath(
path,
this.options
Expand Down Expand Up @@ -272,13 +294,19 @@ export class PrefixTree extends TreeNode {
return node
}

/**
* Returns the tree node of the given file path.
*
* @param filePath - file path of the tree node to get
*/
getChild(filePath: string) {
return this.map.get(filePath)
}

/**
* Removes the tree node of the given file path.
*
* @param filePath -
* @param filePath - file path of the tree node to remove
*/
removeChild(filePath: string) {
if (this.map.has(filePath)) {
Expand All @@ -288,6 +316,9 @@ export class PrefixTree extends TreeNode {
}
}

/**
* @deprecated Use `new PrefixTree()` instead.
*/
export function createPrefixTree(options: ResolvedOptions) {
return new PrefixTree(options)
}
Expand Down
72 changes: 55 additions & 17 deletions src/core/treeNodeValue.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ class _TreeNodeValueBase {
* flag based on the type of the segment
*/
_type: TreeNodeType

parent: TreeNodeValue | undefined

/**
* segment as defined by the file structure e.g. keeps the `index` name
*/
Expand All @@ -37,15 +40,11 @@ class _TreeNodeValueBase {
*/
subSegments: SubSegment[]

/**
* fullPath of the node based on parent nodes
*/
path: string

/**
* Overrides defined by each file. The map is necessary to handle named views.
*/
private _overrides = new Map<string, RouteRecordOverride>()
// TODO: cache the overrides generation

/**
* Should we add the loader guard to the route record.
Expand All @@ -69,12 +68,19 @@ class _TreeNodeValueBase {
this.rawSegment = rawSegment
this.pathSegment = pathSegment
this.subSegments = subSegments
const parentPath = parent?.path
this.path =
// both the root record and the index record have a path of /
(!parentPath || parentPath === '/') && this.pathSegment === ''
? '/'
: joinPath(parent?.path || '', this.pathSegment)
this.parent = parent
}

/**
* fullPath of the node based on parent nodes
*/
get path(): string {
const parentPath = this.parent?.path
// both the root record and the index record have a path of /
const pathSegment = this.overrides.path ?? this.pathSegment
return (!parentPath || parentPath === '/') && pathSegment === ''
? '/'
: joinPath(parentPath || '', pathSegment)
}

toString(): string {
Expand Down Expand Up @@ -105,14 +111,14 @@ class _TreeNodeValueBase {
}, {} as RouteRecordOverride)
}

setOverride(path: string, routeBlock: CustomRouteBlock | undefined) {
this._overrides.set(path, routeBlock || {})
setOverride(filePath: string, routeBlock: CustomRouteBlock | undefined) {
this._overrides.set(filePath, routeBlock || {})
}

/**
* Remove all overrides for a given key.
*
* @param key - key to remove from the override
* @param key - key to remove from the override, e.g. path, name, etc
*/
removeOverride(key: keyof CustomRouteBlock) {
this._overrides.forEach((routeBlock) => {
Expand All @@ -121,15 +127,35 @@ class _TreeNodeValueBase {
})
}

mergeOverride(path: string, routeBlock: CustomRouteBlock) {
const existing = this._overrides.get(path) || {}
this._overrides.set(path, mergeRouteRecordOverride(existing, routeBlock))
/**
* Add an override to the current node by merging with the existing values.
*
* @param filePath - The file path to add to the override
* @param routeBlock - The route block to add to the override
*/
mergeOverride(filePath: string, routeBlock: CustomRouteBlock) {
const existing = this._overrides.get(filePath) || {}
this._overrides.set(
filePath,
mergeRouteRecordOverride(existing, routeBlock)
)
}

/**
* Add an override to the current node using the special file path `@@edits` that makes this added at build time.
*
* @param routeBlock - The route block to add to the override
*/
addEditOverride(routeBlock: CustomRouteBlock) {
return this.mergeOverride(EDITS_OVERRIDE_NAME, routeBlock)
}

/**
* Set a specific value in the _edits_ override.
*
* @param key - key to set in the override, e.g. path, name, etc
* @param value - value to set in the override
*/
setEditOverride<K extends keyof RouteRecordOverride>(
key: K,
value: RouteRecordOverride[K]
Expand Down Expand Up @@ -193,6 +219,13 @@ export interface TreeNodeValueOptions extends ParseSegmentOptions {
format?: 'file' | 'path'
}

/**
* Creates a new TreeNodeValue based on the segment. The result can be a static segment or a param segment.
*
* @param segment - path segment
* @param parent - parent node
* @param options - options
*/
export function createTreeNodeValue(
segment: string,
parent?: TreeNodeValue,
Expand Down Expand Up @@ -505,6 +538,11 @@ function parseRawPathSegment(
]
}

/**
* Helper function to create an empty route param used by the parser.
*
* @returns an empty route param
*/
function createEmptyRouteParam(): TreeRouteParam {
return {
paramName: '',
Expand Down

0 comments on commit f651961

Please sign in to comment.