重学webpack系列(四) -- webpack的plugins机制的解读

697

上一章重学webpack系列(三) -- webpack的loader机制的解读,我们介绍了常用的loader的基本配置与作用,重点实践了一下css-loaderstyle-loader,也去了解了一下loader机制的背后原理,那么这一章将会跟同学们一起探讨一下webpackplugin系统。

前言

我们所了解的plugin也是webpack核心机制的一种,与loader在项目中加载资源模块实现模块化的工作不同的是,plugin能够解决除了资源模块加载打包之外的项目自动化的工作,增强了webpack在项目的构建能力。

plugin能够解决的常见的场景

  • 自动清除上一次的打包结果,比如clean-webpack-plugin

    • 上一章我们已经使用到了clean-webpack-plugin,实现了每一次的build文件夹自动删除替换,因为每一次打包我们生成的文件hash不一样,导致文件名不一样,不一样的文件只会增量叠加,覆盖的只是那些同名的文件,所以我们需要把上一次的所有build的内容全部清除掉。
  • 根据模板自动生成需要的html文件,比如html-webpack-plugin

    • 上一章我们利用了html-webpack-plugin插件,实现了html的生成,他可以自动引入我们打包后依赖的js文件。
  • 压缩webpack打包后的输出文件,比如uglifyjs-webpack-pluginmini-css-extract-pluginimagemin-webpack-plugin

  • 每一次修改模块代码之后实现热替换,比如Hot Module Replacement

  • 帮助开发者分析打包后的文件,以便做性能优化,比如webpack-bundle-analyzer

  • 拆分不需要打包的资源直接输出到文件目录,比如copy-webpack-plugin,自动发布打包结果到服务器实现自动化部署等。

插件的基本使用

在这里我们就以html-webpack-plugin为例来讲述一下它的具体使,我们可以在下面看到它的基本配置。

// 安装
npm i html-webapck-plugin -S

// 引入
const HtmlWebpackPlugin = require('html-webpack-plugin')

// webpack.config.json
module.exports = {
    ...
    plugins:[
        ...
        new HtmlWebpackPlugin({
            // 页面title
            titie: 'title',
            // 使用模板
            template: './index.html',
            meta:{
                // 修改meta标签
                viewport: 'width=device-widt, initial-scale=2.0'
            },
            // 输出到目录的文件名,默认index.html,配合多个HtmlWebpackPlugin实例,可以生成多个html文件
            filename: 'main.html'
            // 用于生成外链标签的src的公共路径
            publicPath: './',
            // script加载方式,比如defer,type=’module‘
            scriptLoading: 'module',
            // 用于指定favicon到选项卡里面
            favicon: '....',
            // 用于破坏缓存hash,设置了true则会在改变内容之后,每一次的打包之后改变hash
            hash: true
            更多请查阅:https://github.com/jantimon/html-webpack-plugin
        }),
        ...
    ]
    ...
}

所以经过这样的配置,html-webpack-plugin就可以成功使用了,再比如一些不需要参与构建的文件,比如favicon,我们通常希望webpack能帮助我们把它处理到静态目录里面,以便于上线的时候,随着资源包一起发布。

// 安装
npm i copy-webpack-plugin

// 引入
const CopyWebpackplugin = require('copy-webpack-plugin');

// webpack.config.json
module.exports = {
    ...
    plugins: [
        new CopyWebpackPlugin([
            {
                patterns:[
                    {   
                        // 以什么目录下资源作为源文件   
                        from: path.resolve(__dirname, './static'),
                        // copy到那个目录里面去
                        to: path.resolve(__dirname, 'build/static'),
                        // 忽略掉不处理的文件
                        ignore:[
                            "**/index.html", 
                            "**/.DS_Store",
                        ]
                    },
                ]
            }
        ])
    ]
}

插件系统尤其复杂,各位同学应该多多去看看官网对插件的描述及其用法,那么插件究竟是怎么样工作的呢,我们应该去探索一下插件背后的秘密。

插件的工作原理

webpackplugin的工作机制,说通俗一点就类似于框架里面的生命周期机制,它具有很多的生命周期钩子,比如enterOptionrunwatchRuncompile等,具体的同学们可以去查阅 官方文档,他能够提供模块/流程中所需要功能的处理的功能,举个例子,比如我们在使用css-loader的时候,我们希望实现cssjs文件的去空格化压缩,所以在这个时刻,webpack能够利用相应的plugin来帮我们处理。当然光了解plugin执行原理可不行,我们还得去了解一下webpack的构建流程与原理,详细请戳 >>> 重学webpack系列(八) -- webpack的构建原理与流程。

编写一个自己的插件

需求
  • 我们想把一个html文件中的类名改掉,从index改成main,比如
// index.html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <title>Document</title>
  <link rel="stylesheet" href="">
</head>
<body>
  <div class="index"></div>
</body>
</html>

插件的要求

  • 每一个插件的本身就是一个函数。
  • 每个函数内部都要实现一个apply方法。
  • 借助webpack生命周期钩子,通过compiler对象访问钩子,帮我们把插件注入到webpack运行流程中执行。
  • 根据需求编写我们的逻辑。

所以大致源码如下

// webpack.config.json
const ChangeWebpackplugin = require('./change-webpack-plugin.js')

module.exports = {
    ...
    plugins:[
        new ChangeWebpackPlugin( )
    ]
    ...
}

// change-webpack-plugin.js
class ChangeWebpackPlugin {
  // webpack启动的时候会调用apply方法
  apply(compiler){
    console.log('ChangeWebpackPlugin 插件启动');
    compiler.hooks.emit.tap('ChangeWebpackPlugin', compilation => {
      for(const name in compilation.assets){
        // 找到了所有打包后的文件 .assets
        if(name.endsWith('.js')){
          // 读取原内容
          const content = compilation.assets[name].source();
          // 处理逻辑
          const changeClassNameContent = content.replace("index", "main")
          // 处理函数逻辑
          compilation.assets[name] = {
            source:()=> changeClassNameContent,
            size: ()=> changeClassNameContent.length
          }
        }
      }
    })
  }  
}

// 导出函数
module.exports = ChangeWebpackPlugin;
文件效果
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <title>Document</title>
  <link rel="stylesheet" href="">
</head>
<body>
  <div class="main"></div>
</body>
</html>

总结

webpackplugin让整个webpack的构建能力得到了大大增强,但是在方便应用开发,学习怎么样使用插件的同时,我们应该也要去深挖一下plugin,到底是怎么去配合webpack执行机制去实现的功能的,后期我将会跟大家一起去学习webpack的构建流程与原理,那么下一章我们继续去探索webpack的另一个机制 -- 本地web服务器,直通车 >>> 重学webpack系列(五) -- webpack的devServer实践与原理