前言
webpack 是一种前端资源构建工具——它是一种工具。我们日常生活中遇到新型工具时,都是上手实际操作一下才能掌握工具的使用。在学会使用工具前,我们是不会刻意去先理解工具的原理。webpack 的学习也是如此,有部分开发人员在学习 webpack 时都找错了方向。所以我在简单介绍完 webpack 的基础概念后,写下这篇文章,让大家会简单的使用 webpack 。
一切从零开始
首先,我们新建一个文件夹,文件夹名即为项目名。打开终端,使用 npm init
命令初始化一个包管理文件(package.json),初始化时名称填项目名,其他先使用默认即可。

接下来我们就能下载包了,先使用 npm i webpack webpack-cli -g
命令全局安装 webpack 和 webpack-cli 包(已经全局安装过的可以不用安装,但要注意版本)。安装完后使用 npm i webpack webpack-cli -D
命令将 webpack 和 webpack-cli 作为开发依赖安装到 package.json 中。
注意:
-g
是指全局安装,这是为了将来可以作为指令直接调用这两个包。因为我们配置的是开发环境,所以除了此处的全局安装外,本文其他地方包的安装都可以安装到开发依赖中(使用-D
)。
文件目录及 js、json 文件的打包
当我们下载好包后,项目内会有 package.json 文件、package-lock.json 文件和 node_modules 文件夹。package-lock.json 文件是 package.json 文件的补充,用来锁定安装时包的版本号,以防多人协作时因包版本的问题而造成的项目崩溃(具体在博客 npm 相关文章中讲解)。为了保证项目在多人协作时的可维护性,不同团队会制定团队内的项目目录规范,而绝大多数团队都会在项目的根路径建立 src 文件夹,用于项目代码的存放,这里我们也新建 src 目录,并且在此目录下新建 index.js 文件作为项目的入口文件。当项目打包后,我们需要指定输出路径,通常会把输出目录命名为 dist ,这里我们先手动创建,等深入学习后可以在打包时自动创建这个路径。

接下来我们就先使用 webpack 的打包指令体验一下 js 文件的打包。首先介绍两个运行指令:
webpack ./src/index.js -o ./dist --mode=development
此指令的意思为:webpack 会以 ./src/index.js 为入口文件开始打包,打包后输出到 ./dist/index.js ,整体打包环境是开发环境。webpack ./src/index.js -o ./dist --mode=production
此指令的意思为:webpack 会以 ./src/index.js 为入口文件开始打包,打包后输出到 ./dist/index.js ,整体打包环境是生产环境。
我们可以在终端输入这两个指令来让 webpack 进行打包操作。现在我们先在 index.js 内简单写些代码:
function add(x, y) {
return x + y;
}
const value = add(1, 3);
console.log(value);
function add(x, y) {
return x + y;
}
const value = add(1, 3);
console.log(value);
然后在终端执行 webpack ./src/index.js -o ./dist/index.js --mode=development
命令,我们会发现 dist 文件夹下多了 main.js 文件,此文件即为我们使用 webpack 打包后的文件。文件中 "./src/index.js"
代表我们所打包的 index.js 文件,而在它下方的 eval()
函数内,是 index.js 中的代码。

而如果执行第二个命令,那么打包出来的文件会被压缩,文件体积会变小。使用开发环境或生产环境的命令打包出来的文件均可执行,可以使用 node 执行验证(node ./dist/main.js
)。由于没有其他配置,此时开发环境和生产环境的区别仅是代码是否被压缩。
如果想在浏览器里验证结果,我们需要在 dist 目录下新建 index.html 文件,并引入打包后的资源,然后用浏览器打开即可。等深入学习后可以在打包时自动创建这个文件,并自动引入入口文件。

接下来我们验证一下 json 文件的打包,在 src 目录下新建 data.json 文件,并添加一些 json 数据。然后在 index.js 文件內使用 ES6 module 引入 json 文件,再将数据打印出来。


运行开发环境打包后,我们发现 main.js 文件里多了 json 文件的引入,使用浏览器打开 index.html 文件,在控制台中也能看到数据的打印。

