前言
以 Vite 项目为例,通过 unplugin-vue-router 插件,可以实现把指定目录下的 Vue 文件自动转换为路由配置,并且根据约定好的目录结构和文件命名规则,支持 Vue-Router 所有类型的路由,完全不用再手动编写 router.js
。
这到底是怎么实现的呢?
js
// vite.config.js
import { defineConfig } from 'vite'
import VueRouter from 'unplugin-vue-router/vite'
export default defineConfig({
plugins: [
VueRouter({
routesFolder: 'src/pages',
dts: 'types/typed-router.d.ts'
}),
vue()
]
})
// src/main.js
import { createApp } from 'vue'
import { createRouter, createWebHistory } from 'vue-router/auto'
import App from './App.vue'
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
})
createApp(App).use(router).mount('#app')
正文
不知道你发现了没有,我们在 main.js
里是从 vue-router/auto
中导入的相关路由函数,而查看 vue-router
的 dist
目录和 package.json
的 exports
配置,其中根本没有这个东西,无中生有?
那我们只能合理怀疑,这是通过构建工具在打包过程中插进去的。直接去看源码。
src/index.ts
unplugin-vue-router
基于 unplugin,编写的插件可以同时适配 Vite、Webpack、Rollup 等市面主流的打包工具。为了便于理解,你可以把下面的代码看成一个 Rollup 插件。
ts
{
name: 'unplugin-vue-router',
resolveId(id) {
// vue-router/auto-routes
if (id === MODULE_ROUTES_PATH) {
return asVirtualId(id)
}
// vue-router/auto
if (id === MODULE_VUE_ROUTER) {
return asVirtualId(id)
}
},
load(id) {
const resolvedId = getVirtualId(id)
// vue-router/auto-routes
if (resolvedId === MODULE_ROUTES_PATH) {
return ctx.generateRoutes()
}
// vue-router/auto
if (resolvedId === MODULE_VUE_ROUTER) {
return ctx.generateVueRouterProxy()
}
}
}
这里我们只关注两个插件钩子,在 resolveId
阶段,如果遇到代码导入了 vue-router/auto
,则使用 asVirtualId
方法做一个简单的转换,变为 virtual:vue-router/auto
,添加一个标识避免和其他插件产生冲突。
然后在 load
阶段,我们再反向操作,去除 virtual:
标识,得到真实的导入,如果匹配到 vue-router/auto
,则调用 ctx.generateVueRouterProxy
生成真正的代码。
src/codegen/vueRouterModule.ts
ts
export function generateVueRouterProxy() {
return `
import { routes } from 'vue-router/auto-routes' // 默认路由
import { createRouter as _createRouter } from 'vue-router'
export * from 'vue-router' // 导出 vue-router 全部内容
......
export function createRouter(options) {
const { extendRoutes } = options // 新增 extendRoutes 配置
const router = _createRouter(Object.assign(
options,
{ routes: typeof extendRoutes === 'function' ? extendRoutes(routes) : routes },
))
return router
}
`.trimStart()
}
从 generateVueRouterProxy
这个名字我们也能看出一二,生成一个 VueRouter 代理,即在 VueRouter 的基础上加入新的功能。先原封不动地导出 vue-router
的全部内容,在 createRouter
方法中直接内置一些路由,这里内置的 routes
就是根据 unplugin-vue-router
插件配置,解析指定目录内容生成出来的路由配置。
同理,在返回上述这段生成的代码后,Rollup 继续解析,回到我们上一部分讲解的内容,resolveId
阶段处理 vue-router/auto-routes
,load
阶段调用 ctx.generateRoutes
方法生成最终的路由配置。
至于具体的路由生成细节,就不展开了,思路不难,不过 unplugin-vue-router
很贴心地加入了文件监听,当我们创建了新文件后,无需重启 Vite,开发体验更丝滑了。
VSCode 代码提示
到这里,还剩最后一个问题,针对 vue-router/auto
这种不存在的东西,是怎么实现让 VSCode 进行代码提示的?
还记得文章开头插件配置项里的 dts: types/typed-router.d.ts
吗?
每次启动 Vite,都会生成一个 TypeScript 类型声明文件,只要把它加到 tsconfig.json
里,VSCode 就可以识别到自定义模块和所有自动解析生成的路由了,甚至写 <RouterLink to="">
时都有路由提示,TypeScript 大法确实好啊。
ts
// types/typed-router.d.ts
declare module 'vue-router/auto' {
......
}
总结
不仅是 unplugin-vue-router
,unplugin-auto-import
等等插件实现原理都是类似的。在没看过相关的插件源码之前,感觉自动导入像黑魔法一样,而实际上,也没有特别高深的东西,只是佩服第一个想到这个点子的人🐂🍺。