前言
阅读此文需要结合webpack官方中文文档
1.什么是webpack?
webpack是一套基于Node.js的模块打包工具,在webpack刚推出的时候,它是一个单纯的js模块打包工具,可以将多个模块的js文件打包合并到一个文件中,随着时间的推移和众多开发者的追捧和贡献,现在的webpack不仅仅能打包js文件,还可以打包css/less/scss/图片等其它文件。
为什么要分模块?
由于在开发中,为了提升代码的可维护性和可复用性,我们会将不同的功能写到不同的模块中。但是这样就会面临一些问题,比如:
(1).导入资源变多了,请求的次数也变多了,性能就变差了;
(2).如何维护模块之间的关系?
如何解决这些问题呢?
(1).项目上线时将所有的模块都合并到一个文件中去
(2).在index.html中只引用主文件,在主文件中导入依赖模块
假设,现在有三个模块,它们的功能分别是创建了网页的头部、内容、底部区域:

// header.js模块
function addHeader() {
const header = document.createElement('div')
header.innerText = '我是头部'
document.body.appendChild(header)
}
// content.js模块
function addContent() {
const content = document.createElement('div')
content.innerText = '我是内容'
document.body.appendChild(content)
}
// footer.js模块
function addFooter() {
const footer = document.createElement('div')
footer.innerText = '我是底部'
document.body.appendChild(footer)
}
这时,我们想要在一个index.js中去使用这三个模块,就要先导入这三个模块,然后导入index.js,然后在index.js执行addHeader,addContent,addFooter这三个方法,这里就会有一个先后顺序:
// index.js
addHeader()
addContent()
addFooter()
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<script src="index.js"></script>
<script src="header.js"></script>
<script src="footer.js"></script>
<script src="content.js"></script>
</body>
</html>
如果模块加载的时候先加载index.js,后加载三个模块,肯定就会出问题:

因此,当项目过大的时候,这种关系维护起来非常麻烦,所以我们要利用webpack来帮我们打包
2.webpack的安装
前面说过,由于webpack是基于Node.js的,因此在安装webpack前需要确保已经安装了node
先来初始化一个项目,生成package.json:
// y是yes的意思,可以快速初始化,省去了npm init的时候一步一步敲回车的过程
npm init -y
然后开始安装webpack4+,需要两条命令:
npm install --save-dev webpack
npm install --save-dev webpack-cli
命令执行完以后,就可以看到package.json中多了两项,这就代表安装好了:

3.如何打包一个文件
我们将刚才的header、content、footer包装成真正的模块,暴露出来给index.js来使用:
// header.js模块
function addHeader() {
const header = document.createElement('div')
header.innerText = '我是头部'
document.body.appendChild(header)
}
export default addHeader
// content.js模块
function addContent() {
const content = document.createElement('div')
content.innerText = '我是内容'
document.body.appendChild(content)
}
export default addContent
// footer.js模块
function addFooter() {
const footer = document.createElement('div')
footer.innerText = '我是底部'
document.body.appendChild(footer)
}
export default addFooter
// index.js
import headerModule from './header.js'
import contentModule from './content.js'
import footerModule from './footer.js'
headerModule()
contentModule()
footerModule()
然后打包index.js:
// npx webpack 打包文件的名称
npx webpack index.js
index.js就是我们需要打包的文件,等待命令执行完以后,可以看到目录中多了一个dist文件夹,里面有一个main.js文件,这个main.js就是对index.js打包之后的结果:

在html中引入main.js:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<script src="dist/main.js"></script>
</body>
</html>
打包后执行的最终结果:

4.webpack的配置文件
什么是webpack的配置文件?
前面我们说可以通过npx webpack index.js命令去打包index.js文件,其实在webpack中,除了可以用命令行的形式去打包文件之外,还可以通过配置文件的形式来打包文件
官方文档说,配置是写在一个叫webpack.config.js的文件里面的,行,那我们先不管三七二十一,新建一个webpack.config.js文件,依样画葫芦

然后我们来看看webpack.config.js各项配置的含义:
// 这里需要用到node的path模块
const path = require("path")
module.exports = {
/*
mode:指定打包的模式,模式有两种
开发模式(development):不会对打包的js代码进行压缩
生产模式(production):会对打包的js代码进行压缩
*/
mode: "development", // "production" | "development"
/*
entry:指定需要打包的文件
*/
entry: "./index.js",
/*
output:指定打包后文件输出的路径和输出文件的名称
*/
output: {
/*
filename: 指定打包之后的JS文件的名称
*/
filename: "bundle.js",
/*
path: 指定打包之后的文件存储到什么地方
__dirname代表当前这个js文件所在目录的绝对路径
*/
path: path.resolve(__dirname, "bundle")
}
}
配置好以后,我们来看看效果,在命令行中输入npx webpack,当后面没有文件名的时候,会自动去查找webpack.config.js配置文件进行打包
npx webpack
执行完成后,可以看到根目录中多了一个bundle文件夹,文件夹中多了一个bundle.js,说明打包成功

如果是开发模式(mode为development),bundle.js中的代码是没有压缩的

如果是生产模式(mode为production),bundle.js中的代码是压缩过的

接下来我们使用新打包生成的bundle.js文件
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<!--<script src="dist/main.js"></script>-->
<script src="bundle/bundle.js"></script>
</body>
</html>
可以看到效果和之前是一样的:

webpack配置的注意事项
当我们去执行npx webpack命令的时候,webpack会自动去找到叫webpack.config.js的配置文件,根据配置去打包,如果找不到这个文件的话,会报错
那这个配置文件可以叫其它名字吗?当然可以,需要这么一条命令:
// npx webpack --config 文件名
npx webpack --config xxx
可是每次输入这么一条指令,我觉得又长又麻烦,我们可以利用package.json的script属性来保存指令,比如这样:

然后每次我们在命令行输入npm run build的时候,就等于输入了npx webpack --config new.config.js,或者直接点击前面绿色的三角形也可以执行这条命令
5.sourcemap
什么是sourcemap?
先来看,我们在index.js中写上这样一行代码
// index.js
console.log(a)
打包之后生成bundle.js,然后引用,结果肯定是报错,因为a并没有声明,但重点是报错文件显示在第97行,我明明只写了一行代码呀

原因很简单,打包后的bundle.js会自己添加很多代码,那这样就有一个问题,如果我们写的代码出现问题了,这里定位起来就非常麻烦,非常不利于调试,因为如果运行webpack打包后的代码,错误提示的内容也是打包后文件的内容
所以为了降低调试的难度, 提高错误代码的阅读性, 我们就需要知道打包后代码和打包之前代码的映射关系
只要有了这个映射关系我们就能很好的显示错误提示的内容, 存储这个映射关系的文件我们就称之为sourcemap
如何开启sourcemap?
通过webpack的devtool属性可以开启,具体可以参考devtool

参照官方文档,devtool属性可以有很多取值。需要了解一下,在构建速度这一列中,+代表可以加快打包的速度,-代表会降低打包的速度。由于这个属性的取值非常多,我们可以根据这些它们的规律来了解:
(1).eval
当devtool为none的时候,代码是这样的
devtool: "none"

我们把devtool设置成eval后,可以看到我们代码被放进了eval之中,并且还存储了它的映射关系
devtool: "eval"

总结:不会单独生成sourcemap文件, 会将映射关系存储到打包的文件中, 并且通过eval存储
优点:性能最好,但是在企业开发中不推荐
缺点:会让打包后的文件变得更加冗余,业务逻辑比较复杂时候提示信息可能不全面不正确
(2).sourcemap
如果是取值后面带有source-map的,就代表会将映射关系存储到单独的文件之中
比如我们将devtool设置成cheap-source-map
devtool: "cheap-source-map"
可以看到,bundle.js中多了bundle.js.map的文件,这个文件中就存储了映射关系

