之前配置了一次 Vue + babel + webpack,在这里做一些记录方便以后再看。生成的 dist 目录结构参考的是 vue-cli,源代码也是从 vue-cli 生成的文件中拷贝过来的。

代码和注释上传到了这个仓库,方便查看。

首先配置好 package.json:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
{
"name": "hello-vue",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"vue": "^2.6.10"
},
"devDependencies": {
"babel-loader": "^8.0.6",
"file-loader": "^4.2.0",
"less-loader": "^5.0.0",
"less": "^3.10.3",
"style-loader": "^1.0.0",
"css-loader": "^3.2.0",
"vue-loader": "^15.7.1",
"webpack": "^4.41.1",
"webpack-cli": "^3.3.9"
}
}

首先可以看到很多 loader 啦,loader 是给 webpack 用的用于转换文件的工具:

loader 用于对模块的源文件(不一定是代码)进行转换。loader 可以使你在 import 或”加载”模块时预处理文件。因此,loader 类似于其他构建工具中的“任务(task)”,并提供了处理前端构建步骤的强大方法。loader 可以将文件从不同的语言(如 TypeScript)转换为 JavaScript 或将内联图像转换为 data URL。loader 甚至允许你直接在 JavaScript 模块中 import CSS 文件!

先装一下,我用的是 yarn

1
$ yarn install

为了方便,从 vue-cli 生成的文件拷贝 src 目录过来。

现在的目录是这样的:

1
2
3
4
5
.
├── node_modules
├── package.json
├── src
└── yarn.lock

然后是配置 webpack.config.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
// webpack.config.js
const path = require('path')
// vue-loader 文档要求要装的插件
const VueLoaderPlugin = require('vue-loader/lib/plugin')

module.exports = {
mode: 'production',
entry: path.resolve(__dirname, 'src', 'main.js'),
output: {
filename: 'js/[name].[chunkhash:12].js',
path: path.resolve(__dirname, 'dist')
// 如果在 filename,chunkFilename 中增加了路径,则在打包出来的 index.html
// 中会自动加上该路径去引入 scripts,不需要使用 publicPath
// publicPath: "/js/"
},
module: {
rules: [
{
test: /\.vue$/,
loader: 'vue-loader'
},
{
test: /\.(png|jpe?g|gif)$/i,
use: [
{
loader: 'file-loader',
options: {
name: '[contenthash:12].[ext]',
outputPath: 'assets'
}
}
]
},
// 它会应用到普通的 `.css` 文件
// 以及 `.vue` 文件中的 `<style>` 块
{
test: /\.css$/,
use: ['css-loader']
},
// 它会应用到普通的 `.css` 文件
// 以及 `.vue` 文件中的 `<style>` 块
{
test: /\.less$/,
use: ['style-loader', 'css-loader', 'less-loader']
},
{
test: /\.html$/,
use: ['html-loader']
},
// from https://vue-loader.vuejs.org/zh/guide/pre-processors.html#babel
// exclude:/node_modules/
// 在应用于 .js 文件的 JS 转译规则 (例如 babel-loader) 中是蛮常见的。
// 鉴于 v15 中的推导变化,如果你导入一个 node_modules 内的 Vue 单文件组件,
// 它的 <script> 部分在转译时将会被排除在外。
// 为了确保 JS 的转译应用到 node_modules 的 Vue 单文件组件,
// 你需要通过使用一个排除函数将它们加入白名单:
{
test: /\.js$/,
loader: 'babel-loader',
include: path.resolve(__dirname, 'src'),
exclude: file => /node_modules/.test(file) && !/\.vue\.js/.test(file)
}
]
},
plugins: [
// vue-loader 文档中提醒必须引入这个插件
// https://vue-loader.vuejs.org/zh/guide/#%E6%89%8B%E5%8A%A8%E8%AE%BE%E7%BD%AE
new VueLoaderPlugin()
]
}

配置了好几个 loader,当 webpack 遇到了符合 test 字段的相应文件结尾的文件时,就会调用对应的 loader 进行转换。

再使用 npx 运行 webpack:

1
$ npx webpack

