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

前言

以 Vite 项目为例,通过 unplugin-vue-router 插件,可以实现把指定目录下的 Vue 文件自动转换为路由配置,并且根据约定好的目录结构和文件命名规则,支持 Vue-Router 所有类型的路由,完全不用再手动编写 router.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 插件。

{
    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

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 大法确实好啊。

// types/typed-router.d.ts
declare module 'vue-router/auto' {
    ......
}

总结

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

原文链接:https://juejin.cn/post/7346530887473201161 作者:justorez

(0)
上一篇 2024年3月16日 下午4:10
下一篇 2024年3月16日 下午4:21

相关推荐

发表回复

登录后才能评论