总结:会单独生成sourcemap文件, 通过单独文件来存储映射关系
优点:提示信息全面,可以直接定位到错误代码的行和列
缺点:打包速度慢
(3).inline
如果将devtool设置成带inline的值会怎么样?
devtool: "inline-cheap-source-map"
打包完成之后,可以看到bundle.js下面没有sourcemap文件

并且在bundle.js中,多了一个sourceMappingURL字段,在这个字段中以字符串的形式存储了映射关系

总结:不会单独生成sourcemap文件, 会将映射关系存储到打包的文件中, 并且通过base64字符串形式存储
(4).cheap
如果是取值中带cheap的,表示生成的映射信息只能定位到错误行不能定位到错误列
(5).module
如果是取值中带module的,就表示不仅希望存储我们代码的映射关系, 还希望存储第三方模块映射关系, 以便于第三方模块出错时也能更好的排错
在企业开发中,通常配置如下:
development: cheap-module-eval-source-map
只需要行错误信息, 并且包含第三方模块错误信息, 并且不会生成单独sourcemap文件
production: cheap-module-source-map
只需要行错误信息, 并且包含第三方模块错误信息, 并且会生成单独sourcemap文件
开启sourcemap之后,现在的报错提示可以让我们轻易的找到错误行了


6.file-loader
使用fileloader打包图片
前面说webpack不仅能打包js,还可以打包图片,那我们来试试,在index.js中往body插入一张图片(请注意这里打印了img)
// 引入一张图片
const img = require('./img/wechatheader.png')
// 打印img看看
console.log(img, 'img')
const imgNode = document.createElement('img')
imgNode.src = img
document.body.appendChild(imgNode)
然后执行npx webpack index.js,结果报错了,提示:你可能需要一个loader去处理文件类型,那问题来了,什么是loader?

什么是loader?
由于webapck的本质是一个模块打包工具, 所以webpack默认只能处理JS文件,不能处理其他文件,因为其他文件中没有模块的概念;
但是在企业开发中我们除了需要对JS进行打包以外,还有可能需要对图片/CSS等进行打包,,所以为了能够让webpack能够对其它的文件类型进行打包,在打包之前就必须将其它类型文件转换为webpack能够识别处理的模块;
用于将其它类型文件转换为webpack能够识别处理模块的工具我们就称之为loader
怎么使用loader?
webpack中的loader都是用NodeJS编写的,但是在企业开发中我们完全没有必要自己编写,因为已经有现成的,我们只需要安装、配置、使用即可
由于import是用来导入模块的,所以我们需要把图片转成模块,需要用到file-loader
根据文档教程,我们先来安装file-loader
npm install --save-dev file-loader
安装好之后,需要在webpack.config.js配置文件中进行配置
注意:现在回头来看我们为什么要打印img。在file-loader 4.3.0之前,导入的img通过file-loader转换以后,输出的是图片的路径;但是从4.3.0开始默认使用了esModule语法,转换后输出的是一个Moudle对象

可以使用Module.default拿到图片的路径,或者设置esModule为false,让img变成图片的路径
module.exports = {
/*
module:告诉webpack如何处理webpack不能够识别的文件
*/
module: {
/*
rules:rules表示规则,rules数组下的一个对象就代表一个规则
*/
rules: [
/*
test:但凡正则表达式匹配到png、jpg、gif结尾的文件
use:就使用file-loader进行处理
*/
{
test: /\.(png|jpg|gif)$/,
use: [
{
loader: 'file-loader',
options: {
// file-loader新版本默认使用了esModule语法
esModule: false,
}
}
]
}
]
}
}
这时再来看看img打印的结果,就变成了打包之后的图片地址了

配置好规则以后,file-loader会将图片转换成模块,这样我们在index.js中去导入之后,再打包就不会报错,可以看到图片被打包到了bundle中了,名称变成了一个MD5的哈希值

再来引入bundle.js看看效果,图片就被顺利加载出来了

fileloader的其它配置
通过上一步可以看到,打包之后的图片名称是一个MD5的哈希值,那我们可以改变这个图片的名称吗?或者我们想要改变打包后图片的路径,这些可以通过配置来实现
module: {
/*
rules:rules表示规则,rules数组下的一个对象就代表一个规则
*/
rules: [
/*
test:但凡正则表达式匹配到png、jpg、gif结尾的文件
use:就使用file-loader进行处理
*/
{
test: /\.(png|jpg|gif)$/,
use: [
{
loader: 'file-loader',
options: {
// file-loader新版本默认使用了esModule语法
esModule: false,
/* 指定打包后文件名称
[name]表示打包后的文件名称和打包前一样
[ext]表示打包后的文件后缀和打包前一样
*/
name: '[name].[ext]',
// 指定打包后文件存放目录
outputPath: 'images/',
// 指定托管服务器地址(统一替换图片地址)
publicPath: 'http://www.it666.com/images/'
}
}
]
}
]
}
打包完成之后,可以看到图片的名称和打包前一样,并且被放到了images文件夹下面

这时候打印的img的地址,就变成了指定托管服务器地址,当我们把图片配置到第三方托管的时候,这样设置就可以拿到图片资源了

7.url-loader
打包图片不仅可以用前一小节说到的file-loader,还可以用url-loader;我们在官方的loader文档中找到教程,loader的使用方式都是一样的,这里就不再赘述了,第一步还是需要安装
npm install --save-dev url-loader
安装完成后进行配置,由于url-loader和file-loader的功能是差不多的,二者取其一就好,在配置url-loader之前先删掉原来的file-loader配置
module: {
/*
rules:rules表示规则,rules数组下的一个对象就代表一个规则
*/
rules: [
/*
test:但凡正则表达式匹配到png、jpg、gif结尾的文件
use:就使用file-loader进行处理
*/
{
test: /\.(png|jpg|gif)$/,
use: [
{
loader: 'url-loader',
options: {
esModule: false,
},
}
]
}
]
}
打包以后的效果:

打包后的文件结构:

在bundle文件夹中并没有看到图片文件,但在浏览器中还是可以看到图片,查看图片的路径发现图片被转成了base64的字符串

url-loader和file-loader的区别就是url-loader可以将图片转成base64的字符串,也可以不转,通过配置中的limit属性来决定:
如果图片的大小超过了limit限制的大小,图片就被保存成一个文件
如果图片的大小没有超过limit限制的大小,图片就会被转成base64的字符串
当前将limit配置为1024 * 100,也就是限制为100kb,图片的大小为232k,是大于limit的(其它的配置和file-loader是一样的)
module: {
/*
rules:rules表示规则,rules数组下的一个对象就代表一个规则
*/
rules: [
/*
test:但凡正则表达式匹配到png、jpg、gif结尾的文件
use:就使用file-loader进行处理
*/
{
test: /\.(png|jpg|gif)$/,
use: [
{
loader: 'url-loader',
options: {
esModule: false,
/*
limit: 指定图片限制的大小
* */
limit: 1024 * 100,
// 指定打包后文件名称
name: '[name].[ext]',
// 指定打包后文件存放目录
outputPath: 'images/',
},
}
]
}
]
}

打包后,可以看到bundle文件夹下面产生了一张图片,因此可以证明,当图片大小大于limit的时候,会被保存为文件的形式:

注意:
对于比较小的图片, 我们将图片转换成base64的字符串之后, 可以提升网页的性能(因为减少了请求的次数)
对于比较大的图片, 哪怕我们将图片转换成了base64的字符串之后, 也不会提升网页的性能, 还有可能降低网页的性能(因为图片如果比较大, 那么转换之后的字符串也会比较多, 那么网页的体积就会变大, 那么访问的速度就会变慢)
8.css-loader
和图片一样,webpack默认不能处理css文件,所以需要借助css-loader将css转换为wepack能够处理的类型,安装命令:
npm install --save-dev css-loader
我们在index.js中创建一个<img>标签,然后引入图片和css样式,css样式设置图片的边框为红色
// index.js
import img from './img/wechatheader.png'
import './index.css'
let oImg = document.createElement("img")
oImg.src = img
oImg.setAttribute("class", "border")
document.body.appendChild(oImg)
// index.css
.border{
border:1px solid red;
}
由于webpack默认情况下也是不能处理css文件的,现在打包肯定会报错:

