上一篇我们分享了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 安装一下,具体操作如下:

  1. cnpm install clean-webpack-plugin --save-dev

安装过后,我们回到 Webpack 的配置文件中,然后导入 clean-webpack-plugin 插件,这个插件模块导出了一个叫作 CleanWebpackPlugin 的成员,我们先把它解构出来,具体代码如下。

  1. const { CleanWebpackPlugin } = require('clean-webpack-plugin')

回到配置对象中,添加一个 plugins 属性,这个属性就是专门用来配置插件的地方,它是一个数组,添加一个插件就是在这个数组中添加一个元素。

绝大多数插件模块导出的都是一个类型,我们这里的 CleanWebpackPlugin 也不例外,使用它,就是通过这个类型创建一个实例,放入 plugins 数组中,具体代码如下:

  1. const { CleanWebpackPlugin } = require('clean-webpack-plugin')
  2. module.exports = {
  3. ...
  4. plugins: [
  5. new CleanWebpackPlugin()
  6. ]
  7. }

完成以后我们来测试一下 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 模块,我们这里同样需要单独安装这个模块,具体操作如下:

  1. cnpm install html-webpack-plugin --save-dev

安装完成过后,回到配置文件,载入这个模块,不同于 clean-webpack-plugin,html-webpack-plugin 插件默认导出的就是插件类型,不需要再解构内部成员,具体如下:

  1. const HtmlWebpackPlugin = require('html-webpack-plugin')

有了这个类型过后,回到配置对象的 plugins 属性中,同样需要添加一下这个类型的实例对象,完成这个插件的使用,具体配置代码如下:

  1. const HtmlWebpackPlugin = require('html-webpack-plugin')
  2. const { CleanWebpackPlugin } = require('clean-webpack-plugin')
  3. module.exports = {
  4. ...
  5. plugins: [
  6. new CleanWebpackPlugin(),
  7. new HtmlWebpackPlugin()
  8. ]
  9. }

最后我们回到命令行终端,再次运行打包命令,此时打包过程中就会自动生成一个 index.html 文件到 dist 目录。我们找到这个文件,可以看到文件中的内容就是一段使用了 main.js 的空白 HTML,具体结果如下:

  1. <!DOCTYPE html>
  2. <html>
  3. <head>
  4. <meta charset="utf-8">
  5. <title>Webpack App</title>
  6. <meta name="viewport" content="width=device-width, initial-scale=1">
  7. <script defer src="./main.js"></script>
  8. </head>
  9. <body>
  10. </body>
  11. </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 目录下的文件就会同时拷贝到输出目录中。

至此,我们简单了解了几个非常常用的插件,这里的重点是,你不仅要学会使用这几个插件的使用,还要能够总结出大多数插件在使用上的共性。