前言

在 wabpack 中核心功能除了 loader 应该就是 plugins 插件了,它是在webpack执行过程中会广播一系列事件,plugin 会监听这些事件并通过 webpack Api 对输出文件做对应的处理, 如 hmlt-webpack-plugin 就是对模板魔剑 index.html 进行拷贝到 dist 目录的

认识

先来通过源码来认识一下 plugins 的基本结构
https://github.com/webpack/webpack/blob/webpack-4/lib/Compiler.js 551行

// 创建一个编译器
createChildCompiler(
  compilation,
  compilerName,
  compilerIndex,
  outputOptions,
  plugins // 里边就有包含插件
) {

   // new 一个 编译器
  const childCompiler = new Compiler(this.context);
  // 寻找存在的所有 plugins 插件
  if (Array.isArray(plugins)) {
    for (const plugin of plugins) {
       // 如果存在, 就调用 plugin 的 apply 方法
      plugin.apply(childCompiler);
    }
  }
  
  // 遍历寻找 plugin 对应的 hooks
  for (const name in this.hooks) {
    if (
      ![
        "make",
        "compile",
        "emit",
        "afterEmit",
        "invalid",
        "done",
        "thisCompilation"
      ].includes(name)
    ) {
    
      // 找到对应的 hooks 并调用, 
      if (childCompiler.hooks[name]) {
        childCompiler.hooks[name].taps = this.hooks[name].taps.slice();
      }
    }
  }
 
 // .... 省略 ....

  return childCompiler;
}

通过上述源码可以看出来 plugin 本质就是一个类, 首先就是 new 一个 compiler 类,传入当前的上下文,然后判断是否存在,存在则直接调用对应 plugin 的 apply 方法,然后再找到对应 plugin 调用的 hooks 事件流 , 发射给对应 hooks 事件
hooks 哪里来的 ?

https://github.com/webpack/webpack/blob/webpack-4/lib/Compiler.js 42行

