unplugin-vue-router 自动生成路由的实现原理

前言

以 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-routerdist 目录和 package.jsonexports 配置,其中根本没有这个东西,无中生有?

那我们只能合理怀疑,这是通过构建工具在打包过程中插进去的。直接去看源码。

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-routesload 阶段调用 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-routerunplugin-auto-import 等等插件实现原理都是类似的。在没看过相关的插件源码之前,感觉自动导入像黑魔法一样,而实际上,也没有特别高深的东西,只是佩服第一个想到这个点子的人🐂🍺。

相关推荐
待磨的钝刨38 分钟前
【格式化查看JSON文件】coco的json文件内容都在一行如何按照json格式查看
开发语言·javascript·json
Devil枫4 小时前
Vue 3 单元测试与E2E测试
前端·vue.js·单元测试
GIS程序媛—椰子5 小时前
【Vue 全家桶】6、vue-router 路由(更新中)
前端·vue.js
前端青山6 小时前
Node.js-增强 API 安全性和性能优化
开发语言·前端·javascript·性能优化·前端框架·node.js
毕业设计制作和分享6 小时前
ssm《数据库系统原理》课程平台的设计与实现+vue
前端·数据库·vue.js·oracle·mybatis
程序媛小果6 小时前
基于java+SpringBoot+Vue的旅游管理系统设计与实现
java·vue.js·spring boot
从兄7 小时前
vue 使用docx-preview 预览替换文档内的特定变量
javascript·vue.js·ecmascript
凉辰7 小时前
设计模式 策略模式 场景Vue (技术提升)
vue.js·设计模式·策略模式
清灵xmf8 小时前
在 Vue 中实现与优化轮询技术
前端·javascript·vue·轮询
薛一半9 小时前
PC端查看历史消息,鼠标向上滚动加载数据时页面停留在上次查看的位置
前端·javascript·vue.js