前言
之前我们已经学习了开发环境的搭建(不熟悉的同学请先去熟悉开发环境的搭建)。开发环境是将源代码经过 webpack 编译成浏览器能识别的语法,并输出成 bundle,同时为了让程序员更加轻松方便的进行项目开发,开发环境还做了一些自动化的工作。而生产环境我们在基础知识里讲过,是能让代码优化上线运行的环境,那么具体需要做哪些事情呢?
首先是样式资源的优化,之前我们将样式处理完后整合在 js 中,这样就会使 js 文件体积特别大,导致加载速度变慢。同时页面在加载中需要先加载 js 才能创建 style 标签将样式插入页面中,所以又会出现闪屏现象。所以我们需要将样式资源从 js 文件中提取出来。其次,我们还要将样式、js 代码等做兼容性处理,以此适配更多的运行环境。最后,我们需要对所有文件做代码压缩处理(注意不是压缩文件,是压缩代码),进一步的减小项目体积。除此以外,我们需要做的事还有很多,但总体而言我们的目标是:1. 让代码更快、更强、性能更好。2. 增强代码的稳定性,让代码可以在各个平台上平稳的运行。
处理样式文件
提取 CSS 成单独文件
我们重新创建一个项目,创建过程可以参考开发环境的文章。项目目录及文件内容如下:

接下来我们在根路径下新建 webpack.config.js 进行配置。先按之前开发环境的配置:
const { resolve } = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");
module.exports = {
entry: "./src/js/index.js",
output: {
filename: "js/main.js",
path: resolve(__dirname, "dist"),
},
module: {
rules: [
{
test: /\.css$/,
use: ["style-loader", "css-loader"],
},
],
},
plugins: [
new HtmlWebpackPlugin({
template: "./src/index.html",
}),
],
mode: "development",
};
const { resolve } = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");
module.exports = {
entry: "./src/js/index.js",
output: {
filename: "js/main.js",
path: resolve(__dirname, "dist"),
},
module: {
rules: [
{
test: /\.css$/,
use: ["style-loader", "css-loader"],
},
],
},
plugins: [
new HtmlWebpackPlugin({
template: "./src/index.html",
}),
],
mode: "development",
};
配置完下载完包后我们进行打包,样式资源会在 js 文件中。提取样式资源为单独的文件需要用到 mini-css-extract-plugin,我们先下载这个包,然后在配置中使用:
const { resolve } = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
module.exports = {
entry: "./src/js/index.js",
output: {
filename: "js/main.js",
path: resolve(__dirname, "dist"),
},
module: {
rules: [
{
test: /\.css$/,
use: [
// "style-loader",
MiniCssExtractPlugin.loader,
"css-loader",
],
},
],
},
plugins: [
new HtmlWebpackPlugin({
template: "./src/index.html",
}),
new MiniCssExtractPlugin({
filename: "css/main.css",
}),
],
mode: "development",
};
const { resolve } = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
module.exports = {
entry: "./src/js/index.js",
output: {
filename: "js/main.js",
path: resolve(__dirname, "dist"),
},
module: {
rules: [
{
test: /\.css$/,
use: [
// "style-loader",
MiniCssExtractPlugin.loader,
"css-loader",
],
},
],
},
plugins: [
new HtmlWebpackPlugin({
template: "./src/index.html",
}),
new MiniCssExtractPlugin({
filename: "css/main.css",
}),
],
mode: "development",
};
从配置中我们看到,mini-css-extract-plugin 使用时需要在 loader 和 plugins 中同时使用。在 loader 中 css-loader 的作用是将 CSS 打包编译后文件整合到 js 中,style-loader 的作用是创建 style 标签将样式放入 head 中。而 MiniCssExtractPlugin.loader 会帮我们提取 js 中的 CSS 成单独文件,并在 head 中插入样式,所以不需要再使用 style-loader 了。在 plugins 里我们也同样使用了 mini-css-extract-plugin,使用时参数 filename 可以配置打包后 CSS 文件的路径和名称。配置完成后我们打包看一下:

我们发现,在打包后的文件目录中,CSS 已经独立成一个文件,在 js 文件中也没有 CSS 样式的源代码了,index.html 文件中 CSS 样式用 link 标签引入,并且所有的 CSS 被整合在一个文件中。我们打开浏览器后,之前写的样式依然生效,Element 中的样式不见了,取而代之的是 link 标签,Network 中也能看到样式文件的获取。

CSS 兼容性处理
处理 CSS 兼容性问题,我们需要使用到 postcss 这个库,具体到 webpack 配置中便是需要使用 postcss-loader 和 postcss-preset-env 这两个包。下载好包后我们进行配置,配置有两种写法,我们先看第一种。
module.exports = {
... // 此处省略其他配置
module: {
rules: [
{
test: /\.css$/,
use: [
MiniCssExtractPlugin.loader,
"css-loader",
{
loader: "postcss-loader",
options: {
postcssOptions: {
plugins: [["postcss-preset-env"]],
},
},
},
],
},
],
}
}
module.exports = {
... // 此处省略其他配置
module: {
rules: [
{
test: /\.css$/,
use: [
MiniCssExtractPlugin.loader,
"css-loader",
{
loader: "postcss-loader",
options: {
postcssOptions: {
plugins: [["postcss-preset-env"]],
},
},
},
],
},
],
}
}
配置中 postcss-loader 是用来处理 CSS 兼容性的,而 postcss-preset-env 是帮助 postcss 找到 package.json 中 browserslist 里面的配置,通过配置加载指定的 CSS 兼容性样式。所以接下来我们需要在 package.json 文件中配置 browserslist 项。

