Plugin工作机制

前端工程化模块化开发Webpack

旨在增强webpack自动化能力,Loader专注实现资源模块加载,实现项目整体打包。Plugin解决除了资源加载以外其他自动化工作。相比于Loader,Plugin拥有更宽的能范围。

例如:

  • 在打包之前自动清除dist目录
  • 拷贝静态文件至输出目录
  • 压缩输出代码

有了Plugin的webpack实现了大多数前端工程化的工作。

# webpack插件工作原理

webpack插件就是通过在生命周期的钩子中挂载函数实现扩展的:

在webpack工作的过程中有很多的环节,为了便于插件的扩展,webpack给每一个环节都埋下了一个钩子,这样我们可以往不同的节点上挂载不同的任务,就可以轻松去扩展webpack的能力。

webpack钩子有哪些?

文档查找Compiler Hooks API (opens new window)

# webpack常用插件

# clean-webpack-plugin

自动清除输出目录插件

原生是同名文件覆盖,其他文件不清理,并不是很合理,合理的是每次打包之前自动清理dist目录。

  1. 安装插件npm i clean-webpack-plugin --save-dev
  2. 在webpack.config.js中写
const { CleanWebpackPlugin } = require('clean-webpack-plugin')
// 专门用来配置插件的地方,是一个数组,添加一个插件就是添加一个元素
// 类型实例放入数组中
plugins: [
    new CleanWebpackPlugin()
]
1
2
3
4
5
6
  1. 命令行运行npm run build可以看到output文件是被清理过后生成的

# html-webpack-plugin

这个插件的作用在于自动生成HTML文件

# 自动生成使用bundle.js的HTML

之前我们是通过硬编码的方式把html放在项目根目录下面的。这种方式有两个问题:

  1. 我们在发布的时候需要发布根目录下面的index.html文件和dist目录下面所有的打包结果
  2. 上线之后我们要确保路径和名称没有问题,而且如果文件名要修改的话需要手动去修改。

最好的办法是通过Webpack输出HTML文件,这样我们只需要发布dist文件即可,而且也不需要考虑文件名称和引用路径的问题。

  1. 安装npm i html-webpack-plugin --save-dev
  2. 在webpack.config.js中引用
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
    ...,
    plugins: [
        new CleanWebpackPlugin(),
        new HtmlWebpackPlugin()
    ]
}
1
2
3
4
5
6
7
8
  1. 命令行中执行npm run build,可以看到在output目录下面生成了一个index.html文件
  2. 如果引用路径有错误,是之前我们设置的publicPath,因为当时的index.html在根目录下面,如果把index.html放在output目录下面,那么就不需要再设置publickPath了,这个时候需要删除。并且把根目录下面的index.html文件删除即可。

# 给HTML添加自定义配置

  • 页面的标题是需要修改的
  • 我们还需要去自定义原数据标签和一些基础的DOM结构

对于简单的我们通过html-webpack-plugin的属性来实现

plugins: [
    new CleanWebpackPlugin(),
    new HtmlWebpackPlugin({
        title: 'webpack plugin sample',
        meta: {
            viewport: 'width=device-width'
        }
    })
]
1
2
3
4
5
6
7
8
9

对于我们需要大量的修改的话,最好是生成一个模板,让html-webpack-plugin根据模板生成一个页面:

  1. 在src文件夹下添加一个index.ejs模板
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>webpack</title>
</head>
<body>
  <div class="container">
    <!-- 这个变量是内部变量,我们也可以自己添加自定义变量 -->
    <h1><%= htmlWebpackPlugin.options.title %></h1>
    <h2><%= htmlWebpackPlugin.options.hello %></h2>
  </div>
</body>
</html>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
  1. 在webpack.config.js里面配置模板
plugins: [
    new CleanWebpackPlugin(),
    new HtmlWebpackPlugin({
      title: 'webpack plugin sample',
      hello: 'hello~',
      meta: {
        viewport: 'width=device-width'
      },
      //在这里设置template模板的路径是src下的index.html文件
      template: './src/index.ejs'
    })
  ]
1
2
3
4
5
6
7
8
9
10
11
12
  1. 命令行运行npm run build,可以看到output目录下的index.html被解析出来
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>webpack</title>
<meta name="viewport" content="width=device-width"></head>
<body>
  <div class="container">
    <!-- 这个变量是内部提供的变量,我们也可以自己添加自定义变量 -->
    <h1>webpack plugin sample</h1>
    <h2>hello~</h2>
  </div>