// 上述的 Compiler 类继承自 Tapable 类,而 Tapable 就定义了这些 hooks 事件流
class Compiler extends Tapable {
 constructor(context) {
            super();
            this.hooks = {
                    /** @type {SyncBailHook<Compilation>} */
                    shouldEmit: new SyncBailHook(["compilation"]),
                    /** @type {AsyncSeriesHook<Stats>} */
                    done: new AsyncSeriesHook(["stats"]),
                    /** @type {AsyncSeriesHook<>} */
                    additionalPass: new AsyncSeriesHook([]),
                    /** @type {AsyncSeriesHook<Compiler>} */
                    beforeRun: new AsyncSeriesHook(["compiler"]),
                    /** @type {AsyncSeriesHook<Compiler>} */
                    run: new AsyncSeriesHook(["compiler"]),
                    /** @type {AsyncSeriesHook<Compilation>} */
                    emit: new AsyncSeriesHook(["compilation"]),
                    /** @type {AsyncSeriesHook<string, Buffer>} */
                    assetEmitted: new AsyncSeriesHook(["file", "content"]),
                    /** @type {AsyncSeriesHook<Compilation>} */
                    afterEmit: new AsyncSeriesHook(["compilation"]),

                    /** @type {SyncHook<Compilation, CompilationParams>} */
                    thisCompilation: new SyncHook(["compilation", "params"]),
                    /** @type {SyncHook<Compilation, CompilationParams>} */
                    compilation: new SyncHook(["compilation", "params"]),
                    /** @type {SyncHook<NormalModuleFactory>} */
                    normalModuleFactory: new SyncHook(["normalModuleFactory"]),
                    /** @type {SyncHook<ContextModuleFactory>}  */
                    contextModuleFactory: new SyncHook(["contextModulefactory"]),

                    /** @type {AsyncSeriesHook<CompilationParams>} */
                    beforeCompile: new AsyncSeriesHook(["params"]),
                    /** @type {SyncHook<CompilationParams>} */
                    compile: new SyncHook(["params"]),
                    /** @type {AsyncParallelHook<Compilation>} */
                    make: new AsyncParallelHook(["compilation"]),
                    /** @type {AsyncSeriesHook<Compilation>} */
                    afterCompile: new AsyncSeriesHook(["compilation"]),

                    /** @type {AsyncSeriesHook<Compiler>} */
                    watchRun: new AsyncSeriesHook(["compiler"]),
                    /** @type {SyncHook<Error>} */
                    failed: new SyncHook(["error"]),
                    /** @type {SyncHook<string, string>} */
                    invalid: new SyncHook(["filename", "changeTime"]),
                    /** @type {SyncHook} */
                    watchClose: new SyncHook([]),

                    /** @type {SyncBailHook<string, string, any[]>} */
                    infrastructureLog: new SyncBailHook(["origin", "type", "args"]),

                    // TODO the following hooks are weirdly located here
                    // TODO move them for webpack 5
                    /** @type {SyncHook} */
                    environment: new SyncHook([]),
                    /** @type {SyncHook} */
                    afterEnvironment: new SyncHook([]),
                    /** @type {SyncHook<Compiler>} */
                    afterPlugins: new SyncHook(["compiler"]),
                    /** @type {SyncHook<Compiler>} */
                    afterResolvers: new SyncHook(["compiler"]),
                    /** @type {SyncBailHook<string, Entry>} */
                    entryOption: new SyncBailHook(["context", "entry"])
            };
            
            // TODO webpack 5 remove this
            this.hooks.infrastructurelog = this.hooks.infrastructureLog;
               
            // 通过 tab 调用对应的 comiler 编译器,并传入一个回调函数
            this._pluginCompat.tap("Compiler", options => {
                    switch (options.name) {
                            case "additional-pass":
                            case "before-run":
                            case "run":
                            case "emit":
                            case "after-emit":
                            case "before-compile":
                            case "make":
                            case "after-compile":
                            case "watch-run":
                                    options.async = true;
                                    break;
                    }
            });
            // 下方省略 ......
  }

好了,了解过基本的结构之后,就可以推理出 plugin 基本的结构和用法了,就是下边这样

// 定义一个 plugins 类   
class MyPlugins {
    // 上边有说 new 一个编译器实例,会执行实例的 apply 方法,传入对应的 comiler 实例
    apply (compiler) {
        // 调用 new 出来 compiler 实例下的 hooks 事件流,通过 tab 触发,并接收一个回调函数
        compiler.hooks.done.tap('一般为插件昵称', (默认接收参数) => {
            console.log('进入执行体');
        })
    }
}
// 导出
module.exports = MyPlugins

ok, 以上就是一个简单的 模板 ,我们来试试内部的钩子函数,是否会如愿以偿的被调用和触发

配置 webpack

let path = require('path')
let DonePlugin = require('./plugins/DonePlugins')
let AsyncPlugins = require('./plugins/AsyncPlugins')

module.exports = {
    mode: 'development',
    entry: './src/index.js',
    output: {
        filename: 'build.js',
        path: path.resolve(__dirname, 'dist')
    },
    plugins: [
        new DonePlugin(),    // 内部同步 hooks
        new AsyncPlugins()   // 内部异步 hooks
    ]
}

同步 plugin 插件模拟调用

class DonePlugins {
    apply (compiler) {
        compiler.hooks.done.tap('DonePlugin', (stats) => {
            console.log('执行: 编译完成');
        })
    }
}

module.exports = DonePlugins

异步 plugin 插件模拟调用

class AsyncPlugins {
    apply (compiler) {
        compiler.hooks.emit.tapAsync('AsyncPlugin', (complete, callback) => {
            setTimeout(() => {
                console.log('执行:文件发射出来');
                callback()
            }, 1000)
        })
    }
}

module.exports = AsyncPlugins

最后编译 webpack 可以看到编译控制台,分别打印 执行: 编译完成,执行:文件发射出来,说明这样是可以调用到 hooks 事件流的,并且可以触发。

实践出真知

了解过基本结构和使用的方式了,现在来手写一个 plugin 插件,嗯,就来一个文件说明插件吧,我们日常打包,可以打包一个 xxx.md 文件到 dist 目录,来做一个打包说明,就来是实现这么一个小功能

文件说明插件

class FileListPlugin {
    // 初始化,获取文件的名称
    constructor ({filename}) {
        this.filename = filename
    }
    // 同样的模板形式,定义 apply 方法
    apply (compiler) {
        compiler.hooks.emit.tap('FileListPlugin', (compilation) => {
            // assets 静态资源,可以打印出  compilation 参数,还有很多方法和属性
            let assets = compilation.assets;
            
            // 定义输出文档结构
            let content = `## 文件名  资源大小\r\n`
            
            // 遍历静态资源,动态组合输出内容
            Object.entries(assets).forEach(([filename, stateObj]) => {
                content  = `- ${filename}    ${stateObj.size()}\r\n`
            })
            
            // 输出资源对象
            assets[this.filename] = {
                source () {
                    return content;
                },
                size () {
                    return content.length
                }
            }
            
        })
    }
}
// 导出
module.exports = FileListPlugin

webpack 配置

let path = require('path')
let HtmlWebpackPlugin = require('html-webpack-plugin')
// plugins 目录与node_modules 同级, 自定义 plugins , 与 loader 类似
let FileListPlugin = require('./plugins/FileListPlugin')

module.exports = {
    mode: 'development',
    entry: './src/index.js',
    output: {
        filename: 'build.js',
        path: path.resolve(__dirname, 'dist')
    },
    plugins: [
        new HtmlWebpackPlugin({
            template: './src/index.html',
            filename: 'index.html'
        }),
        new FileListPlugin({
            filename: 'list.md'
        })
    ]
}

ok,通过以上配置,我们再打包的时候就可以看到,每次打包在 dist 目录就会出现一个 xxx.md 文件,而这个文件的内容就是我们上边的 content

到此这篇关于浅谈Webpack4 plugins 实现原理的文章就介绍到这了,更多相关Webpack4 plugins 内容请搜索Devmax以前的文章或继续浏览下面的相关文章希望大家以后多多支持Devmax!

浅谈Webpack4 plugins 实现原理的更多相关文章

