Skip to content

Commit

Permalink
feat: handle long extensions
Browse files Browse the repository at this point in the history
Close #101
  • Loading branch information
posva committed Jan 3, 2023
1 parent a58e42c commit d93db33
Show file tree
Hide file tree
Showing 10 changed files with 93 additions and 11 deletions.
3 changes: 2 additions & 1 deletion .vscode/settings.json
@@ -1,3 +1,4 @@
{
"prettier.enable": true
"prettier.enable": true,
"testing.automaticallyOpenPeekView": "never"
}
5 changes: 5 additions & 0 deletions playground/src/pages/with-extension.page.vue
@@ -0,0 +1,5 @@
<template>
<main>
<h1>I have an extension</h1>
</main>
</template>
1 change: 1 addition & 0 deletions playground/typed-router.d.ts
Expand Up @@ -63,6 +63,7 @@ declare module 'vue-router/auto/routes' {
'/users/[id]': RouteRecordInfo<'/users/[id]', '/users/:id', { id: ParamValue<true> }, { id: ParamValue<false> }>,
'/users/[id].edit': RouteRecordInfo<'/users/[id].edit', '/users/:id/edit', { id: ParamValue<true> }, { id: ParamValue<false> }>,
'/users/nested.route.deep': RouteRecordInfo<'/users/nested.route.deep', '/users/nested/route/deep', Record<never, never>, Record<never, never>>,
'/with-extension': RouteRecordInfo<'/with-extension', '/with-extension', Record<never, never>, Record<never, never>>,
}
}