<script src="bundle.js"></script></body>
</html>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# 同时输出多个页面文件

我们可以在webpack.config.js中再通过new HtmlWebpackPlugin创建新的实例对象

plugins: [
    new CleanWebpackPlugin(),
    new HtmlWebpackPlugin({
      title: 'webpack plugin sample',
      hello: 'hello~',
      meta: {
        viewport: 'width=device-width'
      },
      template: './src/index.ejs'
    }),
    new HtmlWebpackPlugin({
      filename: 'about.html',
      title: 'About'
    })
  ]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

就可看到在output目录下新生成了一个about.html文件

# copy-webpack-plugin

我们在项目中还有一些不需要构建的静态文件,他们最终也要发布到线上,例如:public目录

  1. 安装npm i copy-webpack-plugin --save-dev
  2. 在webpack中引用
const CopyWebpackPlugin = require('copy-webpack-plugin')
new CopyWebpackPlugin({
  // 传入数组,用于我们需要copy的文件路径
  // 可以是一个通配符,也可以是一个目录或者文件的相对路径
  // 'public/**'这个是将public目录下的所有按照目录结构copy到output中
  // 下面是把public目录整体打散copy到output目录下
  patterns: [{
    from: 'public',
    // to是到的文件位置,如果不写默认是output目录
    // 如果写的话和上面的output属性一样,要写绝对路径
    // to: path.join(__dirname, 'output/public')
  }]
})
1
2
3
4
5
6
7
8
9
10
11
12
13
  1. 在命令行中运行npm run build可以看到在output目录中copy了我们需要的文件

PS: 一般在上线前去使用,在平时的打包过程中不会使用这个,因为我们在平时打包比较频繁,如果复制的文件比较多,开销就比较大,打包速度就会降低

# 其他插件可以去搜索

# 动手实践:开发一个webpack插件

开发一个删除bundle.js中的无用注释的webpack插件

PS: webpack要求插件必须是一个函数,或者是一个包含apply方法的对象。一般我们会定义一个类,在类中定义一个apply方法,在使用的时候就是通过类型创建一个实例。

  1. 在webpack.config.js中创建一个类MyPlugin
class MyPlugin {
  /**
   * apply方法会在启动时自动被调用
   * @param {object} compiler 
   * compiler对象参数,是webpack工作中最核心的对象
   * compiler里面包含了此次构建的所有配置信息,我们也是通过这个对象去注册钩子函数
   * 
   * 这个插件目的:是为了清除webpack打包中没有必要有的注释,这样去除了注释之后就更加容易阅读
   * 
   * 步骤:需要在bundle.js内容明确之后再进行清除
   */
  apply (compiler) {
    console.log('MyPlugin 启动')
    /**
     * emit 这个方法是webpack即将要往输出目录输出之前执行,
     * 通过compiler.hooks.emit访问到那个钩子
     * 使用tap方法注册一个钩子,两个参数
     *  第一个参数是插件名称:MyPlugin
     *  第二个参数是执行的函数,并且要接收一个compilation的对象参数,compilation可以理解为此次打包的上下文,所有打包的结果都会放到这个对象中
     */
    compiler.hooks.emit.tap('MyPlugin', compilation => {
      // 通过assets属性获取即将写入文件目录的资源文件信息,我们需要遍历这个对象,键是每个文件的名称,值的source方法可以访问内容
      for (const name in compilation.assets) {
        // 键是每个文件的名称
        // console.log(name)
        // 值的source方法可以访问内容
        // console.log(compilation.assets[name].source())
        // 匹配后缀是.js文件的文件名
        if (name.endsWith('.js')) {
          // 获取文件对应的内容
          const contents = compilation.assets[name].source()
          // 将所有的注释都替换掉
          const withoutComments = contents.replace(/\/\*\**\*\//g, '')
          // 把现在的变量替换到原来的source方法下,并且要更新一下文件大小size
          compilation.assets[name] = {
            source: () => withoutComments,
            size: () => withoutComments.length
          }
        }
      }
    })
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
  1. 在plugins中引用
plugins: [
    ...,
    new MyPlugin()
]    
1
2
3
4
  1. 在命令行中执行npm run build就可以看到output中的bundle.js的无用注释被删除了。
更新时间: 2021-10-11 15:57