通过 js 文件、json 文件的引入,我们知道了 webpack 能处理这两种文件类型。并且无论是生产环境还是开发环境,都能将 ES6 module 编译成浏览器能识别的模块化(未来专开文章讲)。那么如何打包其他类型的引入文件呢?
配置文件及样式资源的打包
接下来我们学习样式资源的打包。首先我们在 src 目录下新建 index.css 文件,然后简单写一些样式,并在 index.js 中引入。

如果此时我们运行打包指令,会发现终端报错。

这是因为 webpack 默认无法识别 js、css 以外的模块文件,那么我们需要使用 loader 来帮助 webpack 解析、翻译一些无法识别的模块文件。而 loader 需要定义 webpack 配置文件来使用,所以我们在项目根目录下(即与 package.json 同级)新建 webpack.config.js 文件,文件内容及分析如下:
/**
* webpack.config.js:webpack 的配置文件
* 作用:指示 webpack 干哪些活(当你运行 webpack 指令时,会加载里面的配置)
*
* 所有构建工具都是基于 nodejs 平台运行的,模块化默认采用 commonjs。(项目代码一般用 ES6)
*/
// resolve 用来拼接绝对路径的方法
const { resolve } = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");
// 使用 module.exports 暴露对象(commonjs)
module.exports = {
// 入口起点
entry: "./src/index.js",
// 输出
output: {
// 输出文件名
filename: "main.js",
// 输出路径
// __dirname:nodejs 的变量,代表当前文件的目录绝对路径
path: resolve(__dirname, "dist"),
},
// loader 的配置
module: {
rules: [
// 详细loader配置
// 不同文件必须配置不同 loader 处理
{
// 匹配哪些文件(以 .css 结尾)
test: /\.css$/,
// 使用哪些 loader 进行处理。use 数组中 loader 执行顺序:从右到左,从下到上,依次执行
use: [
// 创建 style 标签,将 js 中的样式资源插入进去,添加到 head 中生效
{ loader: "style-loader" },
// 将 css 文件变成 commonjs 模块加载 js 中,里面内容是样式字符串
{
loader: "css-loader",
options: {
modules: false,
},
},
],
},
],
},
// plugins 的配置
plugins: [],
// 模式
mode: "development",
};
/**
* webpack.config.js:webpack 的配置文件
* 作用:指示 webpack 干哪些活(当你运行 webpack 指令时,会加载里面的配置)
*
* 所有构建工具都是基于 nodejs 平台运行的,模块化默认采用 commonjs。(项目代码一般用 ES6)
*/
// resolve 用来拼接绝对路径的方法
const { resolve } = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");
// 使用 module.exports 暴露对象(commonjs)
module.exports = {
// 入口起点
entry: "./src/index.js",
// 输出
output: {
// 输出文件名
filename: "main.js",
// 输出路径
// __dirname:nodejs 的变量,代表当前文件的目录绝对路径
path: resolve(__dirname, "dist"),
},
// loader 的配置
module: {
rules: [
// 详细loader配置
// 不同文件必须配置不同 loader 处理
{
// 匹配哪些文件(以 .css 结尾)
test: /\.css$/,
// 使用哪些 loader 进行处理。use 数组中 loader 执行顺序:从右到左,从下到上,依次执行
use: [
// 创建 style 标签,将 js 中的样式资源插入进去,添加到 head 中生效
{ loader: "style-loader" },
// 将 css 文件变成 commonjs 模块加载 js 中,里面内容是样式字符串
{
loader: "css-loader",
options: {
modules: false,
},
},
],
},
],
},
// plugins 的配置
plugins: [],
// 模式
mode: "development",
};
配置完文件,我们需要下载用到的包(css-loader、style-loader),使用 npm i css-loader style-loader -D
命令下载。下载之后,只需要输入 webpack
即可启动 webpack 打包(因为参数我们在配置文件已经配置)。打包后我们观察打包后的文件 main.js。

文件中我们可以清晰的看到 loader 的使用顺序,并且还能找到 css 源码。用浏览器打开我们之前所建的 html 文件,会发现样式都已生效,打开控制台我们会发现,head 标签内插入了 style 标签,style 标签内包含了我们写的样式。

