Skip to content

Commit

Permalink
fix: no missing imports
Browse files Browse the repository at this point in the history
  • Loading branch information
posva committed Apr 13, 2023
1 parent ee8e6eb commit 65f8c83
Show file tree
Hide file tree
Showing 5 changed files with 103 additions and 36 deletions.
14 changes: 6 additions & 8 deletions src/codegen/__snapshots__/generateRouteRecords.spec.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -99,9 +99,8 @@ exports[`generateRouteRecord > generate custom imports 1`] = `
`;

exports[`generateRouteRecord > generate custom imports 2`] = `
Map {
"_page_0" => "a.vue",
}
"import _page_0 from 'a.vue'
"
`;

exports[`generateRouteRecord > generate static imports 1`] = `
Expand Down Expand Up @@ -142,11 +141,10 @@ exports[`generateRouteRecord > generate static imports 1`] = `
`;

exports[`generateRouteRecord > generate static imports 2`] = `
Map {
"_page_0" => "a.vue",
"_page_1" => "b.vue",
"_page_2" => "nested/file/c.vue",
}
"import _page_0 from 'a.vue'
import _page_1 from 'b.vue'
import _page_2 from 'nested/file/c.vue'
"
`;

exports[`generateRouteRecord > handles multiple named views 1`] = `
Expand Down
11 changes: 6 additions & 5 deletions src/codegen/generateRouteRecords.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,13 @@ import { describe, expect, it } from 'vitest'
import { createPrefixTree, TreeNode } from '../core/tree'
import { ResolvedOptions, resolveOptions } from '../options'
import { generateRouteRecord } from './generateRouteRecords'
import { ImportsMap } from '../core/utils'

const DEFAULT_OPTIONS = resolveOptions({})

