上一篇我们分享了webpack 的 loader 机制,现在分享另外一个重要的核心特性:插件机制。
Webpack 插件机制的目的是为了增强 Webpack 在项目自动化构建方面的能力。通过上一篇的介绍我们应该知道,Loader 就是负责完成项目中各种各样资源模块的加载,从而实现整体项目的模块化,而 Plugin 则是用来解决项目中除了资源模块打包以外的其他自动化工作,所以说 Plugin 的能力范围更广,用途自然也就更多。
几个插件的常用应用场景
- 实现自动在打包之前清除 dist 目录(上次的打包结果)
- 自动生成应用所需要的 HTML 文件
- 根据不同环境为代码注入类似 API 地址这种可能变化的部分
- 拷贝不需要参与打包的资源文件到输出目录
- 压缩 Webpack 打包完成后输出的文件
- 自动发布打包结果到服务器实现自动部署
总之,有了 Plugin 的 Webpack 几乎“无所不能”。借助插件,我们就可以轻松实现前端工程化中绝大多数经常用到的功能,这也正是很多初学者会认为 “Webpack 就是前端工程化,或者前端工程化就是 Webpack” 的原因。
体验插件机制
自动清除输出目录的插件
通过之前的尝试,可能已经发现,Webpack 每次打包的结果都是直接覆盖到 dist 目录。而在打包之前,dist 目录中就可能已经存入了一些在上一次打包操作时遗留的文件,当我们再次打包时,只能覆盖掉同名文件,而那些已经移除的资源文件就会一直累积在里面,最终导致部署上线时出现多余文件,这显然非常不合理。
更为合理的做法就是在每次完整打包之前,自动清理 dist 目录,这样每次打包过后,dist 目录中就只会存在那些必要的文件。
clean-webpack-plugin 这个插件就很好的实现了这一需求。它是一个第三方的 npm 包,我们需要先通过 npm 安装一下,具体操作如下:
cnpm install clean-webpack-plugin --save-dev
安装过后,我们回到 Webpack 的配置文件中,然后导入 clean-webpack-plugin 插件,这个插件模块导出了一个叫作 CleanWebpackPlugin 的成员,我们先把它解构出来,具体代码如下。
const { CleanWebpackPlugin } = require('clean-webpack-plugin')
回到配置对象中,添加一个 plugins 属性,这个属性就是专门用来配置插件的地方,它是一个数组,添加一个插件就是在这个数组中添加一个元素。
绝大多数插件模块导出的都是一个类型,我们这里的 CleanWebpackPlugin 也不例外,使用它,就是通过这个类型创建一个实例,放入 plugins 数组中,具体代码如下:
const { CleanWebpackPlugin } = require('clean-webpack-plugin')module.exports = {...plugins: [new CleanWebpackPlugin()]}
完成以后我们来测试一下 clean-webpack-plugin 插件的效果。回到命令行终端,再次运行 Webpack 打包,此时之前的打包结果就不会存在了,dist 目录中存放的就都是我们本次打包的结果。
在这里,我只是希望通过这个非常简单的插件带你体验一下 Webpack 插件的使用。一般来说,当我们有了某个自动化的需求过后,可以先去找到一个合适的插件,然后安装这个插件,最后将它配置到 Webpack 配置对象的 plugins 数组中,这个过程唯一有可能不一样的地方就是,有的插件可能需要有一些配置参数。
用于生成 HTML 的插件
除了自动清理 dist 目录,我们还有一个非常常见的需求,就是自动生成使用打包结果的 HTML,所谓使用打包结果指的是在 HTML 中自动注入 Webpack 打包生成的 bundle。
在使用接下来这个插件之前,我们的 HTML 文件一般都是通过硬编码的方式,单独存放在项目根目录下的,这种方式有两个问题:
项目发布时,我们需要同时发布根目录下的 HTML 文件和 dist 目录中所有的打包结果,非常麻烦,而且上线过后还要确保 HTML 代码中的资源文件路径是正确的。
如果打包结果输出的目录或者文件名称发生变化,那 HTML 代码中所对应的 script 标签也需要我们手动修改路径。
解决这两个问题最好的办法就是让 Webpack 在打包的同时,自动生成对应的 HTML 文件,让 HTML 文件也参与到整个项目的构建过程。这样的话,在构建过程中,Webpack 就可以自动将打包的 bundle 文件引入到页面中。
相比于之前写死 HTML 文件的方式,自动生成 HTML 的优势在于:
- HTML 也输出到 dist 目录中了,上线时我们只需要把 dist 目录发布出去就可以了
- HTML 中的 script 标签是自动引入的,所以可以确保资源文件的路径是正常的
具体的实现方式就需要借助于 html-webpack-plugin 插件来实现,这个插件也是一个第三方的 npm 模块,我们这里同样需要单独安装这个模块,具体操作如下:
cnpm install html-webpack-plugin --save-dev
安装完成过后,回到配置文件,载入这个模块,不同于 clean-webpack-plugin,html-webpack-plugin 插件默认导出的就是插件类型,不需要再解构内部成员,具体如下:
const HtmlWebpackPlugin = require('html-webpack-plugin')
有了这个类型过后,回到配置对象的 plugins 属性中,同样需要添加一下这个类型的实例对象,完成这个插件的使用,具体配置代码如下:
const HtmlWebpackPlugin = require('html-webpack-plugin')const { CleanWebpackPlugin } = require('clean-webpack-plugin')module.exports = {...plugins: [new CleanWebpackPlugin(),new HtmlWebpackPlugin()]}
最后我们回到命令行终端,再次运行打包命令,此时打包过程中就会自动生成一个 index.html 文件到 dist 目录。我们找到这个文件,可以看到文件中的内容就是一段使用了 main.js 的空白 HTML,具体结果如下:
<!DOCTYPE html><html><head><meta charset="utf-8"><title>Webpack App</title><meta name="viewport" content="width=device-width, initial-scale=1"><script defer src="./main.js"></script></head><body></body></html>
上述的文件是通过 html-webpack-plugin 插件自动生成。至此,Webpack 就可以动态生成应用所需的 HTML 文件了,但是这里仍然存在一些需要改进的地方:
- 对于生成的 HTML 文件,页面 title 必须要修改
- 很多时候还需要我们自定义页面的一些 meta 标签和一些基础的 DOM 结构
也就是说,还需要我们能够充分自定义这个插件最终输出的 HTML 文件。
如果只是简单的自定义,我们可以通过修改 HtmlWebpackPlugin 的参数来实现。
我们回到 Webpack 的配置文件中,这里我们给 HtmlWebpackPlugin 构造函数传入一个对象参数,用于指定配置选项。其中,title 属性设置的是 HTML 的标题,我们把它设置为 Webpack Plugin Simple。meta 属性需要以对象的形式设置页面中的元数据标签,这里我们尝试为页面添加一个 viewport 设置,具体代码如下:
const path = require('path')
const { CleanWebpackPlugin } = require('clean-webpack-plugin')
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
entry: './src/index.js',
mode: 'none',
output: {
filename: 'main.js',
path: path.join(__dirname, 'dist'),
},
plugins: [
new CleanWebpackPlugin(),
new HtmlWebpackPlugin({
title: 'Webpack Plugin Sample',
meta: {
viewport: 'width=device-width',
},
}),
],
}
完成以后回到命令行终端,再次打包,然后我们再来看一下生成的 HTML 文件,此时这里的 title 和 meta 标签就会根据配置生成,具体结果如下:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Webpack Plugin Sample</title>
<meta name="viewport" content="width=device-width">
<script defer src="main.js"></script>
</head>
<body>
</body>
</html>
如果需要对 HTML 进行大量的自定义,更好的做法是在源代码中添加一个用于生成 HTML 的模板,然后让 html-webpack-plugin 插件根据这个模板去生成页面文件。
我们这里在 src 目录下新建一个 index.html 文件作为 HTML 文件的模板,然后根据我们的需要在这个文件中添加相应的元素。对于模板中动态的内容,可以使用 Lodash 模板语法输出,模板中可以通过htmlWebpackPlugin.options访问这个插件的配置数据,例如我们这里输出配置中的 title 属性,具体代码如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title><%= htmlWebpackPlugin.options.title %></title>
</head>
<body>
<div class="container">
<h1>页面上的基础结构</h1>
<div id="root"></div>
</div>
</body>
</html>
有了模板文件过后,回到配置文件中,我们通过 HtmlWebpackPlugin 的 template 属性指定所使用的模板,具体配置如下:
const path = require('path')
const { CleanWebpackPlugin } = require('clean-webpack-plugin')
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
entry: './src/index.js',
mode: 'none',
output: {
filename: 'main.js',
path: path.join(__dirname, 'dist'),
},
plugins: [
new CleanWebpackPlugin(),
new HtmlWebpackPlugin({
title: 'Webpack Plugin Sample',
template: './src/index.html'
}),
],
}
完成以后我们回到命令行终端,运行打包命令,然后再来看一下生成的 HTML 文件,此时 HTML 中就都是根据模板生成的内容了,具体结果如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Webpack Plugin Sample</title>
<script defer src="main.js"></script></head>
<body>
<div class="container">
<h1>页面上的基础结构</h1>
<div id="root"></div>
</div>
</body>
</html>
至此,我们应该了解了如何通过 html-webpack-plugin 自定义输出 HTML 文件内容。
关于 html-webpack-plugin 插件,除了自定义输出文件的内容,同时输出多个 HTML 文件也是一个非常常见的需求,除非我们的应用是一个单页应用程序,否则一定需要输出多个 HTML 文件。
如果需要同时输出多个 HTML 文件,其实也非常简单,我们回到配置文件中,这里通过 HtmlWebpackPlugin 创建的对象就是用于生成 index.html 的,那我们完全可以再创建一个新的实例对象,用于创建额外的 HTML 文件。
例如,这里我们再来添加一个 HtmlWebpackPlugin 实例用于创建一个 about.html 的页面文件,我们需要通过 filename 指定输出文件名,这个属性的默认值是 index.html,我们把它设置为 about.html,具体配置如下:
const path = require('path')
const { CleanWebpackPlugin } = require('clean-webpack-plugin')
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
entry: './src/index.js',
mode: 'none',
output: {
filename: 'main.js',
path: path.join(__dirname, 'dist'),
},
plugins: [
new CleanWebpackPlugin(),
new HtmlWebpackPlugin({
title: 'Webpack Plugin Sample',
template: './index.html'
}),
// 用于生成 about.html
new HtmlWebpackPlugin({
filename: 'about.html'
})
],
}
完成以后我们再次回到命令行终端,运行打包命令,然后我们展开 dist 目录,此时 dist 目录中就同时生成了 index.html 和 about.html 两个页面文件。
根据这个尝试我们就应该知道,如果需要创建多个页面,就需要在插件列表中加入多个 HtmlWebpackPlugin 的实例对象,让每个对象负责一个页面文件的生成。
当然了,对于同时输出多个 HTML,一般我们还会配合 Webpack 多入口打包的用法,这样就可以让不同的 HTML 使用不同的打包结果。不过关于多入口打包的用法不在本课时的讨论范畴内,我们后面再进行介绍。
用于复制文件的插件
在我们的项目中一般还有一些不需要参与构建的静态文件,那它们最终也需要发布到线上,例如网站的 favicon、robots.txt 等。
一般我们建议,把这类文件统一放在项目根目录下的 public 或者 static 目录中,我们希望 Webpack 在打包时一并将这个目录下所有的文件复制到输出目录。
对于这种需求,我们可以使用 copy-webpack-plugin 插件来帮我们实现。
cnpm install copy-webpack-plugin --save-dev
同理,我们需要先安装一下 copy-webpack-plugin 插件,安装完成过后,回到配置文件中,导入这个插件类型。然后同样在 plugins 属性中添加一个这个类型的实例,具体代码如下:
const path = require('path')
const { CleanWebpackPlugin } = require('clean-webpack-plugin')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const CopyWebpackPlugin = require('copy-webpack-plugin')
module.exports = {
...
plugins: [
...
new CopyWebpackPlugin({
patterns: [
{ from: "public", to: './public' },
],
}),
],
}
配置完成以后回到命令行终端,再次运行 Webpack,此时 public 目录下的文件就会同时拷贝到输出目录中。
至此,我们简单了解了几个非常常用的插件,这里的重点是,你不仅要学会使用这几个插件的使用,还要能够总结出大多数插件在使用上的共性。