有时我们会在项目中使用 less 文件来写样式。我们在 src 目录下新建 index.less 文件,在入口文件中引入,并在 html 文件中添加一个 DOM。运行打包命令后,终端会报错。

在 webpack 中,不同文件必须配置不同 loader 处理。所以我们需要添加 loader 配置:
{
test: /\.less$/,
use: [
"style-loader",
"css-loader",
// 将less文件编译成css文件,需要下载 less-loader 和 less
"less-loader",
],
},
{
test: /\.less$/,
use: [
"style-loader",
"css-loader",
// 将less文件编译成css文件,需要下载 less-loader 和 less
"less-loader",
],
},
这里的写法与 css 文件配置不同,如果不用给 loader 传参,可以使用这样的简写来配置。每个文件的配置的 loader 不可复用,所以即使我们在 css 文件配置里写了 css-loader 和 style-loader,这里仍然要写。但是我们可以不用重复下载这些包,只用下载新添加的包(less-loader、less)。下完包运行命令,webpack 可以正常打包,并且浏览器里的样式也正常生效。
html 资源的打包
前面我们已经学习到将一些常用的资源打包,当我们要验证打包后是否能运行时,使用的还是之前自己手动创建的 index.html 文件。这样非常的不方便,那么我们来看一下如何通过 webpack 来打包 html 文件。
与前面讲的资源打包不同的是,html 资源的打包需要用到 plugins。我们使用 loader 时需要:1.下载;2.使用(配置 loader)。而使用 plugins 时需要:1.下载;2.引入;3.使用。这里我们需要用的 plugin 是 html-webpack-plugin,那么我们先来下载这个包。
下载完包后,我们要在配置文件里引入包。由于 plugin 对外导出的是构造函数,所以我们接收时变量名需要用首字母大写的方式,并且在使用时需要用 new 调用,配置如下:
const HtmlWebpackPlugin = require("html-webpack-plugin");
module.exports = {
// ...其余配置省略
plugins: [new HtmlWebpackPlugin()],
};
const HtmlWebpackPlugin = require("html-webpack-plugin");
module.exports = {
// ...其余配置省略
plugins: [new HtmlWebpackPlugin()],
};
此时我们就已使用这个 plugin 了,那这个 plugin 有什么用呢?我们先来运行一下打包命令,会发现终端并没有报错,而我们之前手动创建的 dist/index.html 文件被覆盖了(里面我们写的一些内容消失了),但是 main.js 文件的引入还在。这是因为这个 plugin 默认会创建一个空的 html 文件,自动引入打包输出的所有资源(js、css)。而为了测试之前的样式资源的打包,我们需要的是有结构的 html 文件,所以我们需要再根目录下新建 index.html 文件,在这个文件中写入我们想要的结构(不用写打包后资源的引入)。然后需要在配置文件 plugin 中做一些配置:
const HtmlWebpackPlugin = require("html-webpack-plugin");
module.exports = {
// ...其余配置省略
plugins: [
new HtmlWebpackPlugin({
template: "./index.html",
}),
],
};
const HtmlWebpackPlugin = require("html-webpack-plugin");
module.exports = {
// ...其余配置省略
plugins: [
new HtmlWebpackPlugin({
template: "./index.html",
}),
],
};
这个配置的意义为,不让 plugin 创建空的 html 文件,而是使用 ./index.html 文件,在此基础上自动引入打包输出的所有资源。配置完后我们运行打包命令即可验证 html 资源的打包。
注意:对于 loader 以及 plugin,不管是官方提供的还是第三方开发的,它们都会有一些配置项提供给开发者使用。这些配置项的文档我们可以在 npm、github 等网站中寻找并查看。
图片资源的打包
平常开发时,我们会常用到图片资源,接下来我们看一下图片资源如何打包。首先我们需要做些准备用作验证。

