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

相关推荐
forwardMyLife2 分钟前
element-plus的面包屑组件el-breadcrumb
javascript·vue.js·ecmascript
计算机学姐36 分钟前
基于python+django+vue的影视推荐系统
开发语言·vue.js·后端·python·mysql·django·intellij-idea
luoluoal1 小时前
java项目之基于Spring Boot智能无人仓库管理源码(springboot+vue)
java·vue.js·spring boot
mez_Blog1 小时前
个人小结(2.0)
前端·javascript·vue.js·学习·typescript
珊珊而川2 小时前
【浏览器面试真题】sessionStorage和localStorage
前端·javascript·面试
森叶2 小时前
Electron 安装包 asar 解压定位问题实战
前端·javascript·electron
深情废杨杨2 小时前
前端vue-插值表达式和v-html的区别
前端·javascript·vue.js
GHUIJS2 小时前
【vue3】vue3.3新特性真香
前端·javascript·vue.js
markzzw2 小时前
我在 Thoughtworks 被裁前后的经历
前端·javascript·面试
众生回避2 小时前
鸿蒙ms参考
前端·javascript·vue.js