安装新版本 Node.js 的时候也会安装 npx,npx 的作用就是直接执行目录下的 node_modules/.bin/ 下的可执行文件,运行 npx webpack 的时候,就等于是执行了 ./node_modules/.bin/webpack

当运行 webpack 命令的时候,会默认读取根目录的 webpack.config.js 文件作为配置文件,所以这里不需要传入配置文件的路径。

当然我们用得最多的还是 package.json 的 scripts 字段,等下再配置。

运行以上的命令,如无意外,就能发现多出了一个 dist 目录,但是里面并没有 *.html 文件,因为缺少了生成 html 的插件。

安装 html-webpack-plugin 插件:

1
$ yarn add html-webpack-plugin --dev

webpack.config.js 配置文件中增加:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// webpack.config.js
// 生成 html 文件的插件
const HtmlWebpackPlugin = require('html-webpack-plugin')
·····

module.exports = {
······
plugins: [
// vue-loader 文档中提醒必须引入这个插件
// https://vue-loader.vuejs.org/zh/guide/#%E6%89%8B%E5%8A%A8%E8%AE%BE%E7%BD%AE
new VueLoaderPlugin(),
new HtmlWebpackPlugin({
favicon: path.resolve('src', 'template/favicon.ico'),
// 此处如果需要压缩 html 代码,则需要手动传入一个 Object 而不是 Boolean
// ref https://github.com/jantimon/html-webpack-plugin#minification
minify: {
collapseWhitespace: true,
removeComments: true,
removeRedundantAttributes: true,
removeScriptTypeAttributes: true,
removeStyleLinkTypeAttributes: true,
useShortDoctype: true
},
showErrors: false
})
]
}

再写一个 scripts:

1
2
3
4
5
6
7
8
// package.json
{
···
"scripts": {
"build": "webpack"
},
···
}

运行:

1
$ yarn run build

这样在生成的 dist 目录中就增加了一个 index.html 文件。

这时候观察 dist 目录中是没有 css 文件的,因为 webpack 默认将 css 文件也打包进 javascript 中,所以下一步是分离 css 文件,需要安装一个 webpack 插件,顺便再装一个清理 dist 目录的插件:

1
$ yarn add mini-css-extract-plugin clean-webpack-plugin --dev

webpack.config.js 配置文件中增加:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
// webpack.config.js
// 生成 html 文件的插件
const HtmlWebpackPlugin = require('html-webpack-plugin')
// 清理输出目录的插件
const { CleanWebpackPlugin } = require('clean-webpack-plugin')
// 分离 css 文件的插件
// 在 webpack 中,css 会打包在 JavaScript 中
// 所以需要插件将打包进 JavaScript 的 css 分离出来
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
·····

module.exports = {
······
module: {
rules: [
······
// 它会应用到普通的 `.css` 文件
// 以及 `.vue` 文件中的 `<style>` 块
{
test: /\.css$/,
use: [MiniCssExtractPlugin.loader, 'css-loader']
},
······
]
},
plugins: [
······
new CleanWebpackPlugin(),
new MiniCssExtractPlugin({
publicPath: '/css/',
filename: 'css/[name].[hash:12].css',
chunkFilename: 'css/[id].[hash:12].css'
})
]
}

运行 build 命令,如无意外,dist 目录中会增加了一个 css 目录,并且里面多了一个 *.css 文件,dist/index.html 中也可以看到,引入了 css 的路径。

但是现在的 css 的文件是没有压缩的,我们还需要增加一个插件。

1
$ yarn add optimize-css-assets-webpack-plugin --dev

安装 optimize-css-assets-webpack-plugin 插件用于优化 *.css 文件,详情可以看 官方文档

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// webpack.config.js
······
// 用于配合 mini-css-extract-plugin 将分离之后的 css 进行压缩
const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin')
······

module.exports = {
······
module: {
······
},
plugins: [
······
],
optimization: {
splitChunks: {
chunks: 'all'
},
minimizer: [new OptimizeCSSAssetsPlugin()]
},
}

配置好之后,运行 build 命令,如无意外,dist/css 中的 *.css 文件的内容都被优化压缩了,但是同时 JavaScript 有没有被优化压缩,好烦。

查了 文档 发现 webpack4 需要同时定义一个 JS minimizer

1
$ yarn add terser-webpack-plugin --dev