根据官方文档的教程,我们在webpack.config.js中添加配置:
module.exports = {
module: {
rules: [
// 打包CSS规则
{
test: /\.css$/,
/*
css-loader: 解析css文件中的@import依赖关系
style-loader: 将webpack处理之后的内容插入到HTML的HEAD代码中
* */
use: [ 'style-loader', 'css-loader' ]
}
]
}
}
配置中出现了一个style-loader,也是需要安装才能使用的,官方建议将style-loader与css-loader结合使用:
npm install style-loader --save-dev
css-loader:解析css文件中的@import依赖关系,比如a.css中引入了b.css文件,说明a.css依赖b.css,那css-loader就会把b.css的代码解析到a.css中。
style-loader:将webpack处理之后的内容插入到HTML的HEAD代码中
打包后可以看到图片多了一层红色边框,说明css样式生效了;在html中,css代码也确实被插入到了<head>标签中:


css-loader注意点
(1).单一原则:一个loader只做一件事情
(2).多个loader会按照从右至左,从下至上的顺序执行
// 先执行css-loader解析依赖关系,然后执行style-loader将css代码插入<head>中
use: [ 'style-loader', 'css-loader' ]
// 先执行css-loader解析依赖关系,然后执行style-loader将css代码插入<head>中
use: [
{
loader: "style-loader" // creates style nodes from JS strings
},
{
loader: "css-loader" // translates CSS into CommonJS
}
]
css-loader的模块化
现在我们新建一个custom.js的文件,在里面添加一个addImage的方法,这个方法又创建了一张图片,然后暴露出来给index.js使用,这样一来我们就创建了两个<img>标签:
// custom.js
import img from './img/wechatheader.jpg'
function addImage() {
let oImg = document.createElement("img")
oImg.src = img
oImg.setAttribute("class", "border")
document.body.appendChild(oImg)
}
export {addImage}
// index.js
import img from './img/wechatheader.jpg'
import {addImage} from './custom.js'
import './index.css'
let oImg = document.createElement("img")
oImg.src = img
oImg.setAttribute("class", "border")
document.body.appendChild(oImg)
// 调用custom模块的方法新增一张图片
addImage()
// index.css
.border{
border:1px solid red;
transform: translate(200px, 200px);
}
打包后就看到了两张图片都被加上红色边框:

通过在index.js中使用import './index.css'这种方法引入的样式,会在index.js和custom.js中都生效,如果我们只想让引入的样式在一个模块中生效,就需要开启css-loader的模块化,在webpack.config.js中配置:
module: {
rules: [{
test: /\.css$/,
use: [
{
loader: "style-loader"
},
{
loader: "css-loader",
options: {
// 开启css的模块化
modules: true,
}
}
]
}]
}
在index.js中改变一下css样式文件的引用方式,同时打印一下拿到的cssModule:
// index.js
import img from './img/wechatheader.jpg'
import {addImage} from './custom.js'
// 改变引用方式
import cssModule from './index.css'
// 这里打印一下cssModule
console.log(cssModule, 'cssModule')
let oImg = document.createElement("img")
oImg.src = img
oImg.setAttribute("class", cssModule.border)
document.body.appendChild(oImg)
// 调用custom模块的方法新增一张图片
addImage()
打包完成后,可以看到只有一张图片被设置了红色边框,通过查看打印的cssModule,发现css样式被转换成了一个对象,通过cssModule.border就可以给图片设置样式:

9.less-loader
less-loader的作用是自动将less转成css,安装:
npm install --save-dev less-loader less
配置:
module: {
rules: [{
test: /\.less$/,
use: [{
loader: "style-loader" // creates style nodes from JS strings
}, {
loader: "css-loader" // translates CSS into CommonJS
}, {
loader: "less-loader" // compiles Less to CSS
}]
}]
}
10.sass-loader
sass-loader的作用是自动将scss转换为css,安装:
npm install sass-loader node-sass webpack --save-dev
配置:
module: {
rules: [{
test: /\.scss$/,
use: [{
loader: "style-loader" // 将 JS 字符串生成为 style 节点
}, {
loader: "css-loader" // 将 CSS 转化成 CommonJS 模块
}, {
loader: "sass-loader" // 将 Sass 编译成 CSS
}]
}]
}
11.postcss-loader
postcss和sass/less不同,它不是css预处理器,它是一款使用插件去转换css的工具;postcss有许多非常好用的插件,例如:
(1).autoprefixer(自动补全浏览器前缀)
(2).postcss-pxtorem(自动把px代为转换成rem)
在webpack中也有postcss-loader可以实现这些功能,首先来安装:
npm i -D postcss-loader
在webpack.config.js中添加配置:
module: {
rules: [
{
test: /\.css$/,
use: [
{
loader: "style-loader"
},
{
loader: "css-loader"
},
{
loader: "postcss-loader"
}
]
}
]
}
autoprefixer(自动补全浏览器前缀)
配置好postcss-loader之后,我们还需要添加插件autoprefixer,安装:
npm i -D autoprefixer
安装好autoprefixer之后,我们在根目录创建一个postcss.config.js配置文件:
module.exports = {
plugins: {
"autoprefixer": {
"overrideBrowserslist": [
"ie >= 8", // 兼容IE7以上浏览器
"Firefox >= 3.5", // 兼容火狐版本号大于3.5浏览器
"chrome >=35", // 兼容谷歌版本号大于35浏览器
"opera >= 11.5", // 兼容欧朋版本号大于11.5浏览器
]
}
}
}
接下来我们验证一下,在index.css中加入css3的属性transform,然后打包:
// index.css
.border{
border:1px solid red;
transform: translate(200px, 200px);
}
在浏览器中,<img>标签的transform属性被自动加上了各种前缀,说明autoprefixer生效:

注意:可以在Can I use这个网站中查询浏览各个版本的浏览器是否需要添加前缀
postcss-pxtorem(自动把px代为转换成rem)
安装:
npm install postcss-pxtorem -D
然后在postcss.config.js中添加配置:
module.exports = {
plugins: {
"autoprefixer": {
"overrideBrowserslist": [
"ie >= 8", // 兼容IE7以上浏览器
"Firefox >= 3.5", // 兼容火狐版本号大于3.5浏览器
"chrome >=35", // 兼容谷歌版本号大于35浏览器
"opera >= 11.5", // 兼容欧朋版本号大于11.5浏览器
]
},
"postcss-pxtorem": {
rootValue: 100, // 根元素字体大小
// propList: ['*'], // 可以从px更改到rem的属性,*代表所有属性
propList: ["height"],
}
}
}
然后我们在index.less中将图片的高度设置成200px:
// index.less
body{
img{
height: 200px;
border: 1px solid #4f47ff;
}
}
此时webpack.config.js的配置:
module: {
rules: [
{
test: /\.less$/,
use: [
{
loader: "style-loader"
},
{
loader: "css-loader"
},
{
loader: "less-loader"
},
{
loader: "postcss-loader"
}
]
}
]
}
打包后可以看到,我们在index.less中写的height: 200px被转成了rem:

12.打包字体图标iconfont
我们经常都会使用到iconfont,如图是font目录,里面除了有css文件,还有eot/js/json/svg/ttf/woff/woff2,除了js文件,其它都是webpack默认不能处理的格式,这里就需要用到file-loader来处理:

webpack.config.js中的配置:
module: {
/*
rules:rules表示规则,rules数组下的一个对象就代表一个规则
*/
rules: [
{
test: /\.(eot|json|svg|woff|woff2|ttf)$/,
use: [
{
loader: 'file-loader',
options: {
// esModule: false,
// 指定打包后文件名称
name: '[name].[ext]',
// 指定打包后文件存放目录
outputPath: 'font/',
},
}
]
},
]
}
在index.js中去使用iconfont
// index.js
// 引入iconfont的css文件
import './font/iconfont.css'
// 使用icon
document.body.innerHTML = "<i class='iconfont icon-2_shanchu' style='font-size: 50px'>"
打包后来看最终效果:

13.html-webpack-plugin
从这里开始我们会接触到webpack的插件(plugin),用于扩展webpack的功能;前面讲到的loader其实也是变相的扩展了webpack,但是它只专注于转化文件这一个领域,而plugin的功能更加丰富,不仅仅局限于资源的加载。
html-webpack-plugin的作用是什么呢?我们先来看一个现象:每次我们打包文件后,在bundle文件中的目录结构是这样的:

我们要手动在bundle文件夹中创建一个html,然后将bundle.js引入到html中。这个操作太麻烦,我们希望让webpack来帮我们做这件事情:
要使用html-webpack-plugin插件,首先需要安装:
npm install --save-dev html-webpack-plugin
在webpack.config.js中修改配置:
// 引入HtmlWebpackPlugin插件
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
// 前面的配置省略
/*
告诉webpack需要新增一些什么样的功能
*/
plugins: [new HtmlWebpackPlugin()],
}
删除掉原来的bundle文件夹,重新打包之后,这时看bundle目录中已经自动多了一个html文件,里面也自动引入了打包后的bundle.js:


html-webpack-plugin在每次打包的时候,都为我们新增了一个html模板,并且将打包后的js文件引入其中;那现在又有一个新需求:如何让它使用我们自己的html模板,而不是重新去生成一个新的?
先来新建一个我们自己的html模板:
// index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div>我是自己创建的html</div>
</body>
</html>
接着在webpack.config.js中配置:
// 引入HtmlWebpackPlugin插件
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
// 前面的配置省略
/*
告诉webpack需要新增一些什么样的功能
*/
plugins: [new HtmlWebpackPlugin({
template: "./index.html", // 配置模板
minify:{
collapseWhitespace: false, // 是否要压缩html文件
}
})],
}
然后开始打包,打包后可以看到,bundle文件中不仅自动多了一个index.html,这个html还是我们自己创建的模板,如果想要压缩这个html文件,设置collapseWhitespace属性为true就可以了,这里为了看到效果我就不压缩了:

14.clean-webpack-plugin
了解clean-webpack-plugin之前,我们先来看一个现象,在index.js中使用一张图片,然后打包:
// index.js
import img1 from './img/wechatheader.jpg'
// 引用样式
import './index.css'
let oImg = document.createElement("img")
oImg.src = img1
oImg.setAttribute("class", 'border')
document.body.appendChild(oImg)
打包后在bundle/images下就多了一张图片:

然后我们修改一下index.js的代码,使用另外一张图片:
// index.js
import img2 from './img/c.jpg'
// 引用样式
import './index.css'
let oImg = document.createElement("img")
oImg.src = img2
oImg.setAttribute("class", 'border')
document.body.appendChild(oImg)
然后再来打包,这时第二张图片也被打包到了bundle/images目录中:

现在并没有用到wechatheader.jpg这张图片,但它还是会存在于images中,我们在开发中可能会经常修改图片资源的使用,原来用到但是现在没有用到的图片,不手动删除的话,会一直存在于打包文件中,长期下去就会导致打包文件体积越来越大,使项目难以维护;因此,使用clean-webpack-plugin可以先清空目录,再打包。
clean-webpack-plugin不属于官方插件,所以在webpack官方中文文档中是找不到的,想要使用这个插件,首先也需要安装:
npm install --save-dev clean-webpack-plugin
在webpack.config.js中添加配置:
// 引入HtmlWebpackPlugin插件
const HtmlWebpackPlugin = require('html-webpack-plugin')
// 引入CleanWebpackPlugin插件
const { CleanWebpackPlugin } = require('clean-webpack-plugin')
module.exports = {
// 前面的配置省略
/*
告诉webpack需要新增一些什么样的功能
*/
plugins: [
// 使用HtmlWebpackPlugin
new HtmlWebpackPlugin({
template: "./index.html",
}),
// 使用CleanWebpackPlugin
new CleanWebpackPlugin(),
],
}
打包之后,可以看到bundle/images目录下面只有一张图片了,没有用到的已经被自动清理掉了:

15.copy-webpack-plugin
在打包项目的时候除了JS/CSS/图片/字体图标等文件需要打包以外,可能还有一些相关的文档也需要打包,文档的内容是固定不变的,我们只需要将对应的文件拷贝到打包目录中即可,这个时候就可以使用copy-webpack-plugin来实现文件的拷贝
安装copy-webpack-plugin:
npm install --save-dev copy-webpack-plugin
配置webpack.config.js:
// 引入CopyWebpackPlugin插件
const CopyWebpackPlugin = require('copy-webpack-plugin')
module.exports = {
// 前面的配置省略
/*
告诉webpack需要新增一些什么样的功能
*/
plugins: [
// 使用HtmlWebpackPlugin
new HtmlWebpackPlugin({
template: "./index.html",
}),
// 使用CleanWebpackPlugin
new CleanWebpackPlugin(),
new CopyWebpackPlugin([
{from: "./doc", to: "doc"} // from表示文档原来的路径,to表示需要拷贝到bundle下的路径
])
],
}
打包后再来看文件结构,doc被拷贝到了bundle文件夹下了:

16.mini-css-extract-plugin
mini-css-extract-plugin是一个专门用于将打包的css内容提取到单独文件的插件,前面我们通过style-loader打包的css都是直接插入到<head>中的
安装:
npm install --save-dev mini-css-extract-plugin
配置webpack.config.js,注意这里要替换掉原来的style-loader:
// 引入MiniCssExtractPlugin插件
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
module.exports = {
// 前面的内容省略
module: {
rules: [
{
test: /\.css$/,
use: [
{
// loader: "style-loader", // 原来是将css插入<head>
loader: MiniCssExtractPlugin.loader, // 使用MiniCssExtractPlugin的loader
},
{
loader: "css-loader",
},
]
},
]
},
/*
告诉webpack需要新增一些什么样的功能
*/
plugins: [
// 前面的内容省略
new MiniCssExtractPlugin({
filename: 'css/[name].css',
}),
],
}
打包之后,可以看到目录中多了css文件,并且css也被自动引入到了html之中:

如何压缩css?
前面我们说配置mode属性为production,会对打包的js代码进行压缩;那如果我们想要压缩打包后的css代码呢?
这里需要用到optimize-css-assets-webpack-plugin插件,首先来安装:
npm install --save -d optimize-css-assets-webpack-plugin
配置webpack.config.js,新增一个optimization属性(优化项):
// 引入MiniCssExtractPlugin插件
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
// 引入OptimizeCSSAssetsPlugin插件
const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin')
// 引入TerserJSPlugin插件
const TerserJSPlugin = require('terser-webpack-plugin')
module.exports = {
/*
optimization: 配置webpack的优化项
*/
optimization: {
minimizer: [new OptimizeCSSAssetsPlugin({})],
},
mode: "production", // 配置为生产模式
}
打包后再来看,css代码已经全部被压缩了:

但是文档中有这样一句话:
To minify the output, use a plugin like optimize-css-assets-webpack-plugin. Setting optimization.minimizer overrides the defaults provided by webpack, so make sure to also specify a JS minimizer:
大致意思是:如果想要压缩CSS,可以使用optimize-css-assets-webpack-plugin插件,但是会覆盖掉默认的webpack的配置,会覆盖掉默认的JS的压缩,我们可以看一下此时设置mode为production打包后的JS文件,确实没有被压缩:

我们需要额外的安装js代码压缩的插件:
npm install --save -d terser-webpack-plugin
修改webpack.config.js配置文件:
// 引入MiniCssExtractPlugin插件
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
// 引入OptimizeCSSAssetsPlugin插件
const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin')
// 引入TerserJSPlugin插件
const TerserJSPlugin = require('terser-webpack-plugin')
module.exports = {
/*
optimization: 配置webpack的优化项
*/
optimization: {
minimizer: [new TerserJSPlugin({}), new OptimizeCSSAssetsPlugin({})],
},
mode: "production", // 配置为生产模式
}
打包后再来看,css和js都被压缩了:

17.watch
之前我们打包文件之后,如果修改了代码,需要重新手动打包;启用Watch模式,可以让webpack监听打包文件的更改,当它们修改后会重新编译打包:
配置webpack.config.js:
module.exports = {
watch: true,
watchOptions: {
aggregateTimeout: 300, // 防抖,修改完成之后300ms再编译
poll: 1000, // 每隔1000ms检查一次变动
ignored: ['node_modules'], // 排除一些巨大的文件夹,不需要监控的文件夹,例如node_modules
},
}
18.webpack-dev-server
webpack-dev-server和watch一样可以监听文件变化,但区别是webpack-dev-server可以将打包好的程序运行在一个服务器环境下,还可以解决企业开发中开发阶段跨域的问题。
要使用webpack-dev-server,第一步需要安装:
npm install webpack-dev-server --save-dev
配置webpack.config.js,由于和watch功能相似,所以二者只能保留一个:
module.exports = {
//...
devServer: {
contentBase: "./bundle",
open: true, // 编译打包完以后是否会自动在浏览器打开网页
port: 9000 // 端口号
},
}
此时打包的命令也发生变化:
// 以前
npx webpack
// 现在
npx webpack-dev-server
通过npx webpack-dev-server命令打包完成后,网页自动打开,并且端口号被改成了9000:

webpack-dev-server解决开发阶段的跨域问题
module.exports = {
//...
devServer: {
// 第一种写法
proxy: {
/*
当我们在代码中发送请求到/api和/login的时候,
devServer就会自动将我们请求的地址替换为http://localhost:3000
*/
"/api": {
target: "http://localhost:3000",
changeOrigin: true, // 当target为域名的时候,要开启域名跨域
secure: false, // https跨域
pathRewrite: {'/api' : ''} // 路径重写,将路径中的api替换为空
},
"/login": {
target: "http://localhost:3000",
changeOrigin: true, // 当target为域名的时候,要开启域名跨域
secure: false, // https跨域
pathRewrite: {'/api' : ''} // 路径重写,将路径中的api替换为空
}
}
/***************************分割线*****************************/
// 第二种写法
proxy: [{
context: ['/api', '/login'],
target: "http://localhost:3000",
changeOrigin: true, // 当target为域名的时候,要开启域名跨域
secure: false, // https跨域
pathRewrite: {'/api' : ''} // 路径重写,将路径中的api替换为空
}]
}
};
target:请求的地址;
changeOrigin:如果请求的地址是个域名(www.mi.com),就要开启域名跨域;
secure:如果是https协议(https://localhost:3000),需要开启https跨域;
pathRewrite:重写路径,例如{'/api' : ''},请求到 http://localhost:3000/api/xxx 现在会被代理到请求 http://localhost:3000/xxx
注意:webpack-dev-server只能解决开发阶段的跨域问题,并不能解决项目上线之后的跨域问题,因为项目上线之后是将打包好的文件上传到服务器,而打包好的文件中并没有webpack,更不会有webpack-dev-server,想要解决跨域问题,还是要依赖服务端。
19.热模块替换(热更新)插件
什么是热模块替换?(hot module replacement)
细心的读者可能会发现,在上一节中通过webpack-dev-server自动打包的文件并没有真正放到指定的目录中,因为读写磁盘是非常耗时耗性能的,为了提升性能,webpack-dev-server将打包好的内容直接放到了内存中(读写内存的速度比读写磁盘的速度快很多)。
通过webpack-dev-server可以实现实时监听打包内容的变化,每次打包之后都会自动刷新网页,但正是因为每当内容被修改时都会自动刷新网页,在开发阶段可能会带来不便,这时就需要需要通过热模块替换(HMR)插件来优化调试开发,使用热更新插件会在内容发生变化的时候更新修改的内容,但是不会重新刷新网页。
HMR的使用
配置webpack.config.js:
// 引入webpack
const webpack = require('webpack')
module.exports = {
devServer: {
hot: true, // 开启热更新,开启以后就不会自动刷新网页
hotOnly: true, // 哪怕不支持热更新也不要刷新网页
},
module: {
rules: [
{
test: /\.css$/,
use: [
{
// loader: "style-loader", // 原来是将css插入<head>
loader: MiniCssExtractPlugin.loader,
options: {
//如果这里使用MiniCssExtractPlugin.loader,需要设置hmr: true,才能支持css代码的热更新
hmr: true,
}
},
{
loader: "css-loader",
options: {
// 开启css的模块化
// modules: true,
}
},
{
loader: "postcss-loader"
}
]
},
]
},
plugins: [new webpack.HotModuleReplacementPlugin()] // 使用热更新插件
}
注意
对于css模块而言,在css-loader中已经帮我们实现了热更新,只要css代码被修改就会立即更新;但是对于js模块而言,默认并没有实现热更新,所以修改了js模块代码并不会立即更新
如何实现js模块的热更新呢
在index.js中引入一个custom.js模块,当我们修改了custom.js代码的时候,会执行module.hot.accept()中的回调去更新内容:
// index.js
import img2 from '../img/c.jpg'
// 引用样式
import '../css/index.css'
// 引用js模块
import {addSpan} from './custom.js'
let oImg = document.createElement("img")
oImg.src = img2
oImg.setAttribute("class", 'border')
document.body.appendChild(oImg)
addSpan()
// 判断当前有没有开启热更新
if (module.hot) {
// 告诉热更新需要监听哪一个js模块发生变化
module.hot.accept('./custom.js', function () {
// 删掉原来的span
let oSpan = document.querySelector('span')
document.removeChild(oSpan)
// 重新添加span
addSpan()
})
}
// custom.js
function addSpan() {
let oSpan = document.createElement("span")
oSpan.innerText = '我是新增的span 的内容'
document.body.appendChild(oSpan)
}
export {addSpan}
20.babel
在企业开发中为了兼容一些低级版本的浏览器,我们需要将ES6、ES7、ES8高级语法转换为ES5低级语法,否则在低版本浏览器中我们的程序无法正确执行,默认情况下webpack是不会将我们的代码转成ES5低级语法的,需要使用babel来转换。
如何使用babel
安装babel-loader及其核心库:
npm install -D babel-loader @babel/core @babel/preset-env webpack
配置webpack.config.js,我们可以通过配置presets的方式来告诉webpack我们需要兼容哪些浏览器,然后babel就会根据我们的配置自动调整转换方案,如果需要兼容的浏览器已经能够实现,就不转换了:
module.exports = {
module: {
rule: [
{
test: /\.js$/,
exclude: /node_modules/, // 不处理哪一个文件夹
use: [
{
loader: 'babel-loader',
options: {
"presets": [['@babel/preset-env', {
// 配置哪些版本的浏览器需要转换
"targets": {
"chrome": "58",
"ie": "11"
}
}]]
}
}
]
}
]
}
}
如何利用babel实现低版本语法
对于ES6、ES7、ES8语法有对应关系的ES5语法而言,babel现在可以帮我们实现自动转换,但是对于没有对应关系的语法而言,按照现在的配置还不能实现自动转换,例如Promise,includes等方法就是新增的语法,ES5中没有对应关系,这时候就需要用到polyfill来让babel实现对应语法:
安装不存在代码的实现包:
npm install --save @babel/polyfill
在对应的js模块中引入:
// index.js
import "@babel/polyfill"
// *****
但是这样导入有个坏处,无论我们有没有用到不存在的语法都会打包到文件中,会增加了打包后文件的大小,我们希望按需加载,所以要在webpack.config.js中配置useBuiltIns属性:
module.exports = {
module: {
rule: [
{
test: /\.js$/,
exclude: /node_modules/, // 不处理哪一个文件夹
use: [
{
loader: 'babel-loader',
options: {
"presets": [['@babel/preset-env', {
// 配置哪些版本的浏览器需要转换
"targets": {
"chrome": "58",
"ie": "11"
},
useBuiltIns: 'usage', // 按需加载
}]]
}
}
]
}
]
}
}
使用babel的注意点
先来打包一下,这时控制台有一个提示:

意思是如果配置了useBuiltIns: 'usage',就可以移除入口文件中的import "@babel/polyfill"。
直接导入polyfill的方式只适用于一般项目的开发,但是如果在编写第三方模块的时候会有一些问题,因为这种方式是通过全局变量的方式来注入代码,会污染全局环境,所以我们再来看一下polyfill的第二种配置方式:
安装相关模块transform-runtime:
npm install --save-dev @babel/plugin-transform-runtime
npm install --save @babel/runtime
配置webpack.config.js:
module.exports = {
module: {
rule: [
{
test: /\.js$/,
exclude: /node_modules/, // 不处理哪一个文件夹
use: [
{
loader: 'babel-loader',
options: {
"presets": [['@babel/preset-env', {
// 配置哪些版本的浏览器需要转换
"targets": {
"chrome": "58",
"ie": "11"
},
useBuiltIns: 'usage', // 按需加载
}]],
"plugins": [
[
"@babel/plugin-transform-runtime",
{
"absoluteRuntime": false,
"corejs": 2, // 设置为2,不污染全局环境
"helpers": true,
"regenerator": true,
"useESModules": false,
"version": "7.0.0-beta.0"
}
]
]
}
}
]
}
]
}
}
但是这时候打包会报错,因为corejs属性设置为2,表示不污染全局环境,文档中可以看到,corejs配置为2或者3的时候,还要再单独安装包:

安装,然后再打包就没问题啦:
npm install --save @babel/runtime-corejs2
21.html-withimg-loader
此前我们通过file-loader或url-loader已经可以将JS或者CSS中的图片打包到指定目录中了,但是并不能将HTML中引用的图片打包到指定目录中,所以我们需要html-withimg-loader来实现这一功能
安装html-withimg-loader:
npm install html-withimg-loader --save
配置webpack.config.js:
module.exports = {
module: {
rule: [
{
test: /\.(htm|html)$/i,
loader: 'html-withimg-loader'
},
]
}
22.图片的压缩和合并
在企业开发中,为了提升网页的访问速度,我们除了会压缩JS/CSS/HTML以外,还会对网页上的图片进行压缩和合并,压缩可以减少网页的体积,合并可以减少请求次数。
压缩打包之后的图片
每次在打包图片之前,我们可以通过配置webpack对打包的图片进行压缩,以减少打包之后的体积
利用image-webpack-loader压缩图片,安装(如果安装后打包遇到image-webpack-loader报错,尝试使用cnpm):
npm install image-webpack-loader --save-dev
配置webpack.config.js:
module.exports = {
module: {
rules: [
// 打包jpg|png|gif的规则
{
test: /\.(jpg|png|gif)$/,
use: [
{
loader: 'file-loader',
options: {
esModule: false,
// 指定打包后文件名称
name: '[name].[ext]',
// 指定打包后文件存放目录
outputPath: 'images/',
},
},
{
loader: 'image-webpack-loader',
options: {
// 代表不同类型文件的压缩规则
mozjpeg: {
progressive: true,
quality: 65, // 压缩的质量,100表示不压缩,值越小压缩得越多
},
// optipng.enabled: false will disable optipng
optipng: {
enabled: false,
},
pngquant: {
quality: [0.65, 0.90],
speed: 4
},
gifsicle: {
interlaced: false,
},
// the webp option will enable WEBP
webp: {
quality: 75
}
}
},
]
},
]
}
}
压缩之前的图片大小:

压缩之后的图片大小:

合并打包的图片
过去为了减少网页请求的次数,我们需要UI设计师提供精灵图,并且在使用的时候还需要手动的去设置没张图片的位置,但是webpack可以让我们自己合成精灵图,并且会自动设置好图片的位置
利用postcss-sprites来合并图片,先来安装postcss-sprites和postcss:
npm install --save -d postcss-sprites postcss
在前面讲postcss-loader的时候我们在根目录创建一个postcss.config.js配置文件,所以和postcss相关的插件我们在postcss.config.js配置文件中配置:
module.exports = {
plugins: {
"autoprefixer": {
"overrideBrowserslist": [
"ie >= 8", // 兼容IE7以上浏览器
"Firefox >= 3.5", // 兼容火狐版本号大于3.5浏览器
"chrome >=35", // 兼容谷歌版本号大于35浏览器
"opera >= 11.5", // 兼容欧朋版本号大于11.5浏览器
]
},
"postcss-pxtorem": {
rootValue: 100, // 根元素字体大小
// propList: ['*'], // 可以从px更改到rem的属性
propList: ["height"],
},
"postcss-sprites": {
// 告诉webpack合并之后的图片保存到什么地方
spritePath: "./bundle/images"
},
}
}
现在我们编写一个简单的案例来测试一下:
// index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div class="img1"></div>
<div class="img2"></div>
</body>
</html>
// index.css
div{
height: 200px;
width: 200px;
}
.img1{
background: url("../img/wechatheader.jpg");
}
.img2{
background: url("../img/c.jpg");
}
// index.js
import '../css/index.css'
打包之后再来看,bundle/images下面被合并成了一张图片,打包后的css代码也自动添加了位置:

但这时候打开html,发现什么没有,查看图片的路径我们会发现一些问题:

由于webpack打包之后给我们的都是相对路径,所以会导致在html中使用的图片能够正常运行,在css中的图片不能正常运行,例如:打包之后的路径是images/c.jpg,那么在html中会去html文件所在的路径下找images,能找到所以不会有问题;但是在css中,会去css文件下找images,找不到所以图片加载不出来,这就是为什么这里的路径变成了css/images的原因。
要想解决这个问题,我们需要找到file-loader的配置,在下面添加一个属性publicPath:
在开发阶段,将publicPath设置为dev-server服务器地址
在上线阶段,将publicPath设置为线上服务器地址
{
loader: 'file-loader',
options: {
esModule: false,
// 指定打包后文件名称
name: '[name].[ext]',
// 指定打包后文件存放目录
outputPath: 'images/',
publicPath: 'http://127.0.0.1:9000/images'
},
},
重新打包以后就可以看到图片了:

新的问题又来了,所有的图片都会被合并成一张图片,如果我想合并成两张图片,三张图片又该怎么做呢?可以通过groupBy属性来设置,让图片按照文件夹来合并:
先将图片按照文件夹分类:

配置groupBy属性,这个属性必须要返回一个Promise对象:
module.exports = {
plugins: {
"autoprefixer": {
"overrideBrowserslist": [
"ie >= 8", // 兼容IE7以上浏览器
"Firefox >= 3.5", // 兼容火狐版本号大于3.5浏览器
"chrome >=35", // 兼容谷歌版本号大于35浏览器
"opera >= 11.5", // 兼容欧朋版本号大于11.5浏览器
]
},
"postcss-pxtorem": {
rootValue: 100, // 根元素字体大小
// propList: ['*'], // 可以从px更改到rem的属性
propList: ["height"],
},
"postcss-sprites": {
// 告诉webpack合并之后的图片保存到什么地方
spritePath: "./bundle/images",
// 告诉webpack合并图片的时候如何分组
groupBy: function (image) {
let path = image.url.substr(0, image.url.lastIndexOf('/'))
let name = path.substr(path.lastIndexOf('/') + 1)
return Promise.resolve(name)
}
},
}
}
打包后再来看bundle/images目录,生成了两张精灵图:

还有一个filterBy属性需要了解,这个属性是用来做过滤的,用来设置哪些图片需要合并,哪些图片不需要合并:
module.exports = {
plugins: {
"autoprefixer": {
"overrideBrowserslist": [
"ie >= 8", // 兼容IE7以上浏览器
"Firefox >= 3.5", // 兼容火狐版本号大于3.5浏览器
"chrome >=35", // 兼容谷歌版本号大于35浏览器
"opera >= 11.5", // 兼容欧朋版本号大于11.5浏览器
]
},
"postcss-pxtorem": {
rootValue: 100, // 根元素字体大小
// propList: ['*'], // 可以从px更改到rem的属性
propList: ["height"],
},
"postcss-sprites": {
// 告诉webpack合并之后的图片保存到什么地方
spritePath: "./bundle/images",
// 告诉webpack合并图片的时候如何分组
groupBy: function (image) {
// image是一个对象,通过image.url拿到图片的文件夹名称
let path = image.url.substr(0, image.url.lastIndexOf('/'))
let name = path.substr(path.lastIndexOf('/') + 1)
return Promise.resolve(name)
},
// 告诉webpack哪些图片需要合并,哪些不需要合并
filterBy: function (image) {
let path = image.url
// 如果是png结尾的才合并
if (!/\.png$/.test(path)) {
return Promise.reject()
} else return Promise.resolve()
}
},
}
}
23.ESLint
ESLint是一个插件化的JavaScript代码检测工具,它可以用于检查常见的JavaScript代码错误,也可以进行代码规范的检查;在企业开发中项目负责人会定制一套ESLint规则,然后应用到所编写的项目上,从而实现辅助编码规范的执行,有效控制项目代码的质量。在编译打包时如果语法有错误或者不符合规范就会报错,并且提示相关错误信息。
如何使用ESLint
安装ESLint和对应eslint-loader:
npm install eslint --save-dev
npm install eslint-loader --save-dev
修改配置文件webpack.config.js,由于loader是从下往上执行的,所以检查编码规范的规则要写在最下面,让它最先执行(或者可以设置enforce属性为pre):
module.exports = {
module: {
rules: [
// 前面的配置省略......
// 检查编码规范的规则
{
enforce: 'pre', // 当前loader最先执行
test: /\.js$/,
exclude: /node_modules/, // 排除不需要检查的目录
include: path.resolve(__dirname, 'src'),
loader: 'eslint-loader',
options: {
// eslint options (if necessary)
},
},
]
}
}
配置webpack.config.js完成以后,我们还需要配置对应的规则,ESLint中文文档说还需要创建一个.eslintrc.*文件,所以我们根据提示,创建一个.eslintrc.js文件,配置对应的规则:
// .eslintrc.js
// https://eslint.org/docs/user-guide/configuring
module.exports = {
/*
不重要,永远写true
*/
root: true,
parserOptions: {
// parser: 'babel-eslint',
/*
默认设置为 3,5(默认), 你可以使用 6、7、8、9 或 10 来指定你想要使用的 ECMAScript 版本
*/
"ecmaVersion": 10,
/*
设置为 "script" (默认) 或 "module"(如果你的代码是 ECMAScript 模块)。
*/
"sourceType": "module",
/*
ecmaFeatures - 这是个对象,表示你想使用的额外的语言特性:
globalReturn - 允许在全局作用域下使用 return 语句
impliedStrict - 启用全局 strict mode (如果 ecmaVersion 是 5 或更高)
jsx - 启用 JSX
*/
"ecmaFeatures": {}
},
// 指定代码运行的宿主环境
env: {
browser: true, // 浏览器
node: true, // node
/*
支持 ES6 语法并不意味着同时支持新的 ES6 全局变量或类型(比如 Set 等新类型)。
对于 ES6 语法,使用 { "parserOptions": { "ecmaVersion": 6 } }
*/
es6: true,
},
extends: [
/*
引入standard代码规范
*/
// https://github.com/standard/standard/blob/master/docs/RULES-en.md
'standard'
],
/*
扩展或覆盖规则
*/
rules: {
// 强制语句结束添加,分号
semi: ["error", "always"],
// 强制缩进2个空格
indent: ["error", 4],
// 方法名和刮号之间不加空格
'space-before-function-paren': ['error', 'never'],
"no-unexpected-multiline": "off"
}
};
由于这里用到了standard标准,因此还需要安装:
npm install standard --save-dev
配置完成以后,此时我们打包,webpack就会先根据我们配置的规则检查语法,如果语法不正确就会报错,打包失败;除此之外,我们可以配合编码工具来使用eslint,当我们在编码阶段写出语法错误或者不符合eslint规则的代码时,编码工具就会提示错误,这里以webstorm为例,打开setting,找到eslint,按照图上这样设置,就可以开启webstorm的语法规则提示了:

24.配置文件完整代码
webpack.config.js:
const path = require('path'); // 这里需要用到node的path模块
const HtmlWebpackPlugin = require('html-webpack-plugin'); // 引入HtmlWebpackPlugin插件
const { CleanWebpackPlugin } = require('clean-webpack-plugin'); // 引入CleanWebpackPlugin插件
const CopyWebpackPlugin = require('copy-webpack-plugin'); // 引入CopyWebpackPlugin插件
const MiniCssExtractPlugin = require('mini-css-extract-plugin'); // 引入MiniCssExtractPlugin插件
const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin'); // 引入OptimizeCSSAssetsPlugin插件
const TerserJSPlugin = require('terser-webpack-plugin'); // 引入TerserJSPlugin插件
const webpack = require('webpack');
module.exports = {
/*
mode:指定打包的模式,模式有两种
开发模式(development):不会对打包的js代码进行压缩
生产模式(production):会对打包的js代码进行压缩
*/
mode: 'development', // "production" | "development"
/*
entry:指定需要打包的入口文件
*/
entry: './src/js/index.js',
/*
output:指定打包后文件输出的路径和输出文件的名称
*/
output: {
/*
filename: 指定打包之后的JS文件的名称
*/
filename: 'js/bundle.js',
/*
path: 指定打包之后的文件存储到什么地方
__dirname代表当前这个js文件所在目录的绝对路径
*/
path: path.resolve(__dirname, 'bundle')
},
/*
watch和devServer功能相似,一般只用devServer就够了
*/
// watch: true,
// watchOptions: {
// aggregateTimeout: 300, // 防抖,修改完成之后300ms再编译
// poll: 1000, // 每隔1000ms检查一次变动
// ignored: ['node_modules'], // 排除一些巨大的文件夹,不需要监控的文件夹,例如node_modules
// },
devServer: {
contentBase: './bundle',
open: true, // 编译打包完以后是否会自动在浏览器打开网页
port: 9000, // 端口号
hot: true, // 开启热更新,开启以后就不会自动刷新网页
hotOnly: true // 哪怕不支持热更新也不要刷新网页
},
/*
optimization: 配置webpack的优化项
*/
optimization: {
/*
OptimizeCSSAssetsPlugin:压缩css
TerserJSPlugin:压缩js
*/
minimizer: [new TerserJSPlugin({}), new OptimizeCSSAssetsPlugin({})]
},
/*
development:cheap-module-eval-source-map,只需要行错误信息, 并且包含第三方模块错误信息, 并且不会生成单独sourcemap文件
production: cheap-module-source-map,只需要行错误信息, 并且包含第三方模块错误信息, 并且会生成单独sourcemap文件
*/
devtool: 'cheap-module-eval-source-map',
/*
module:告诉webpack如何处理webpack不能够识别的文件
*/
module: {
/*
rules:rules表示规则,rules数组下的一个对象就代表一个规则
*/
rules: [
// 打包jpg|png|gif的规则
{
test: /\.(jpg|png|gif)$/,
use: [
{
loader: 'file-loader',
options: {
// 新版本默认使用了esModule语法
esModule: false,
// 指定打包后文件名称
name: '[name].[ext]',
// 指定打包后文件存放目录
outputPath: 'images/',
// 指定托管服务器地址(统一替换图片地址)
publicPath: 'http://127.0.0.1:9000/images'
}
},
// 图片的压缩
{
loader: 'image-webpack-loader',
options: {
mozjpeg: {
progressive: true,
quality: 65
},
optipng: {
enabled: false
},
pngquant: {
quality: [0.65, 0.90],
speed: 4
},
gifsicle: {
interlaced: false
},
webp: {
quality: 75
}
}
}
]
},
// 处理css的规则
{
test: /\.css$/,
use: [
{
// loader: "style-loader", // 原来是将css插入<head>
loader: MiniCssExtractPlugin.loader, // 如果配置了MiniCssExtractPlugin插件,就要使用MiniCssExtractPlugin.loader
options: {
hmr: true // 开启css的热更新
}
},
{
loader: 'css-loader',
options: {
// 开启css的模块化
modules: true
}
},
{
loader: 'postcss-loader'
}
]
},
// 处理less的规则
{
test: /\.less$/,
use: [
{
loader: 'style-loader' // 将 JS 字符串生成为 style 节点
},
{
loader: 'css-loader' // 将 CSS 转化成 CommonJS 模块
},
{
loader: 'less-loader' // 将 less 编译成 CSS
}
]
},
// 处理sass的规则
{
test: /\.scss$/,
use: [
{
loader: 'style-loader' // 将 JS 字符串生成为 style 节点
},
{
loader: 'css-loader' // 将 CSS 转化成 CommonJS 模块
},
{
loader: 'sass-loader' // 将 sass 编译成 CSS
}
]
},
// 处理html
{
test: /\.(htm|html)$/i,
loader: 'html-withimg-loader' // 将html中的图片打包到指定目录
},
// 打包字体图标的规则
{
test: /\.(eot|json|svg|woff|woff2|ttf)$/,
use: [
{
loader: 'file-loader',
options: {
// 指定打包后文件名称
name: '[name].[ext]',
// 指定打包后文件存放目录
outputPath: 'font/'
}
}
]
},
// 处理js文件
{
test: /\.js$/,
exclude: /node_modules/, // 不处理哪一个文件夹
use: [
{
loader: 'babel-loader',
options: {
presets: [['@babel/preset-env', {
// 配置哪些版本的浏览器需要转化
targets: {
chrome: '58',
ie: '11'
},
useBuiltIns: 'usage'
}]],
plugins: [
[
'@babel/plugin-transform-runtime',
{
absoluteRuntime: false,
corejs: 2, // 设置为2,不污染全局变量
helpers: true,
regenerator: true,
useESModules: false,
version: '7.0.0-beta.0'
}
]
]
}
}
]
},
// 检查编码规范的规则
{
enforce: 'pre', // 当前loader最先执行
test: /\.js$/,
exclude: /node_modules/, // 排除不需要检查的目录
include: path.resolve(__dirname, 'src'),
loader: 'eslint-loader',
options: {
// eslint options (if necessary)
fix: true
}
}
]
},
/*
告诉webpack需要新增一些什么样的功能
*/
plugins: [
// 自动生成html
new HtmlWebpackPlugin({
template: './src/index.html'
}),
// 清除打包后没有使用的文件
new CleanWebpackPlugin(),
// 实现自动拷贝
new CopyWebpackPlugin([
{ from: './doc', to: 'doc' } // from表示文档原来的路径,to表示需要拷贝到bundle下的路径
]),
// css文件的单独生成、压缩等功能
new MiniCssExtractPlugin({
filename: 'css/[name].css'
}),
// 热更新
new webpack.HotModuleReplacementPlugin()
]
};
postcss.config.js:
module.exports = {
plugins: {
// 自动添加浏览器前缀
autoprefixer: {
overrideBrowserslist: [
'ie >= 8', // 兼容IE7以上浏览器
'Firefox >= 3.5', // 兼容火狐版本号大于3.5浏览器
'chrome >=35', // 兼容谷歌版本号大于35浏览器
'opera >= 11.5' // 兼容欧朋版本号大于11.5浏览器
]
},
// px转换成rem
'postcss-pxtorem': {
rootValue: 100, // 根元素字体大小
// propList: ['*'], // 可以从px更改到rem的属性,*表示全部
propList: ['height']
},
'postcss-sprites': {
// 告诉webpack合并之后的图片保存到什么地方
spritePath: './bundle/images',
// 告诉webpack合并图片的时候如何分组
groupBy: function(image) {
const path = image.url.substr(0, image.url.lastIndexOf('/'));
const name = path.substr(path.lastIndexOf('/') + 1);
return Promise.resolve(name);
},
// 告诉webpack合并图片的时候如何分组
filterBy: function(image) {
const path = image.url;
// 如果是png结尾的才合并
if (!/\.png$/.test(path)) {
return Promise.reject();
} else return Promise.resolve();
}
}
}
};
.eslintrc.js:
module.exports = {
/*
不重要,永远写true
*/
root: true,
parserOptions: {
// parser: 'babel-eslint',
/*
默认设置为 3,5(默认), 你可以使用 6、7、8、9 或 10 来指定你想要使用的 ECMAScript 版本
*/
"ecmaVersion": 10,
/*
设置为 "script" (默认) 或 "module"(如果你的代码是 ECMAScript 模块)。
*/
"sourceType": "module",
/*
ecmaFeatures - 这是个对象,表示你想使用的额外的语言特性:
globalReturn - 允许在全局作用域下使用 return 语句
impliedStrict - 启用全局 strict mode (如果 ecmaVersion 是 5 或更高)
jsx - 启用 JSX
*/
"ecmaFeatures": {}
},
// 指定代码运行的宿主环境
env: {
browser: true, // 浏览器
node: true, // node
/*
支持 ES6 语法并不意味着同时支持新的 ES6 全局变量或类型(比如 Set 等新类型)。
对于 ES6 语法,使用 { "parserOptions": { "ecmaVersion": 6 } }
*/
es6: true,
},
extends: [
/*
引入standard代码规范
*/
// https://github.com/standard/standard/blob/master/docs/RULES-en.md
'standard'
],
/*
扩展或覆盖规则
*/
rules: {
// 强制语句结束添加,分号
semi: ["error", "always"],
// 强制缩进2个空格
indent: ["error", 4],
// 方法名和刮号之间不加空格
'space-before-function-paren': ['error', 'never'],
"no-unexpected-multiline": "off"
}
}
小技巧
使用WebStorm的Macros宏指令,实现保存的同时格式化代码,并跳至行尾(简单来说就是每次ctrl+s的时候会保存代码然后自动修复eslint然后光标跳至行尾)
(1).Edit>Macros>Start Macros Recording

(2).开启录制以后会有如下的状态
按下Ctrl+s(保存)然后按下Ctrl+Alt+L(格式化),然后按下end(跳转行尾)然后点击右下角录制按钮结束录制,输入你想要保存的名字。


(3).进入设置页面添加快捷键
按下Ctrl+Alt+S或者File>Settings,在搜索框输入你刚才保存的名字,然后添加快捷键。如果提示警告该快捷键已存在,不用管直接移除,然后apply>OK.
如果这篇文章对你有帮助,请点个赞再走吧