describe('generateRouteRecord', () => {
function generateRouteRecordSimple(tree: TreeNode) {
return generateRouteRecord(tree, DEFAULT_OPTIONS, new Map())
return generateRouteRecord(tree, DEFAULT_OPTIONS, new ImportsMap())
}

it('works with an empty tree', () => {
Expand Down Expand Up @@ -106,10 +107,10 @@ describe('generateRouteRecord', () => {
tree.insert('a.vue')
tree.insert('b.vue')
tree.insert('nested/file/c.vue')
const importList = new Map<string, string>()
const importList = new ImportsMap()
expect(generateRouteRecord(tree, options, importList)).toMatchSnapshot()

expect(importList).toMatchSnapshot()
expect(importList.toString()).toMatchSnapshot()
})

it('generate custom imports', () => {
Expand All @@ -123,10 +124,10 @@ describe('generateRouteRecord', () => {
tree.insert('a.vue')
tree.insert('b.vue')
tree.insert('nested/file/c.vue')
const importList = new Map<string, string>()
const importList = new ImportsMap()
expect(generateRouteRecord(tree, options, importList)).toMatchSnapshot()

expect(importList).toMatchSnapshot()
expect(importList.toString()).toMatchSnapshot()
})

describe('names', () => {
Expand Down
28 changes: 15 additions & 13 deletions src/codegen/generateRouteRecords.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,19 @@
import type { TreeNode } from '../core/tree'
import { ImportsMap } from '../core/utils'
import { ResolvedOptions, _OptionsImportMode } from '../options'

export function generateRouteRecord(
node: TreeNode,
options: ResolvedOptions,
importList: Map<string, string>,
importsMap: ImportsMap,
indent = 0
): string {
// root
if (node.value.path === '/' && indent === 0) {
return `[
${node
.getSortedChildren()
.map((child) => generateRouteRecord(child, options, importList, indent + 1))
.map((child) => generateRouteRecord(child, options, importsMap, indent + 1))
.join(',\n')}
]`
}
Expand Down Expand Up @@ -43,7 +44,7 @@ ${
node,
indentStr,
options.importMode,
importList
importsMap
)
: '/* no component */'
}
Expand All @@ -59,7 +60,7 @@ ${overrides.props != null ? indentStr + `props: ${overrides.props},\n` : ''}${
? `children: [
${node
.getSortedChildren()
.map((child) => generateRouteRecord(child, options, importList, indent + 2))
.map((child) => generateRouteRecord(child, options, importsMap, indent + 2))
.join(',\n')}
${indentStr}],`
: '/* no children */'
Expand All @@ -69,12 +70,13 @@ ${startIndent}}`
if (node.hasDefinePage) {
const definePageDataList: string[] = []
for (const [name, filePath] of node.value.components) {
const pageDataImport = `_definePage_${name}_${importList.size}`
const pageDataImport = `_definePage_${name}_${importsMap.size}`
definePageDataList.push(pageDataImport)
importList.set(pageDataImport, `${filePath}?definePage&vue`)
importsMap.addDefault(`${filePath}?definePage&vue`, pageDataImport)
}

if (definePageDataList.length) {
importsMap.add('unplugin-vue-router/runtime', '_mergeRouteRecord')
return ` _mergeRouteRecord(
${routeRecord},
${definePageDataList.join(',\n')}
Expand All @@ -89,12 +91,12 @@ function generateRouteRecordComponent(
node: TreeNode,
indentStr: string,
importMode: _OptionsImportMode,
importList: Map<string, string>
importsMap: ImportsMap
): string {
const files = Array.from(node.value.components)
const isDefaultExport = files.length === 1 && files[0][0] === 'default'
return isDefaultExport
? `component: ${generatePageImport(files[0][1], importMode, importList)},`
? `component: ${generatePageImport(files[0][1], importMode, importsMap)},`
: // files has at least one entry
`components: {
${files
Expand All @@ -103,7 +105,7 @@ ${files
`${indentStr + ' '}'${key}': ${generatePageImport(
path,
importMode,
importList
importsMap
)}`
)
.join(',\n')}
Expand All @@ -114,21 +116,21 @@ ${indentStr}},`
* Generate the import (dynamic or static) for the given filepath. If the filepath is a static import, add it to the
* @param filepath - the filepath to the file
* @param importMode - the import mode to use
* @param importList - the import list to fill
* @param importsMap - the import list to fill
* @returns
*/
function generatePageImport(
filepath: string,
importMode: _OptionsImportMode,
importList: Map<string, string>
importsMap: ImportsMap
) {
const mode =
typeof importMode === 'function' ? importMode(filepath) : importMode
if (mode === 'async') {
return `() => import('${filepath}')`
} else {
const importName = `_page_${importList.size}`
importList.set(importName, filepath)
const importName = `_page_${importsMap.size}`
importsMap.addDefault(filepath, importName)
return importName
}
}
Expand Down
16 changes: 6 additions & 10 deletions src/core/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { promises as fs } from 'fs'
import {
appendExtensionListToPattern,
asRoutePath,
ImportsMap,
logTree,
throttle,
} from './utils'
Expand Down Expand Up @@ -167,25 +168,20 @@ export function createRoutesContext(options: ResolvedOptions) {
}

function generateRoutes() {
// keys are import names while values are paths import __ from __
// TODO: reverse the order and make a list of named imports and another for defaults?
const importList = new Map<string, string>()
const importsMap = new ImportsMap()

const routesExport = `export const routes = ${generateRouteRecord(
routeTree,
options,
importList
importsMap
)}`

// generate the list of imports
let imports = ''
if (options.dataFetching) {
imports += `import { _HasDataLoaderMeta, _mergeRouteRecord } from 'unplugin-vue-router/runtime'\n`
}
for (const [name, path] of importList) {
imports += `import ${name} from '${path}'\n`
importsMap.add('unplugin-vue-router/runtime', '_HasDataLoaderMeta')
}

// generate the list of imports
let imports = `${importsMap}`
// add an empty line for readability
if (imports) {
imports += '\n'
Expand Down
70 changes: 70 additions & 0 deletions src/core/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -274,3 +274,73 @@ export function appendExtensionListToPattern(
? filePatterns.map((filePattern) => `${filePattern}${extensionPattern}`)
: `${filePatterns}${extensionPattern}`
}

export interface ImportEntry {
// name of the variable to import
name: string
// optional name to use when importing
as?: string
}

export class ImportsMap {
// path -> import as -> import name
// e.g map['vue-router']['myUseRouter'] = 'useRouter' -> import { useRouter as myUseRouter } from 'vue-router'
private map = new Map<string, Map<string, string>>()

constructor() {}

add(path: string, importEntry: ImportEntry): this
add(path: string, importEntry: string): this
add(path: string, importEntry: string | ImportEntry): this {
if (!this.map.has(path)) {
this.map.set(path, new Map())
}
const imports = this.map.get(path)!
if (typeof importEntry === 'string') {
imports.set(importEntry, importEntry)
} else {
imports.set(importEntry.as || importEntry.name, importEntry.name)
}

return this
}

addDefault(path: string, as: string): this {
return this.add(path, { name: 'default', as })
}

getImportList(path: string): Required<ImportEntry>[] {
if (!this.map.has(path)) return []
return Array.from(this.map.get(path)!).map(([as, name]) => ({
as: as || name,
name,
}))
}

toString(): string {
let importStatements = ''
for (const [path, imports] of this.map) {
if (!imports.size) continue

// only one import and it's the default one
if (imports.size === 1) {
// we extract the first and only entry
const [[importName, maybeDefault]] = [...imports.entries()]
// we only care if this is the default import
if (maybeDefault === 'default') {
importStatements += `import ${importName} from '${path}'\n`
continue
}
}
importStatements += `import { ${Array.from(imports)
.map(([as, name]) => (as === name ? name : `${name} as ${as}`))
.join(', ')} } from '${path}'\n`
}

return importStatements
}

get size(): number {
return this.map.size
}
}

0 comments on commit 65f8c83

Please sign in to comment.