Skip to content

Commit 0711dff

Browse files
giaBaoJSposva
andauthored
fix(unplugin): sanitize named view in definePage import (#2809)
* fix(unplugin): sanitize named view in definePage import A named view whose name is not a valid identifier (e.g. `index@side-bar.vue`) that uses definePage() with runtime properties generated `import _definePage_side-bar_0 from ...`, which fails to parse. * refactor: handle more cases --------- Co-authored-by: Eduardo San Martin Morote <posva13@gmail.com>
1 parent 610644a commit 0711dff

5 files changed

Lines changed: 55 additions & 4 deletions

File tree

‎packages/router/src/unplugin/codegen/generateRouteRecords.spec.ts‎

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,27 @@ describe('generateRouteRecord', () => {
9494
expect(generateRouteRecordSimple(tree)).toMatchSnapshot()
9595
})
9696

97+
it('generates valid definePage imports for any named view name', () => {
98+
const tree = new PrefixTree(DEFAULT_OPTIONS)
99+
tree.insert('foo', 'foo.vue')
100+
tree.insert('foo@side-bar', 'foo@side-bar.vue')
101+
const node = tree.insert('foo@nav.top', 'foo@nav.top.vue')
102+
node.setDefinePageImport('foo@side-bar.vue', true)
103+
node.setDefinePageImport('foo@nav.top.vue', true)
104+
105+
const importsMap = new ImportsMap()
106+
const routes = generateRouteRecords(tree, DEFAULT_OPTIONS, importsMap)
107+
108+
expect(importsMap.toString()).toMatchInlineSnapshot(`
109+
"import _definePage_side_2d_bar_0 from 'foo@side-bar.vue?definePage&vue&lang.tsx'
110+
import _definePage_nav_2e_top_1 from 'foo@nav.top.vue?definePage&vue&lang.tsx'
111+
import { _mergeRouteRecord } from 'vue-router/experimental'
112+
"
113+
`)
114+
expect(routes).toContain('_definePage_side_2d_bar_0,\n')
115+
expect(routes).toContain('_definePage_nav_2e_top_1\n')
116+
})
117+
97118
it('nested children', () => {
98119
const tree = new PrefixTree(DEFAULT_OPTIONS)
99120
tree.insert('a/a', 'a/a.vue')

‎packages/router/src/unplugin/codegen/generateRouteRecords.ts‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { getLang } from '@vue-macros/common'
22
import type { TreeNode } from '../core/tree'
33
import type { ImportsMap } from '../core/utils'
44
import { type ResolvedOptions } from '../options'
5-
import { pad, toStringLiteral } from '../utils'
5+
import { encodeImportIdentifierPart, pad, toStringLiteral } from '../utils'
66

77
/**
88
* Generate the route records for the given node.
@@ -39,7 +39,7 @@ ${node
3939
if (node.needsDefinePageImport) {
4040
for (const [name, filePath] of node.value.components) {
4141
if (!node.fileNeedsDefinePageImport(filePath)) continue
42-
const pageDataImport = `_definePage_${name}_${importsMap.size}`
42+
const pageDataImport = `_definePage_${encodeImportIdentifierPart(name)}_${importsMap.size}`
4343
definePageDataList.push(pageDataImport)
4444
const lang = getLang(filePath)
4545
importsMap.addDefault(

‎packages/router/src/unplugin/codegen/generateRouteResolver.spec.ts‎

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1297,6 +1297,29 @@ describe('generateRouteResolver', () => {
12971297
expect(resolver).not.toContain('_definePage_sidebar_')
12981298
})
12991299

1300+
it('generates valid definePage imports for any named view name', () => {
1301+
const tree = new PrefixTree(DEFAULT_OPTIONS)
1302+
tree.insert('dashboard', 'dashboard.vue')
1303+
const dashboardNode = tree.insert(
1304+
'dashboard@side-bar',
1305+
'dashboard@side-bar.vue'
1306+
)
1307+
dashboardNode.setDefinePageImport('dashboard@side-bar.vue', true)
1308+
1309+
const importsMap = new ImportsMap()
1310+
const resolver = generateRouteResolver(
1311+
tree,
1312+
DEFAULT_OPTIONS,
1313+
importsMap,
1314+
new Map()
1315+
)
1316+
1317+
expect(importsMap.toString()).toContain(
1318+
`import _definePage_side_2d_bar_0 from 'dashboard@side-bar.vue?definePage&vue&lang.tsx'`
1319+
)
1320+
expect(resolver).toContain('_definePage_side_2d_bar_0\n')
1321+
})
1322+
13001323
it('includes query property in route records with query params', () => {
13011324
const tree = new PrefixTree(DEFAULT_OPTIONS)
13021325
tree.insert('search', 'search.vue')

‎packages/router/src/unplugin/codegen/generateRouteResolver.ts‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { getLang } from '@vue-macros/common'
22
import { PrefixTree, type TreeNode } from '../core/tree'
33
import type { ImportsMap } from '../core/utils'
44
import { type ResolvedOptions } from '../options'
5-
import { toStringLiteral, ts } from '../utils'
5+
import { encodeImportIdentifierPart, toStringLiteral, ts } from '../utils'
66
import type { ParamParsersMap } from './generateParamParsers'
77
import {
88
generatePathParamsOptions,
@@ -164,7 +164,7 @@ export function generateRouteRecord({
164164
if (node.needsDefinePageImport) {
165165
for (const [name, filePath] of node.value.components) {
166166
if (!node.fileNeedsDefinePageImport(filePath)) continue
167-
const pageDataImport = `_definePage_${name}_${importsMap.size}`
167+
const pageDataImport = `_definePage_${encodeImportIdentifierPart(name)}_${importsMap.size}`
168168
definePageDataList.push(pageDataImport)
169169
const lang = getLang(filePath)
170170
importsMap.addDefault(

‎packages/router/src/unplugin/utils/index.ts‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,3 +67,10 @@ export function formatMultilineUnion(items: string[], spaces: number): string {
6767
export function toStringLiteral(str: string): string {
6868
return `'${str.replace(/\\/g, '\\\\').replace(/'/g, "\\'")}'`
6969
}
70+
71+
export function encodeImportIdentifierPart(str: string): string {
72+
return str.replace(
73+
/[^A-Za-z0-9]/gu,
74+
char => `_${char.codePointAt(0)!.toString(16)}_`
75+
)
76+
}

0 commit comments

Comments
 (0)