vite-plugin-pages
基于文件系统的 Vite 路由生成器
Usage
对于 Vite + Vue 而言,使用方式如下:
$ npm i vite-plugin-pages
例如对于下面的目录结构:
src
pages
blog
today
- index.vue
about
id
- nested.vue
- [id].vue
- about.vue
- [id].vue
- jsx.jsx
- [...all].vue
- main.ts
- vite.config.ts
例如对于下面的目录结构:
routes
原理
利用 vite 内部暴露的 paulmillr/chokidar 监听 pages 下面文件的的新增、变动、删除等
下面的作用就是监听 pages 目录下面的文件的增加,把结果收集到 _pageRouteMap 中
hannoeru/vite-plugin-pages/src/context.ts
Lines 45 to 90 #a638fe3
45
...
54
55
56
57
58
59
60
61
62
...
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
setupWatcher(watcher: ViteDevServer['watcher']) {
watcher
.on('add', async (path) => {
path = slash(path)
if (!isTarget(path, this.options))
return
const page = this.options.dirs.find(i => path.startsWith(slash(resolve(this.root, i.dir))))!
await this.addPage(path, page)
this.onUpdate()
})
}
async addPage(path: string | string[], pageDir: PageOptions) {
debug.pages('add', path)
for (const p of toArray(path)) {
const pageDirPath = slash(resolve(this.root, pageDir.dir))
const extension = this.options.extensions.find(ext => p.endsWith(`.${ext}`))
if (!extension)
continue
const route = slash(join(pageDir.baseRoute, p.replace(`${pageDirPath}/`, '').replace(`.${extension}`, '')))
this._pageRouteMap.set(p, {
path: p,
route,
})
await this.options.resolver.hmr?.added?.(this, p)
}
}下面的作用就是监听 pages 目录下面的文件的增加,把结果收集到 _pageRouteMap 中
下面是基于上面生成的 pageRouteMap 用于生成 Vue Router 的路由配置:
hannoeru/vite-plugin-pages/src/resolvers/vue.ts
Lines 63 to 159 #a638fe3
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
...
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
...
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
async function computeVueRoutes(ctx: PageContext, customBlockMap: Map<string, CustomBlock>): Promise<VueRoute[]> {
const { routeStyle, caseSensitive, routeNameSeparator } = ctx.options
const pageRoutes = [...ctx.pageRouteMap.values()]
// sort routes for HMR
.sort((a, b) => countSlash(a.route) - countSlash(b.route))
const routes: VueRouteBase[] = []
pageRoutes.forEach((page) => {
const pathNodes = page.route.split('/')
// add leading slash to component path if not already there
const component = page.path.replace(ctx.root, '')
const customBlock = customBlockMap.get(page.path)
const route: VueRouteBase = {
name: '',
path: '',
component,
customBlock,
rawRoute: page.route,
}
let parentRoutes = routes
let dynamicRoute = false
for (let i = 0; i < pathNodes.length; i++) {
route.name += route.name ? `${routeNameSeparator}${normalizedName}` : normalizedName
// Check parent exits
const parent = parentRoutes.find((parent) => {
return pathNodes.slice(0, i + 1).join('/') === parent.rawRoute
})
if (parent) {
// Make sure children exist in parent
parent.children = parent.children || []
// Append to parent's children
parentRoutes = parent.children
// Reset path
route.path = ''
}
else if (normalizedPath === 'index') {
if (!route.path)
route.path = '/'
}
else if (normalizedPath !== 'index') {
}
}
// put dynamic routes at the end
if (dynamicRoute)
parentRoutes.push(route)
else
parentRoutes.unshift(route)
})
let finalRoutes = prepareRoutes(ctx, routes)
finalRoutes = (await ctx.options.onRoutesGenerated?.(finalRoutes)) || finalRoutes
return finalRoutes
}下面是基于上面生成的 pageRouteMap 用于生成 Vue Router 的路由配置:
hannoeru/vite-plugin-pages/src/resolvers/vue.ts
Lines 161 to 219 #a638fe3
161
162
163
164
165
166
167
168
169
...
198
...
205
206
207
...
218
219
async function resolveVueRoutes(ctx: PageContext, customBlockMap: Map<string, CustomBlock>) {
const finalRoutes = await computeVueRoutes(ctx, customBlockMap)
let client = generateClientCode(finalRoutes, ctx.options)
client = (await ctx.options.onClientGenerated?.(client)) || client
return client
}
export function vueResolver(): PageResolver {
return {
async resolveRoutes(ctx) {
return resolveVueRoutes(ctx, customBlockMap)
},
}
}从 pageRouteMap,经过排序,父子关系建立,路由转换,最终得到 finalRoutes
hannoeru/vite-plugin-pages/src/index.ts
Lines 50 to 67 #a638fe3
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
async load(id) {
const {
moduleId,
pageId,
} = parsePageRequest(id)
if (moduleId === MODULE_ID_VIRTUAL && pageId && ctx.options.moduleIds.includes(pageId))
return ctx.resolveRoutes()
if (id === ROUTE_BLOCK_ID_VIRTUAL) {
return {
code: 'export default {};',
map: null,
}
}
return null
},