我们一般添加图片资源时会在样式文件中添加,所以之前讲过的样式资源的打包配置必不可少,这里就不过多赘述了。而解析图片资源时,webpack 是无法识别这些图片资源的,所以我们需要添加 loader 来帮助它识别。
{
test: /\.(jpg|png|gif|jpeg)$/,
// 需要下载 url-loader file-loader
loader: "url-loader",
options: {
limit: 30 * 1024,
name: "[hash:10].[ext]",
outputPath: "assets",
},
}
{
test: /\.(jpg|png|gif|jpeg)$/,
// 需要下载 url-loader file-loader
loader: "url-loader",
options: {
limit: 30 * 1024,
name: "[hash:10].[ext]",
outputPath: "assets",
},
}
需要注意的是,url-loader 需要下载 url-loader 和 file-loader 两个包。当我们多个文件使用同一个 loader 解析时,可以在 test 选项中使用正则表达式来实现。如果只是使用一个 loader,那么就可以不使用 use 配置,直接使用 loader 即可。
这个 loader 有很多实用的配置项。limit 配置项是指当图片资源大小小于 30 kb 时,就会被处理为 base64,它接收的参数是以字节为单位的,所以需要注意单位换算。name 配置项是用来配置处理后文件的文件名的,为了以防文件名重复,所以使用了处理时 hash 的前 10 位作为处理后的名称,并且使用原文件的后缀作为处理后文件的后缀。outputPath 配置项是用来配置图片资源输出的目录。
注意:将图片资源处理为 base64 的优点在于可以减少请求数量,从而减轻服务器的压力。而缺点在于图片体积会更大,导致文件请求变慢。这是由于图片转化成 base64 的过程中,大小会增大 1/3 左右。并且,在打包过程中,相同的 base64 在项目中使用过几次,base64 就会复制几份(base 64 本质是字符串,不容易提取成公共部分)。所以使用时要合理的安排处理策略,通常 12 kb 大小以下的小图片用 base64 处理。这里为了演示效果,所以设置成了 30kb。
下载完需要的包后,我们运行打包命令,会发现 dist 路径下多了 assets 文件夹,并且里面存放着我们使用的图片资源。但是大家会发现,我截图中打包前目录中是有三个图片资源的,打包后却只有两个,这是因为其中一个图片资源符合条件,被处理成 base64 了,我们打开 main.js 后依然可以看到图片资源的引入。如果搜索 base64 则可以看到被处理成 base64 的资源是如何引入的。

我们使用打开浏览器的控制台,在 Elements 中可以更明显的看出两种处理方式的不同。一般的图片资源使用图片的方式是 background-image: url(图片路径);
,我们在 Network 中也可以看到请求了相应的图片资源。而 base64 的方式则是 background-image: url(data:image/jpeg;base64,+ base64 编码);
。我们对未处理成 base64 的图片多次引用,会发现打包后图片数量不会发生改变,这是因为 webpack 不会重复打包同一个图片资源。

我们在开发中引入图片时,除了使用样式的方式引入,还有可能在 html 中通过 img 标签来引入,显然前面配置的处理样式中图片资源的 loader 是无法处理这种情况的。所以我们需要为 html 配置 loader 来帮 webpack 解析模块中的 url。我们使用到的是 html-loader,首先添加 loader 配置:
{
test: /\.html$/,
loader: "html-loader",
},
{
test: /\.html$/,
loader: "html-loader",
},
配置后我们下载 html-loader 包,并运行打包命令。webpack 可以正常打包,但是我们打开浏览器后,会发现图片无法正常显示。检查打包后的 index.html 我们会发现 img 标签的引用路径出错了。解决这个问题的方法有很多,我们先使用最简单的一种,其他方法以后会出相关文章。我们可以换 html- withimg-loader 来代替 html-loader,这个 loader 是在 html-loader 原有功能基础上做了一些加强,其中就有路径处理。使用方法和 html-loader 一模一样,使用时记得删除 html-loader 避免冲突。配置完下载完成后,运行打包命令我们就可以验证 img 标签的打包了。
注意:将图片资源放在项目中会造成项目体积过大,从而造成拉取代码过慢(我曾遇到项目代码大小 5G 的情况)。所以还有一种做法是将图片资源上传到 cdn 上,使用时直接用 cdn 的 url,这样可以有效的减小项目体积。
其他资源的打包
我们在开发中还会打包其他资源,比如字体图标资源等,这类资源的特点是我们不需要做任何处理,只需要原封不动的打包输出即可。首先我们准备一些字体图标资源并在项目中使用,由于只是为了测试验证,所以我们在 index.html 文件中用 font-class 的方式使用字体图标即可,并在入口文件中引入字体图标样式。

