用 Vue CLI 3 建完项目,很多人第一反应是懵的:build 目录呢?webpack.base.conf.js 呢?CLI 2 时代那一堆能随便改的配置文件全没了,根目录干净得只剩一个 package.json。想改个图片打包阈值、加个二级目录、配个代理,无从下手。这篇就顺着这条线走一遍,先把项目跑起来,再讲清 CLI 3 把 webpack 藏到哪去了,然后把 vue.config.js 里每一项配置对应改的是 webpack 的哪个字段讲明白,最后附上默认插件清单和现在该怎么选型。
在本篇文章中,我们将从浅入深,和大家一起学习以下知识:
- 安装 Vue CLI 3 并创建项目,看懂生成的目录结构
- CLI 3 到底把 webpack 配置藏在了哪里,为什么要这么做
vue.config.js六个常用配置项各自改的是 webpack 的什么chainWebpack和configureWebpack的区别,什么时候用哪个- 用
vue inspect反查最终生效的完整配置 - CLI 3 默认给你装了哪些 webpack 插件,各自干什么
- Vue CLI 进入维护状态之后,新项目该怎么办
# 一、先把环境和脚手架装好
先确认本地 Node 环境。CLI 3 官方要求的 Node 版本是 8.9 及以上,太老的版本装不上:
# 查看 node 版本
node -v
# 查看 npm 版本
npm -v
然后全局装脚手架:
# 安装 Vue CLI 3.x
npm i -g @vue/cli
这里有个新旧包名的坑。CLI 2 的包名是 vue-cli,CLI 3 换成了带 scope 的 @vue/cli,两者是两个独立的 npm 包。如果你机器上以前装过 vue-cli,vue -V 打出来还是 2.x,先把老的卸干净再装新的,不然命令会打架。
装完之后,在你想要放项目的目录下执行创建命令:
# my-project 是你的项目名称
vue create my-project
它会先问你选预设还是手动选特性,手动模式下可以勾 Router、Vuex、CSS 预处理器、ESLint、单元测试这些。这一步选完之后配置会记在本地,下次建项目可以直接复用预设,不用再点一遍。
# 二、目录结构和可视化界面
创建完成后的目录长这样:
├── node_modules # 项目依赖包目录
├── public
│ ├── favicon.ico # ico图标
│ └── index.html # 首页模板
├── src
│ ├── assets # 样式图片目录
│ ├── components # 组件目录
│ ├── views # 页面目录
│ ├── App.vue # 父组件
│ ├── main.js # 入口文件
│ ├── router.js # 路由配置文件
│ └── store.js # vuex状态管理文件
├── .gitignore # git忽略文件
├── .postcssrc.js # postcss配置文件
├── babel.config.js # babel配置文件
├── package.json # 包管理文件
└── yarn.lock # yarn依赖信息文件
几个点值得留意。public 目录里的东西是原样拷贝到构建产物里的,不走 webpack 处理,所以那些必须保持固定文件名的资源(第三方 SDK、静态 JSON)放这儿最省事。index.html 是模板,里面可以用 htmlWebpackPlugin.options 的变量。
components 和 views 的分工是 CLI 给的一个约定:views 放路由级别的页面,components 放可复用的组件。这条约定挺重要,它其实对应的是容器组件和展示组件的划分,我在 Vue组件化实践详解 里把组件之间怎么通信讲得比较细,配合着看会更清楚为什么要这么分。
还有一点,router.js 和 store.js 是 CLI 3 早期版本生成的单文件形式。后来的版本改成了 router/index.js 和 store/index.js 目录形式,方便把路由拆成多个模块。你手上的版本生成哪种以实际为准,两种都能跑。
除了命令行,CLI 3 还提供了一个图形化界面,在项目目录下运行:
vue ui
它会起一个本地服务,浏览器里能看到依赖列表、可用的 task、插件管理。日常我用得不多,但有两个场景挺顺手:一是给非前端同学演示怎么跑构建,二是下面要讲的查看 webpack 完整配置。
# 三、CLI 3 为什么看不到 webpack 配置
用过 CLI 2 的人都知道,它生成的项目里有一整套 webpack 配置文件摊在 build 目录下,想改什么直接进去改。CLI 3 里这些文件一个都没有,那它是抛弃 webpack 了吗?
当然不是。CLI 3 用的还是 webpack,只是把配置封装进了 @vue/cli-service 这个包里,对外只暴露一个 vue.config.js 作为修改入口。
这个改动当年争议不小,我一开始也觉得别扭,配置摸不着心里没底。用久了才体会到好处:项目里那份 webpack 配置一旦落地成文件,就跟项目绑死了,升级 webpack、升级 loader 全靠人肉改,几十个项目就是几十份需要各自维护的配置。封装进依赖之后,升级 CLI 版本就等于升级了整套构建配置,成本从 N 降到 1。
代价是灵活性。你想做的事如果超出了 vue.config.js 暴露的范围,就得靠 chainWebpack 去改内部结构,得先知道 CLI 给每条规则、每个插件起的名字是什么。这也是为什么 vue inspect 这个命令后面会反复出现。
# 四、vue.config.js 逐项拆解
vue.config.js 放在项目根目录,导出一个对象。下面这几项是最常改的。
# 1. baseUrl 与现在的 publicPath
如果你想把项目部署在一个二级目录下,比如 http://localhost:8080/vue/,需要配置这一项:
// vue.config.js
module.exports = {
...
baseUrl: 'vue',
...
}
它改的是 webpack 配置里 output.publicPath,重启服务之后首页 URL 就带上二级目录了。
这里要补两个更正。第一,baseUrl 这个名字从 Vue CLI 3.3 起就废弃了,改名叫 publicPath,新版本里写 baseUrl 会给你一条警告,功能还在但别再用。第二,值本身建议写成 '/vue/' 这种前后都带斜杠的形式,只写 'vue' 在部分资源路径拼接时容易出问题。所以现在的写法是:
// vue.config.js
module.exports = {
publicPath: process.env.NODE_ENV === 'production' ? '/vue/' : '/'
}
按环境区分是因为本地开发一般在根路径下跑,生产才有二级目录。如果部署到 CDN,这里直接填完整的 CDN 域名也行。
# 2. outputDir
想把构建产物输出到别的文件夹(默认是 dist):
// vue.config.js
module.exports = {
...
outputDir: 'output',
...
}
跑 yarn build 之后项目根目录会出现 output 文件夹。它改的是 webpack output.path。
顺带说个相关的:assetsDir 控制的是产物内部静态资源的子目录名(默认是空,资源直接散在根下)。如果后端同学要求 js、css、图片必须归到 static 下,配 assetsDir: 'static' 就行,不用去动 webpack。
# 3. productionSourceMap
这一项控制生产构建要不要生成 source map:
// vue.config.js
module.exports = {
...
productionSourceMap: true,
...
}
它对应 webpack 里 devtool 的值。开启后线上报错能还原到源码行号,定位问题快很多。
但这条现在得加个前提。生产环境直接把 .map 文件传上服务器,等于把源码原封不动交出去了,任何人打开 DevTools 都能看到你的业务逻辑。现在通行的做法是关掉 productionSourceMap,改成在构建流程里单独生成 map 文件、上传给 Sentry 这类错误监控平台,然后从部署产物里删掉。既保留了还原能力,又不把源码挂到公网上。
顺手一提,productionSourceMap: false 还能明显缩短构建时间,大项目上能省掉不少。
# 4. chainWebpack
chainWebpack 允许我们用链式操作细致地修改 webpack 内部配置,底层集成的是 webpack-chain 这个库。比如要把图片的 base64 内联阈值改大:
// 用于做相应的合并处理
const merge = require('webpack-merge');
module.exports = {
...
// config 参数为已经解析好的 webpack 配置
chainWebpack: config => {
config.module
.rule('images')
.use('url-loader')
.tap(options =>
merge(options, {
limit: 5120,
})
)
}
...
}
rule('images') 和 use('url-loader') 里的字符串不是随便写的,是 CLI 给这条规则和这个 loader 起的名字,得先用 vue inspect 查出来才能对得上。tap 拿到的是这个 loader 当前的 options,返回值就是新的 options,这里用 webpack-merge 做合并而不是直接覆盖,是为了保住 CLI 原本设的其他字段。
改完之后,webpack 里对应的这段配置变成:
{
...
module: {
rules: [
{
/* config.module.rule('images') */
test: /\.(png|jpe?g|gif|webp)(\?.*)?$/,
use: [
/* config.module.rule('images').use('url-loader') */
{
loader: 'url-loader',
options: {
limit: 5120,
name: 'img/[name].[hash:8].[ext]'
}
}
]
}
]
}
...
}
注意 limit: 5120 的单位是字节,也就是 5KB,不是 5MB。这个数字调大要慎重,内联进 JS 的 base64 会变大三分之一左右,而且没法被浏览器单独缓存,阈值设太大反而拖慢首屏。我的经验是 4KB 到 10KB 之间,专门给图标类小图用。
# 5. configureWebpack
除了链式修改,还可以用 configureWebpack 改配置。两者的区别在于 chainWebpack 是精细的链式操作,configureWebpack 更倾向于整体替换和合并: