@vue/cli-plugin-router 深入指南:在 vue-cli 项目中接入 Vue Router

原创2026-09-18 21:20:531,684 阅读
文章标签:前端开发工具构建工具

@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 模式。这里有两个信息点值得展开:

  1. 两种模式的本质差异:history 模式借助 HTML5 History API,URL 中不再出现 # 号,路径更干净、更利于 SEO;而默认的 hash 模式则在 URL 中以 # 分隔,无需任何服务端配合。该提示文案中特别强调,history 模式在生产环境需要服务器正确配置 index fallback——因为刷新 /about 这类前端路由地址时,服务器必须回退到 index.html 交给前端路由接管,否则会返回 404。
  2. 你的选择如何落到配置里:这个布尔值 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].js chunk,仅在访问该路由时才加载。注释中特意解释了这一机制——这是文档中强调的"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 时有几点值得留意:

  1. 生产环境部署 history 模式必须配置 fallback:选择 history 模式后,Nginx / Apache / Node 服务器需将所有前端路由路径回退到 index.html,否则刷新子路由页面会 404。若不想处理服务端配置,选择 hash 模式即可。
  2. vue-router 版本由插件自动决定:Vue 2 项目装 ^3.5.1,Vue 3 项目装 ^4.0.3,无需手动指定;这与 Vue 2 的 new VueRouter / Vue 3 的 createRouter 两套 API 完全对应。
  3. 懒加载开箱即用:生成的路由模板默认对非首页路由采用动态 import(),自动获得按路由拆包(如 about.[hash].js)的效果。
  4. 与 TypeScript 无缝衔接:项目若启用 TypeScript,生成的路由配置会自动带上 RouteConfig(Vue 2)/ RouteRecordRaw(Vue 3)类型标注,无需手工补充。
  5. 两条安装入口等价: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)。

登录后查看全文
vue-cli