在 browserslist 中,development 和 production 分别对应开发环境和生产环境的兼容性配置。配置中 last 1 XXX version
代表兼容 XXX 浏览器最近的一个版本,>0.2%
代表兼容市场占有率大于 0.2% 的浏览器,not dead
代表不兼容已经死掉的浏览器(即 24 个月内没有官方支持或更新的浏览器),not op_mini all
代表不兼容所有版本的 op_mini(国内基本没人使用)。由于开发环境只用于我们项目开发,所以不用兼容太多浏览器,只要兼容我们平时调试常用的浏览器最近的版本即可,而生产环境为了提高项目的兼容性和稳定性,需要多兼容一些浏览器和版本。
注意:browserslist 的配置需要结合项目的使用场景进行考虑。browserslist 还有其他配置方式,具体可以参考 browserslist 。
browserslist 中的 development、production 都是取 node 中的环境变量,并不是取 webpack.config.js 中的 mode,而 node 中的环境变量默认是 production。所以我们在开发环境下,需要设置 node 环境变量,可以使用 p
来设置(这里仅为了方便验证,以后会出相关文章介绍具体用法):
process.env. NODE_ENV = 'development';
module.exports = {
... // 此处省略配置
}
process.env. NODE_ENV = 'development';
module.exports = {
... // 此处省略配置
}
配置好后我们添加一些需要兼容的 CSS 样式来进行开发环境的兼容验证。

我们可以通过改变 node 环境变量来观察不同兼容性配置下,CSS 样式被处理的结果。

我们可以看到,由于我们在 development 模式下设置了只兼容常用浏览器最近的一个版本,所以 display: flex
打包编译后并没有做兼容性处理。而 production 我们设置兼容的浏览器版本更多,所以 display: flex
打包编译后也同样被做了兼容性处理。所以有了 webpack 对 CSS 样式的统一兼容性处理后,大大提高了程序员们的开发效率。
压缩 CSS
我们可以使用 optimize-css-assets-webpack-plugin 插件来压缩 CSS,此插件的使用非常简单,下载好后引入配置文件,并在 plugins 内调用即可:
const OptimizeCssAssetsWebpackPlugin = require('optimize-css-assets-webpack-plugin');
module.exports = {
... // 此处省略其他配置
plugins: [
new OptimizeCssAssetsWebpackPlugin()
]
}
const OptimizeCssAssetsWebpackPlugin = require('optimize-css-assets-webpack-plugin');
module.exports = {
... // 此处省略其他配置
plugins: [
new OptimizeCssAssetsWebpackPlugin()
]
}
配置好后运行打包命令,我们便能看到 CSS 文件的压缩结果。

我们发现,打包编译后的 CSS 文件内容都压缩成了一行,我们这个项目的样式代码比较少,所以压缩程度不明显。在日常开发中,CSS 样式的压缩可以降低文件大小,提升文件请求速度,从而使页面样式更快的渲染出来,提升用户体验。
处理 js 文件
js 语法检查
在日常开发中,为了方便团队的维护,我们希望团队成员所写的代码风格差不多。所以我们就会用到 js 的语法检查,js 语法检查可以规范我们开发时所写的代码风格,如果风格和所配置的不符则会报错。同时 js 语法检查还可以检查一些常见的语法错误,让我们的代码不容易出现常见的错误。
语法检查通常使用的工具是 eslint。在 webpack 中我们需要使用 eslint-loader 库,而 eslint-loader 库又依赖 eslint 库,所以我们需要将两者都下载到项目。后面配置 airbnb 代码风格时,我们还会用到 eslint-config-airbnb-base 库,使用 eslint-config-airbnb-base 库又需要下载 eslint 和 eslint-plugin-import 库。eslint 只用下载一次即可,所以我们这里一共需要下载 eslint、eslint-loader、eslint-config-airbnb-base、eslint-plugin-import 这四个库。
下载好需要的库后,首先我们先在 package.json 中继承我们的风格(风格配置也可以放到 .eslintrc.js 文件中):
{
// 省略其他配置
"browserslist": {
"development": [],
"production": []
},
"eslintConfig": {
"extends": "airbnb-base"
}
}
{
// 省略其他配置
"browserslist": {
"development": [],
"production": []
},
"eslintConfig": {
"extends": "airbnb-base"
}
}
接下来我们在 webpack.config.js 配置 loader:
module.exports = {
... // 此处省略其他配置
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
loader: 'eslint-loader',
options: {
fix: true,
},
},
],
}
module.exports = {
... // 此处省略其他配置
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
loader: 'eslint-loader',
options: {
fix: true,
},
},
],
}
配置中我们匹配了所有 js 文件,node_modules 中的我们下载的第三方库文件也会被检查到。而语法检查只需要检查自己写的源代码,第三方的库是不用检查的,所以这里将 node_modules 排除在外。在大量开发后,语法检测出的错误会有很多,逐个修改很浪费时间和精力,所以我们在 eslint-loader 的配置项内设置了 fix: ture
,可以在编译打包时自动修复 eslint 的错误。
注意:airbnb 是很著名的一个风格,其中具体的风格写法可以查看 airbnb/javascript 。
js 兼容性处理
目前 ES6 语法已经相当普及,很多开发团队的项目都是使用 ES6 语法的,但是有些浏览器是不识别 ES6 语法的,所以我们需要在打包编译过程中做语法兼容性处理,使我们的代码在生产环境下有很好的兼容性。
首先我们在 js 文件中使用一些 ES6 语法,并直接打包编译,看看输出结果会是如何。