引入这个插件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
······
// 用于配合 mini-css-extract-plugin 将分离之后的 css 进行压缩
const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin')
const TerserPlugin = require('terser-webpack-plugin')
······

module.exports = {
······
module: {
······
},
plugins: [
······
],
optimization: {
splitChunks: {
chunks: 'all'
},
// 如果在此使用了 OptimizeCSSAssetsPlugin,webpack 将不会压缩 javascript 和 html
// 如果需要使用 OptimizeCSSAssetsPlugin 的同时又压缩 JavaScript,则需要在本数组
// 增加 TerserPlugin 实例
minimizer: [new TerserPlugin(), new OptimizeCSSAssetsPlugin()]
},
}

运行 build 命令,如无意外,这一次 *.css*.js 文件都会压缩成功了。

这时候打开 dist/index.html 发现文件里面并没有 id 为 #app 的 HTML 元素,而 src/main.js 中将 Vue 应用挂载在 id 为 app 的元素上:

1
2
3
new Vue({
render: h => h(App)
}).$mount('#app')

查看 vue-cli 生成的文件中发现 public/index.html 被用作 html-webpack-plugin 插件的模板。

我把这个文件拷贝到 src/template 目录中,顺便把 favicon.ico 也拷贝到这个目录中。我把模板改成 *.hbs 文件,因为我比较会用 handlebars,哈哈。

1
$ yarn add handlebars-loader handlebars --dev

修改配置文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
// webpack.config.js
module.exports = {
module: {
rules: [
······
{
test: /\.hbs$/,
use: ['handlebars-loader']
},
······
]
},
plugins: [
new HtmlWebpackPlugin({
favicon: path.resolve('src', 'template/favicon.ico'),
// 此处如果需要压缩 html 代码,则需要手动传入一个 Object 而不是 Boolean
// ref https://github.com/jantimon/html-webpack-plugin#minification
minify: {
collapseWhitespace: true,
removeComments: true,
removeRedundantAttributes: true,
removeScriptTypeAttributes: true,
removeStyleLinkTypeAttributes: true,
useShortDoctype: true
},
// 调用 原生的 vue 脚手架的 build 命令时,会调用 vue-cli-service,
// vue-cli-service 模块中使用的是 ejs 语法(疑似),在这里把模块中的模板拷贝出来
// 用 handlebars 的语法替换 ejs 的语法,实现模板的功能,替换变量
template: path.resolve('src', 'template/index.hbs'),
showErrors: false
})
]
}
······

运行 build 命令,如无意外,dist/index.html 中就包含了模板中的代码了。

还要区分环境,vue-loader 的文档中也显示了需要区分环境:

1
$ yarn add cross-env --dev

我是用 cross-env 来定义环境变量。

修改 package.json:

1
2
3
4
5
6
7
8
// package.json
{
···
"scripts": {
"build:prod": "cross-env NODE_ENV=production webpack --config webpack.config.js --progress"
},
···
}

修改 webpack.config.js:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
······
const = process.env.NODE_ENV === 'production'
······

module.exports = {
module: {
rules: [
······
{
test: /\.css$/,
use: [
!isProduction ? 'vue-style-loader' : MiniCssExtractPlugin.loader,
'css-loader'
]
},
······
]
}
}

webpack 建议使用 webpack.common.js,webpack.dev.js,webpacm.prod.js 区分环境,以后可以自己修改。

另外还可以修改一下构建时输出到控制台的提示格式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
······

module.exports = {
······
stats: {
// 添加构建日期和构建时间信息
builtAt: true,
// 添加资源信息
assets: true,
// `webpack --colors` 等同于
colors: true,
// 不添加构建模块信息
modules: false,
// 添加时间信息
timings: true,
// 添加警告
warnings: true,
// 不添加 children 信息
children: false,
// 不显示通过对应的 bundle 显示入口起点
entrypoints: false
}
······
}

最后把 vue-cli 生成的一些配置文件也拷贝过来,包括 .browserslistrc.eslintrc.js.gitignorebabel.config.js

安装 vue 的 babel preset:

1
$ yarn add @vue/babel-preset-app --dev

这样就大功告成了。

往后如果要添加什么功能,都可以在这个配置的基础上完善。