Expand Down
2 changes: 1 addition & 1 deletion playground/vite.config.ts
Expand Up @@ -7,7 +7,6 @@ import {
getFileBasedRouteName,
getPascalCaseRouteName,
VueRouterAutoImports,
DefinePage,
} from '../src'
import Vue from '@vitejs/plugin-vue'
import AutoImport from 'unplugin-auto-import/vite'
Expand All @@ -25,6 +24,7 @@ export default defineConfig({
// DefinePage.vite(),
VueRouter({
dataFetching: true,
extensions: ['.page.vue', '.vue'],
routesFolder: [
// can add multiple routes folders
{
Expand Down
1 change: 0 additions & 1 deletion src/core/definePage.ts
Expand Up @@ -121,7 +121,6 @@ export function extractDefinePageNameAndPath(
}

const definePageNode = definePageNodes[0]
const setupOffset = scriptSetup.loc.start.offset

const routeRecord = definePageNode.arguments[0]
if (routeRecord.type !== 'ObjectExpression') {
Expand Down
34 changes: 34 additions & 0 deletions src/core/tree.spec.ts
Expand Up @@ -347,4 +347,38 @@ describe('Tree', () => {
expect(child.name).toBe('/a/')
expect(child.fullPath).toBe('/a')
})

it('handles long extensions', () => {
const tree = createPrefixTree({
...DEFAULT_OPTIONS,
extensions: ['.page.vue'],
})
tree.insert('a.page.vue')
tree.insert('nested/b/c.page.vue')
expect(tree.children.size).toBe(2)

console.log(tree.children.keys())

const a = tree.children.get('a')!
expect(a).toBeDefined()
expect(a.value.filePaths.get('default')).toBe('a.page.vue')
expect(a.fullPath).toBe('/a')

const nested = tree.children.get('nested')!
expect(nested).toBeDefined()
expect(nested.children.size).toBe(1)
const b = nested.children.get('b')!
expect(b).toBeDefined()
expect(b.children.size).toBe(1)
const c = b.children.get('c')!
expect(c).toBeDefined()
expect(c.value.filePaths.get('default')).toBe('nested/b/c.page.vue')
expect(c.fullPath).toBe('/nested/b/c')

tree.insert('a/nested.page.vue')
const aNested = a.children.get('nested')!
expect(aNested).toBeDefined()
expect(aNested.value.filePaths.get('default')).toBe('a/nested.page.vue')
expect(aNested.fullPath).toBe('/a/nested')
})
})
14 changes: 10 additions & 4 deletions src/core/tree.ts
Expand Up @@ -43,7 +43,10 @@ export class TreeNode {
* @param filePath - file path, defaults to path for convenience and testing
*/
insert(path: string, filePath: string = path): TreeNode {
const { tail, segment, viewName, isComponent } = splitFilePath(path)
const { tail, segment, viewName, isComponent } = splitFilePath(
path,
this.options
)

if (!this.children.has(segment)) {
this.children.set(segment, new TreeNode(this.options, segment, this))
Expand Down Expand Up @@ -76,7 +79,10 @@ export class TreeNode {
* @param path - file path of the file
*/
remove(path: string) {
const { tail, segment, viewName, isComponent } = splitFilePath(path)
const { tail, segment, viewName, isComponent } = splitFilePath(
path,
this.options
)

const child = this.children.get(segment)
if (!child) {
Expand Down Expand Up @@ -175,15 +181,15 @@ export function createPrefixTree(options: ResolvedOptions) {
*
* @param filePath - filePath to split
*/
function splitFilePath(filePath: string) {
function splitFilePath(filePath: string, options: ResolvedOptions) {
const slashPos = filePath.indexOf('/')
let head = slashPos < 0 ? filePath : filePath.slice(0, slashPos)
const tail = slashPos < 0 ? '' : filePath.slice(slashPos + 1)

let segment = head
// only the last segment can be a filename with an extension
if (!tail) {
segment = trimExtension(head)
segment = trimExtension(head, options.extensions)
}
let viewName = 'default'

Expand Down
18 changes: 18 additions & 0 deletions src/core/utils.spec.ts
@@ -0,0 +1,18 @@
import { describe, expect, it } from 'vitest'
import { trimExtension } from './utils'

describe('utils', () => {
describe('trimExtension', () => {
it('trims when found', () => {
expect(trimExtension('foo.vue', ['.vue'])).toBe('foo')
expect(trimExtension('foo.vue', ['.ts', '.vue'])).toBe('foo')
expect(trimExtension('foo.ts', ['.ts', '.vue'])).toBe('foo')
expect(trimExtension('foo.page.vue', ['.page.vue'])).toBe('foo')
})

it('skips if not found', () => {
expect(trimExtension('foo.vue', ['.page.vue'])).toBe('foo.vue')
expect(trimExtension('foo.page.vue', ['.vue'])).toBe('foo.page')
})
})
})
20 changes: 16 additions & 4 deletions src/core/utils.ts
@@ -1,6 +1,7 @@
import { TreeNode } from './tree'
import type { RouteRecordOverride, TreeRouteParam } from './treeNodeValue'
import { pascalCase } from 'scule'
import { ResolvedOptions } from '../options'

export type Awaitable<T> = T | PromiseLike<T>

Expand Down Expand Up @@ -29,7 +30,7 @@ function printTree(
const hasNext = index++ < total - 1
const { children } = child

treeStr += `${`${parentPre}${hasNext ? '├' : '└'} `}${child}\n`
treeStr += `${`${parentPre}${hasNext ? '├' : '└'}─ `}${child}\n`

if (children) {
treeStr += printTree(
Expand Down Expand Up @@ -57,9 +58,20 @@ function printTree(
export const isArray: (arg: ArrayLike<any> | any) => arg is ReadonlyArray<any> =
Array.isArray

export function trimExtension(path: string) {
const lastDot = path.lastIndexOf('.')
return lastDot < 0 ? path : path.slice(0, lastDot)
export function trimExtension(
path: string,
extensions: ResolvedOptions['extensions']
) {
for (const extension of extensions) {
const lastDot = path.lastIndexOf(extension)
if (lastDot > -1) {
// usually only one extension should match
return path.slice(0, lastDot)
}
}

// no extension found, return the original path
return path
}

export function throttle(fn: () => void, wait: number, initialWait: number) {
Expand Down
6 changes: 6 additions & 0 deletions src/options.ts
Expand Up @@ -131,6 +131,12 @@ export function resolveOptions(options: Options): ResolvedOptions {
src: resolve(root, routeOption.src),
}))

if (options.extensions) {
// sort extensions by length to ensure that the longest one is used first
// e.g. ['.vue', '.page.vue'] -> ['.page.vue', '.vue'] as both would match and order matters
options.extensions.sort((a, b) => b.length - a.length)
}

return {
...DEFAULT_OPTIONS,
...options,
Expand Down

0 comments on commit d93db33

Please sign in to comment.