接下来我们来进行 webpack 配置。观察我们所用的字体图标资源后我们发现,字体图标资源的入口是 iconfont.css 模块,其他文件都是被这个模块引用。我们之前已经处理过 css 模块,所以这里我们只用考虑如何打包其他类型的模块,这些模块不需要我们特殊处理,只要原样输出即可。
{
exclude: /\.(html|js|css|less|jpg|png|gif|jpeg|json)$/,
loader: 'file-loader'
}
{
exclude: /\.(html|js|css|less|jpg|png|gif|jpeg|json)$/,
loader: 'file-loader'
}
我们依然是使用 file-loader 来处理其他资源,这里我们的匹配方式使用的是 exclude,其功能是匹配到所配置的模块类型以外的模块,类似于“反选”。也就是说我们排除了之前所配置的特殊处理的模块,一般在配置 webpack 时,都会用这个选项“兜底”,将不需要特殊处理的模块原样输出。
配置完打包后,我们验证到字体图标可以正常使用。打包后的模块被直接输出到 dist 目录下,并且文件名很长,我们可以使用之前的方法去对文件名进行优化,这里就不再赘述了。
其他配置
开发服务器 devServer 的配置
我们平时开发代码是个连续的过程,现在我们的项目如果添加新的代码,需要重新打包编译后才能显示出来效果,这样开发非常麻烦且耗时。在写代码的同时能实时看到代码所实现的效果会提高开发的效率,而开发服务器 devServer 则可以实现自动编译、自动打开浏览器、自动刷新浏览器等功能,很好的辅助我们开发。
devServer 不属于我们之前介绍的五大核心功能,具体配置如下:
module.exports = {
// 五大核心内容此处省略
entry: './src/index.js',
output: {...},
module: {...},
plugins: [...],
mode: 'development',
devServer: {
contentBase: resolve(__dirname, "dist"),
compress: true,
port: 3000,
open: true,
},
}
module.exports = {
// 五大核心内容此处省略
entry: './src/index.js',
output: {...},
module: {...},
plugins: [...],
mode: 'development',
devServer: {
contentBase: resolve(__dirname, "dist"),
compress: true,
port: 3000,
open: true,
},
}
配置中,contentBase 选项是用于配置项目构建后的输出路径。compress 选项是用于配置是否启动 gzip 压缩,gzip 压缩可以让我们代码体积更小,从而使请求的速度更快。port 选项用于指定开发服务器的端口号。open 选项可以使项目编译完,自动打开电脑默认浏览器并访问项目地址。
配置完成后,我们需要下载 webpack-dev-server 包。启动 devServer 的指令为 npx webpack-dev-server
。当我们执行指令后,终端会有 “webpack X.X.X compiled successfully in Xms” 的提示,并且会弹出默认浏览器访问我们开发的项目。当我们修改代码并保存后,devServer 会监测到 src 目录下的源代码发生变化,然后触发项目自动重新编译,而浏览器访问的内容也会自动刷新。需要注意的是,devServer 只会在内存中编译打包,不会有任何输出,当我们停止 devServer 运行时,内存中相关内容会自动删除。
源代码压缩配置
现在我们的项目可以满足一般的项目开发,但是我们发现,在使用浏览器控制台调试时,代码会被压缩,不利于我们的调试。所以我们需要配置开发环境下代码不被压缩。
module.exports = {
// 其他内容此处省略
entry: './src/index.js',
output: {...},
module: {...},
plugins: [...],
mode: 'development',
devServer: {...},
devtool: "cheap-source-map",
}
module.exports = {
// 其他内容此处省略
entry: './src/index.js',
output: {...},
module: {...},
plugins: [...],
mode: 'development',
devServer: {...},
devtool: "cheap-source-map",
}
devtool 便是配置打包后代码不被压缩的配置项,需要注意它与 devServer 一样在配置文件的根节点上。