@vue/cli-plugin-router 深入指南:在 vue-cli 项目中接入 Vue Router
@vue/cli-plugin-router 深入指南:在 vue-cli 项目中接入 Vue Router
本文以 vue-cli 仓库中的官方文档 docs/ru/core-plugins/router.md(英文版见 docs/core-plugins/router.md)为骨架,结合
@vue/cli-plugin-router插件的真实源码、生成器模板与测试用例,系统讲解如何在已创建的 Vue 项目中通过vue add router一键接入 Vue Router,并深入剖析该插件在 Vue 2 / Vue 3 下的内部实现差异、history 模式选择、懒加载代码分割与 TypeScript 适配等实战细节。读完本文,你将不仅会用这条命令,还能理解它背后到底做了什么、生成的文件如何工作,以及在什么场景下需要注意服务端配置。
一、插件是什么:vue-cli 的官方路由插件
@vue/cli-plugin-router 是 vue-cli 生态中的官方路由插件,定位一句话即可概括——"router plugin for vue-cli"(vue-cli 的路由插件)。它并不承担 Vue Router 本身的运行时代码,而是负责把 Vue Router 以符合 vue-cli 规范的方式安装、配置并注入到你的项目中:
- 向项目
package.json写入vue-router依赖(版本由 Vue 主版本自动决定); - 在应用入口文件中注入
router的导入与挂载代码; - 渲染出
src/router/index.js路由配置文件与配套视图组件; - 根据交互式询问结果决定启用 hash 模式还是 HTML5 History 模式。
从源码结构看,该插件包体积非常轻量,只依赖 @vue/cli-shared-utils(用于 chalk 等共享工具),并声明 @vue/cli-service 为 peerDependency(支持 ^3.0.0 || ^4.0.0 || ^5.0.0-0),这意味着它可以在 vue-cli 3/4/5 的任意项目中工作。插件主体 index.js 仅有一行空实现,真正的逻辑全部集中在 generator/ 与 prompts.js 中——这正是 vue-cli 插件机制"配置驱动、生成器落地"的典型形态。
二、在已创建的项目中安装:vue add router
官方文档给出的安装方式非常直接,在已存在的 vue-cli 项目中执行:
vue add router
这条命令会触发 vue-cli 的插件安装流程:先解析并安装 @vue/cli-plugin-router 依赖,然后以"invoke(调用)"模式运行插件的 generator,把路由相关文件生成到项目中。如果你更习惯直接安装 npm 包,也可以先执行:
npm install @vue/cli-plugin-router --save-dev
随后用 vue invoke @vue/cli-plugin-router 手动触发同样的生成流程(此时会再次弹出交互式问题,详见下一节)。
需要特别说明的是:vue add router 面向的是已经创建好的项目。如果你还在 vue create 的交互式创建流程中,路由功能是作为可勾选特性出现的(仓库 promptModules/router.js 即为该特性注册入口,可以推断创建时选择的 "Router" 特性最终调用的正是同一个 generator)。两种入口殊途同归,最终都落到 generator/index.js 这一核心生成逻辑上。
三、交互式询问:history 模式的选择
当插件被"迟装"(late-installed)进一个已有项目并由 vue invoke 触发时,会弹出 prompts.js 中定义的唯一一个问题:
| 字段 | 值 |
|---|---|
| name | historyMode |
| type | confirm |
| message | Use history mode for router? (Requires proper server setup for index fallback in production) |
| description | By using the HTML5 History API, the URLs don't need the '#' character anymore. |
即询问你是否使用 history 模式。这里有两个信息点值得展开:
- 两种模式的本质差异:history 模式借助 HTML5 History API,URL 中不再出现
#号,路径更干净、更利于 SEO;而默认的 hash 模式则在 URL 中以#分隔,无需任何服务端配合。该提示文案中特别强调,history 模式在生产环境需要服务器正确配置 index fallback——因为刷新/about这类前端路由地址时,服务器必须回退到index.html交给前端路由接管,否则会返回 404。 - 你的选择如何落到配置里:这个布尔值
options.historyMode会作为渲染参数传给 generator(见下文模板),最终决定router/index.js中mode: 'history'/createWebHistory与默认 hash 分支的切换。
四、生成器内部逻辑:Vue 2 与 Vue 3 的差异化处理
generator/index.js 是整个插件的核心,它根据创建项目时选择的 Vue 版本(rootOptions.vueVersion === '3')走两条完全不同的注入路径:
Vue 2 路径(vue-router ^3.5.1):
api.injectImports(api.entryFile, `import router from './router'`)
api.injectRootOptions(api.entryFile, `router`)
api.extendPackage({
dependencies: {
'vue-router': '^3.5.1'
}
})
即在 src/main.js 中注入 import router from './router',并把 router 追加到 new Vue({ ... }) 的根选项中——这是 Vue 2 时代标准的注册方式。
Vue 3 路径(vue-router ^4.0.3):
api.injectImports(api.entryFile, `import router from './router'`)
api.transformScript(api.entryFile, require('./injectUseRouter'))
api.extendPackage({
dependencies: {
'vue-router': '^4.0.3'
}
})
Vue 3 中不再有根选项注入,而是通过 injectUseRouter.js 这个基于 jscodeshift 的 AST 转换脚本,把 createApp(...)(或 Vue.createApp(...))调用改写为 createApp(...).use(router),从而以 Vue 3 组合式风格完成路由插件的注册。这种"代码转换器"(codemod)的方式比简单字符串替换更稳健,能精确命中调用表达式而不破坏其余代码。
两条路径随后都会调用:
api.render('./template', { historyMode, doesCompile, hasTypeScript })
其中 doesCompile 由 api.hasPlugin('babel') || api.hasPlugin('typescript') 计算得出,hasTypeScript 表示项目是否启用了 TypeScript。此外,Vue 3 项目还会额外渲染一套 template-vue3 模板;若项目同时启用了 TypeScript(api.invoking 且存在 typescript 插件),还会调用 @vue/cli-plugin-typescript/generator/convert 对文件做类型转换处理。
五、生成的路由文件解剖
5.1 Vue 3 项目的路由配置
以 Vue 3 模板 generator/template-vue3/src/router/index.js 为例(去除 EJS 条件语法后核心形态如下):
import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router'
import HomeView from '../views/HomeView.vue'
const routes: Array<RouteRecordRaw> = [
{
path: '/',
name: 'home',
component: HomeView
},
{
path: '/about',
name: 'about',
// route level code-splitting
// this generates a separate chunk (about.[hash].js) for this route
// which is lazy-loaded when the route is visited.
component: () => import(/* webpackChunkName: "about" */ '../views/AboutView.vue')
}
]
const router = createRouter({
history: createWebHistory(process.env.BASE_URL), // 开启 history 模式时
// history: createWebHashHistory(), // 默认 hash 模式时
routes
})
export default router
几个关键点:
- 模式二选一:EJS 模板中,若
historyMode为真,则渲染createWebHistory(process.env.BASE_URL),否则渲染createWebHashHistory();process.env.BASE_URL与publicPath配置挂钩,保证路由 base 与资源部署路径一致。 - 路由级代码分割:
/about路由使用动态import(),webpack 会据此把该路由拆成独立的about.[hash].jschunk,仅在访问该路由时才加载。注释中特意解释了这一机制——这是文档中强调的"route level code-splitting"最佳实践。 - 无编译能力时的兼容写法:当项目既没有 babel 也没有 typescript(
doesCompile为假)时,模板会退化为component: function () { return import(...) }的写法,避免旧 Node/webpack 环境下对箭头函数+动态 import 的解析问题。
5.2 Vue 2 项目的路由配置
Vue 2 模板 generator/template/src/router/index.js 使用 Vue 2 风格 API:
import Vue from 'vue'
import VueRouter, { RouteConfig } from 'vue-router'
import HomeView from '../views/HomeView.vue'
Vue.use(VueRouter)
const routes: Array<RouteConfig> = [
{
path: '/',
name: 'home',
component: HomeView
},
{
path: '/about',
name: 'about',
component: () => import(/* webpackChunkName: "about" */ '../views/AboutView.vue')
}
]
const router = new VueRouter({
mode: 'history', // 开启 history 模式时
base: process.env.BASE_URL,
routes
})
export default router
可以看到 Vue 2 与 Vue 3 的差异清晰对应:new VueRouter({ mode, base, routes }) 对应 createRouter({ history, routes });TypeScript 类型也从 Array<RouteConfig> 换成了 Array<RouteRecordRaw>(Vue 2 模板中 RouteConfig 的类型导入同样受 hasTypeScript 条件控制)。
5.3 视图组件与 App 骨架
模板还生成了 src/views/HomeView.vue 与 src/views/AboutView.vue 两个示例视图,并把根组件 App.vue 改写为包含导航与出口的标准骨架:
<template>
<div id="app">
<nav>
<router-link to="/">Home</router-link> |
<router-link to="/about">About</router-link>
</nav>
<router-view/>
</div>
</template>
其中 <router-link> 负责声明式导航(激活态通过 .router-link-exact-active 类自动获得高亮样式 #42b983),<router-view/> 则是路由组件的渲染出口。该文件通过 EJS 的 extend/replace 机制对 vue-cli 默认生成的 App.vue 做定向替换,且样式会根据项目选择的 CSS 预处理器(无 / sass / stylus)渲染为对应语法——从源码结构看,这正是 generator 模板对项目多样性的适配。
六、测试验证:生成器行为有据可查
该插件的生成行为并非无据可依,仓库中提供了对应的测试用例 tests/routerGenerator.spec.js,使用 @vue/cli-test-utils 的 generateWithPlugin 在内存中模拟项目并运行生成器,断言生成文件、依赖版本、入口注入结果是否符合预期。如果你需要二次开发或深入理解该插件,可以直接运行:
npx jest packages/@vue/cli-plugin-router
来观察测试如何覆盖"Vue 2 / Vue 3 双版本生成""history 模式开关""TypeScript 适配"等关键行为。
七、实战要点与常见问题小结
综合文档与源码,使用 vue add router 时有几点值得留意:
- 生产环境部署 history 模式必须配置 fallback:选择 history 模式后,Nginx / Apache / Node 服务器需将所有前端路由路径回退到
index.html,否则刷新子路由页面会 404。若不想处理服务端配置,选择 hash 模式即可。 vue-router版本由插件自动决定:Vue 2 项目装^3.5.1,Vue 3 项目装^4.0.3,无需手动指定;这与 Vue 2 的new VueRouter/ Vue 3 的createRouter两套 API 完全对应。- 懒加载开箱即用:生成的路由模板默认对非首页路由采用动态
import(),自动获得按路由拆包(如about.[hash].js)的效果。 - 与 TypeScript 无缝衔接:项目若启用 TypeScript,生成的路由配置会自动带上
RouteConfig(Vue 2)/RouteRecordRaw(Vue 3)类型标注,无需手工补充。 - 两条安装入口等价:
vue add router与"npm install+vue invoke @vue/cli-plugin-router"最终都会执行同一套 generator,区别仅在于后者会保留交互式询问(history 模式确认)。
延伸阅读:官方文档英文版 docs/core-plugins/router.md 与俄文版 docs/ru/core-plugins/router.md 均只给出最简安装指令;本文所剖析的生成器、模板与测试源码全部位于 packages/@vue/cli-plugin-router 目录下,感兴趣的读者可以沿此路径继续深挖 vue-cli 插件机制(generator/index.js 中的 api.render、api.injectImports、api.transformScript 等 API 均来自 @vue/cli 的 GeneratorAPI)。