From b8a9473f7a43a72d4daea16cec21662582ee70a2 Mon Sep 17 00:00:00 2001 From: Eduardo San Martin Morote Date: Mon, 20 May 2024 16:38:43 +0200 Subject: [PATCH] fix: correctly trim extensions from routesFolder fix #274 --- src/codegen/generateRouteMap.spec.ts | 26 +++++++++++++++++++ src/core/context.ts | 2 +- src/core/tree.ts | 37 +++++++++++++++++++++++----- src/core/utils.ts | 22 ++++++++++++++++- 4 files changed, 79 insertions(+), 8 deletions(-) diff --git a/src/codegen/generateRouteMap.spec.ts b/src/codegen/generateRouteMap.spec.ts index 227ad290d..d7ef50f8d 100644 --- a/src/codegen/generateRouteMap.spec.ts +++ b/src/codegen/generateRouteMap.spec.ts @@ -2,6 +2,7 @@ import { describe, expect, it } from 'vitest' import { generateRouteNamedMap } from './generateRouteMap' import { PrefixTree } from '../core/tree' import { resolveOptions } from '../options' +import { resolve } from 'node:path' const DEFAULT_OPTIONS = resolveOptions({}) @@ -156,6 +157,31 @@ describe('generateRouteNamedMap', () => { }" `) }) + + // https://github.com/posva/unplugin-vue-router/issues/274 + it('removes routeFolders.extensions from the path', () => { + const tree = new PrefixTree( + resolveOptions({ + extensions: ['.pagina.vue'], + routesFolder: [ + { src: 'src/pages', extensions: ['.page.vue'] }, + { src: 'src/paginas' }, + ], + }) + ) + + tree.insert('other.pagina.vue', resolve('src/paginas/other.pagina.vue')) + tree.insert('index.page.vue', resolve('src/pages/index.page.vue')) + tree.insert('about.page.vue', resolve('src/pages/about.page.vue')) + + expect(formatExports(generateRouteNamedMap(tree))).toMatchInlineSnapshot(` + "export interface RouteNamedMap { + '/': RouteRecordInfo<'/', '/', Record, Record>, + '/about': RouteRecordInfo<'/about', '/about', Record, Record>, + '/other': RouteRecordInfo<'/other', '/other', Record, Record>, + }" + `) + }) }) /** diff --git a/src/core/context.ts b/src/core/context.ts index c37bd6037..8e70589b7 100644 --- a/src/core/context.ts +++ b/src/core/context.ts @@ -1,4 +1,4 @@ -import { ResolvedOptions } from '../options' +import { ResolvedOptions, RoutesFolderOption } from '../options' import { TreeNode, PrefixTree } from './tree' import { promises as fs } from 'fs' import { asRoutePath, ImportsMap, logTree, throttle } from './utils' diff --git a/src/core/tree.ts b/src/core/tree.ts index 0e97339a9..ff76ec33d 100644 --- a/src/core/tree.ts +++ b/src/core/tree.ts @@ -1,11 +1,11 @@ -import type { ResolvedOptions } from '../options' +import type { ResolvedOptions, RoutesFolderOption } from '../options' import { createTreeNodeValue, TreeNodeValueOptions, TreeRouteParam, } from './treeNodeValue' import type { TreeNodeValue } from './treeNodeValue' -import { trimExtension } from './utils' +import { resolveOverridableOption, trimExtension } from './utils' import { CustomRouteBlock } from './customBlock' import { RouteMeta } from 'vue-router' @@ -69,9 +69,16 @@ export class TreeNode { * @param filePath - file path, defaults to path for convenience and testing */ insert(path: string, filePath: string = path): TreeNode { + // find the `routesFolder` resolved option that matches the filepath + const folderOptions = findFolderOptions(this.options.routesFolder, filePath) + const { tail, segment, viewName, isComponent } = splitFilePath( path, - this.options + // use the correct extensions for the folder + resolveOverridableOption( + this.options.extensions, + folderOptions?.extensions + ) ) if (!this.children.has(segment)) { @@ -161,10 +168,14 @@ export class TreeNode { * @param path - path segment of the file */ remove(path: string) { + const folderOptions = findFolderOptions(this.options.routesFolder, path) // TODO: rename remove to removeChild const { tail, segment, viewName, isComponent } = splitFilePath( path, - this.options + resolveOverridableOption( + this.options.extensions, + folderOptions?.extensions + ) ) const child = this.children.get(segment) @@ -319,7 +330,7 @@ export class PrefixTree extends TreeNode { * * @param filePath - filePath to split */ -function splitFilePath(filePath: string, options: ResolvedOptions) { +function splitFilePath(filePath: string, extensions: string[]) { const slashPos = filePath.indexOf('/') let head = slashPos < 0 ? filePath : filePath.slice(0, slashPos) const tail = slashPos < 0 ? '' : filePath.slice(slashPos + 1) @@ -327,7 +338,7 @@ function splitFilePath(filePath: string, options: ResolvedOptions) { let segment = head // only the last segment can be a filename with an extension if (!tail) { - segment = trimExtension(head, options.extensions) + segment = trimExtension(head, extensions) } let viewName = 'default' @@ -348,3 +359,17 @@ function splitFilePath(filePath: string, options: ResolvedOptions) { isComponent, } } + +/** + * Find the folder options that match the file path. + * + * @param folderOptions `options.routesFolder` option + * @param filePath resolved file path + * @returns + */ +function findFolderOptions( + folderOptions: RoutesFolderOption[], + filePath: string +): RoutesFolderOption | undefined { + return folderOptions.find((folder) => filePath.includes(folder.src)) +} diff --git a/src/core/utils.ts b/src/core/utils.ts index 41d60accb..93b88e1c7 100644 --- a/src/core/utils.ts +++ b/src/core/utils.ts @@ -1,7 +1,11 @@ import { TreeNode } from './tree' import type { RouteRecordOverride, TreeRouteParam } from './treeNodeValue' import { pascalCase } from 'scule' -import { ResolvedOptions, RoutesFolderOption } from '../options' +import { + ResolvedOptions, + RoutesFolderOption, + _OverridableOption, +} from '../options' export function warn( msg: string, @@ -77,6 +81,22 @@ export function trimExtension( return path } +/** + * Resolves an overridable option by calling the function with the existing value if it's a function, otherwise + * returning the passed `value`. If `value` is undefined, it returns the `defaultValue` instead. + * + * @param defaultValue default value for the option + * @param value and overridable option + */ +export function resolveOverridableOption( + defaultValue: T, + value?: _OverridableOption +): T { + return typeof value === 'function' + ? (value as (existing: T) => T)(defaultValue) + : value ?? defaultValue +} + export function throttle(fn: () => void, wait: number, initialWait: number) { let pendingExecutionTimeout: ReturnType | null = null let pendingExecution = false