我们会发现,打包编译后的代码并没有做兼容性处理,依然是 ES6 的语法。所以我们需要用到 babel 来做兼容性处理,在 webpack 中需要使用到 babel-loader 和 @babel/core 库,而后面配置 babel-loader 的预设值时我们会用到 @babel/preset-env 库,所以我们先下载这三个库,然后进行配置:
module.exports = {
... // 此处省略其他配置
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env'],
},
},
],
}
module.exports = {
... // 此处省略其他配置
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env'],
},
},
],
}
这里我们同样需要使用 exclude 排除 node_modules 内的代码不做处理。babel-loader 配置项内的 presets 为预设,用来指示 babel 做怎么样的兼容性处理。这里我们使用了 @babel/preset-env,它可以帮我们做基本的 js 兼容性处理。配置好后我们尝试重新编译打包。

我们发现,打包编译后的代码被编译成 ES5 的语法,这样就可以在更多的浏览器上运行了。但是如果我们加一些比较高级的语法,比如 Promise 之后,编译打包的结果不会做兼容性处理。这时候我们就需要用到 @babel/polyfill 库,它可以帮我们做全部的 js 兼容性处理。这个库不需要设置 webpack,只需要在入口文件引入即可。

编译完后,我们发现 js 文件会比之前编译的大许多,这是因为文件引入了许多高级语法的兼容。所以使用 @babel/polyfill 后,即使我们只使用了部分需要兼容的语法,它会将所有语法的兼容都引入进来。这导致 js 文件体积太大,降低了加载时的性能。所以我们需要使用一个可以按需加载兼容性的库,core-js 库便可以做到这点。下载好 core-js 库后我们需要修改之前的 loader 配置:
module.exports = {
... // 此处省略其他配置
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
loader: 'babel-loader',
options: {
presets: [
[
'@babel/preset-env',
{
useBuiltIns: 'usage',
corejs: {
version: 3,
},
targets: {
chrome: '60',
firefox: '60',
ie: '9',
safari: '10',
edge: '17',
},
},
],
],
},
},
],
}
module.exports = {
... // 此处省略其他配置
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
loader: 'babel-loader',
options: {
presets: [
[
'@babel/preset-env',
{
useBuiltIns: 'usage',
corejs: {
version: 3,
},
targets: {
chrome: '60',
firefox: '60',
ie: '9',
safari: '10',
edge: '17',
},
},
],
],
},
},
],
}
其中, useBuiltIns: 'usage'
表示按需加载。corejs 为指定 core-js 版本,这里我们使用第三个版本。targets 为指定兼容性做到哪个版本浏览器。当使用完 core-js 库后,@babel/polyfill 库就不用再引入了,将其卸载即可。我们编译打包后,发现 js 文件大小明显比之前全部兼容的方法小了许多,这样既保证了代码的兼容性,又兼顾了代码的加载性能。
注意:core-js 的详细用法可以参考 zloirock/core-js 。
js 的压缩
js 的压缩不用我们刻意处理,只需要将 webpack 配置项 mode 改为 production 即可。当 mode 为 production 时,webpack 会启动许多自带的库(webpack 基础学习中有总结),其中 UglifyJsPlugin 会将 js 代码进行压缩。
处理 html
在 webpack 的处理中,html 是不需要做兼容性处理的,只需要将 html 代码进行压缩。压缩 html 只需要在之前配置的 html-webpack-plugin 库内进行配置即可:
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
... // 此处省略其他配置
plugins: [
new HtmlWebpackPlugin({
template: './src/index.html',
minify: {
collapseWhitespace: true,
removeComments: true,
},
}),
],
}
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
... // 此处省略其他配置
plugins: [
new HtmlWebpackPlugin({
template: './src/index.html',
minify: {
collapseWhitespace: true,
removeComments: true,
},
}),
],
}
配置中 minify 的值是压缩 html 的配置,collapseWhitespace 表示是否移除空格,removeComments 表示是否移除注释。配置完后我们重新打包,就能看到 html 的压缩效果了。
注意:html-webpack-plugin 其他配置可以参考 html-webpack-plugin。