  1. webpack4+react多页面架构的实现

    webpack在单页面打包上应用广泛,以create-react-app为首的脚手架众多。这篇文章主要介绍了webpack4+react多页面架构的实现,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

  2. webpack4从0搭建组件库的实现

    这篇文章主要介绍了webpack4从0搭建组件库的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

  3. 基于Webpack4和React hooks搭建项目的方法

    这篇文章主要介绍了基于Webpack4和React hooks搭建项目的方法,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧

  4. 详解如何用webpack4从零开始构建react开发环境

    这篇文章主要介绍了详解如何用webpack4从零开始构建react开发环境,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧

  5. webpack4 + react 搭建多页面应用示例

    这篇文章主要介绍了webpack4 + react 搭建多页面应用示例,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧

  6. React+TypeScript+webpack4多入口配置详解

    这篇文章主要介绍了React+TypeScript+webpack4多入口配置详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

  7. 浅谈Webpack4 plugins 实现原理

    在wabpack 核心功能除了loader应该就是plugins插件了,本文主要介绍了Webpack4 plugins 实现原理,文中通过示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

  8. webpack4升级到webpack5的实战经验总结

    有些老项目的包长时间没有更新,导致项目中有些性能问题,在项目迭代中考虑升级包,下面这篇文章主要给大家介绍了关于webpack4升级到webpack5的实战经验,需要的朋友可以参考下

  9. 我正在尝试在Fevr WordPress主题上更新我的luvthemes核心插件;s向我显示此错误

    问题可能是什么?

  10. 设置中没有渐变项目渐变没有渐变运行

    [在此处输入图像描述](https://i.stack.imgur.com/redEt.png)

随机推荐

  1. js中‘!.’是什么意思

  2. Vue如何指定不编译的文件夹和favicon.ico

    这篇文章主要介绍了Vue如何指定不编译的文件夹和favicon.ico,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教

  3. 基于JavaScript编写一个图片转PDF转换器

    本文为大家介绍了一个简单的 JavaScript 项目,可以将图片转换为 PDF 文件。你可以从本地选择任何一张图片,只需点击一下即可将其转换为 PDF 文件,感兴趣的可以动手尝试一下

  4. jquery点赞功能实现代码 点个赞吧!

    点赞功能很多地方都会出现,如何实现爱心点赞功能,这篇文章主要为大家详细介绍了jquery点赞功能实现代码,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

  5. AngularJs上传前预览图片的实例代码

    使用AngularJs进行开发,在项目中,经常会遇到上传图片后,需在一旁预览图片内容,怎么实现这样的功能呢?今天小编给大家分享AugularJs上传前预览图片的实现代码,需要的朋友参考下吧

  6. JavaScript面向对象编程入门教程

    这篇文章主要介绍了JavaScript面向对象编程的相关概念,例如类、对象、属性、方法等面向对象的术语,并以实例讲解各种术语的使用,非常好的一篇面向对象入门教程,其它语言也可以参考哦

  7. jQuery中的通配符选择器使用总结

    通配符在控制input标签时相当好用,这里简单进行了jQuery中的通配符选择器使用总结,需要的朋友可以参考下

  8. javascript 动态调整图片尺寸实现代码

    在自己的网站上更新文章时一个比较常见的问题是:文章插图太宽,使整个网页都变形了。如果对每个插图都先进行缩放再插入的话,太麻烦了。

  9. jquery ajaxfileupload异步上传插件

    这篇文章主要为大家详细介绍了jquery ajaxfileupload异步上传插件,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

  10. React学习之受控组件与数据共享实例分析

    这篇文章主要介绍了React学习之受控组件与数据共享,结合实例形式分析了React受控组件与组件间数据共享相关原理与使用技巧,需要的朋友可以参考下

返回
顶部