您当前的位置:首页 > 网站建设 > javascript
| php | asp | css | H5 | javascript | Mysql | Dreamweaver | Delphi | 网站维护 | 帝国cms | React | 考试系统 | ajax | jQuery | 小程序 |

浅谈Webpack4 plugins 实现原理

51自学网 2022-02-21 13:40:55
  javascript

前言

在 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 内容请搜索51zixue.net以前的文章或继续浏览下面的相关文章希望大家以后多多支持51zixue.net!


下载地址:
基于Vue3的全屏拖拽上传组件
一篇文章教你用React实现菜谱系统
万事OK自学网:51自学网_软件自学网_CAD自学网自学excel、自学PS、自学CAD、自学C语言、自学css3实例,是一个通过网络自主学习工作技能的自学平台,网友喜欢的软件自学网站。