工作是这样的

作为一名母胎 solo 二十几年的我,平平无奇的一向活在他人的狗粮之下。逐渐的,我如同活成了一个随时见证他人爱情,也随时能为失恋的人排忧解难的人物。

【Webpack Plugin】写了个插件跟喜爱的女生表达,成果......

直到前两天,公司新来了一个前端妹子。

相视的第一眼,我神迷了,我知道,终究是躲不过去了……

【Webpack Plugin】写了个插件跟喜爱的女生表达,成果......

相逢却似曾相识,未曾相识已想念!

当晚,彻夜未眠…

【Webpack Plugin】写了个插件跟喜爱的女生表达,成果......

第二天早上,从搭档的口中得知了女生的名字,咱们暂且叫她小舒吧。

为了不暴露我的狼子野心(欲擒故纵拿捏的死死的),我决议出于搭档的关心询问一下项目了解的怎样样了,有没有需求我帮忙的。

没想到小舒像抓到了救命稻草一样:“小哥,你来的正好,过来帮我看看项目怎样跑不起来??”

【Webpack Plugin】写了个插件跟喜爱的女生表达,成果......

我回到座位上,很快的发现是由于项目中部分包的版别不兼容导致的,更新下版别就能够了。

正准备动身去找小舒时,一个奇怪的念头闪过……

我决议给咱们的第一次交流一个惊喜:借着这次解决问题的机遇,好好拉近一下咱们之间的联系!!!

【Webpack Plugin】写了个插件跟喜爱的女生表达,成果......

主意一来便挡也挡不住。我决议在项目中运转一个插件:当发动项目时,直接在操控台中向小舒表达我的心意!!!

没办法,单身这么多年肯定是有原因的!一定是我不够主动!这次我可要好好掌握这个机遇!!!

【Webpack Plugin】写了个插件跟喜爱的女生表达,成果......

说干就干

有了主意就开干,哥从来不是一个拖拖拉拉的人。

小舒的项目用的是 Webpack + React 技术栈,既然想要在项目发动的时候做工作,那肯定是得写个 Webpack 插件了。

先去官网了解一下 Webpack Plugin 的概念:

Webpack Plugin:向第三方开发者供给了 Webpack 引擎中完好的才能。使用阶段式的构建回调,开发者能够在 Webpack 构建流程中引入自界说的行为。创立插件比创立 loader 愈加高档,由于你需求了解 Webpack 底层的特性来处理相应的钩子

【Webpack Plugin】写了个插件跟喜爱的女生表达,成果......

浅显点说便是能够在构建流程中刺进咱们的自界说的行为,至于在哪个阶段刺进或许做什么工作都能够通过 Webpack Plugin 来完结。

别的官网还说到,想要弄清楚 Webpack 插件 得先弄清楚这三个东西:tapable、compiler 和 compilation目标,先快点花几分钟去了解一下,争夺在中午吃饭前搞定!

【Webpack Plugin】写了个插件跟喜爱的女生表达,成果......

tapable的使用姿势

tapable是一个类似于 Node.js 中的 EventEmitter 的库,但它更专心于自界说工作的触发和处理。通过 tapable 咱们能够注册自界说工作,然后在恰当的机遇去触发履行。

【Webpack Plugin】写了个插件跟喜爱的女生表达,成果......

举个比如:类比到 VueReact 结构中的生命周期函数,它们便是到了固定的时刻节点就履行对应的生命周期,tapable 做的工作就和这个差不多,能够先注册一系列的生命周期函数,然后在合适的时刻点履行。

概念了解的差不多了,接下来去实操一下。初始化项目,装置依靠:

npm init //初始化项目
yarn add tapable -D //装置依靠

装置完项目依靠后,依据以下目录结构来增加对应的目录和文件:

├── dist # 打包输出目录
├── node_modules
├── package-lock.json
├── package.json
└── src # 源码目录
     └── index.js # 进口文件

依据官方介绍,tapable 使用起来仍是挺简单的,只需三步:

  1. 实例化钩子函数( tapable会暴露出各式各样的 hook,这儿以同步钩子Synchook为例)
  2. 注册工作
  3. 触发工作

src/index.js

const { SyncHook } = require("tapable"); //这是一个同步钩子
//第一步:实例化钩子函数,能够在这儿界说形参
const syncHook = new SyncHook(["author"]);
//第二步:注册工作1
syncHook.tap("监听器1", (name) => {
  console.log("监听器1:", name);
});
//第二步:注册工作2
syncHook.tap("监听器2", (name) => {
  console.log("监听器2", name);
});
//第三步:触发工作
syncHook.call("不要秃头啊");

运转 node ./src/index.js,拿到履行成果:

监听器1 不要秃头啊
监听器2 不要秃头啊

【Webpack Plugin】写了个插件跟喜爱的女生表达,成果......

从上面的比如中能够看出 tapable 选用的是发布订阅模式通过 tap 函数注册监听函数,然后通过 call 函数按次序履行之前注册的函数

大致原理:

class SyncHook {
  constructor() {
    this.taps = [];
  }
  //注册监听函数,这儿的name其实没啥用
  tap(name, fn) {
    this.taps.push({ name, fn });
  }
  //履行函数
  call(...args) {
    this.taps.forEach((tap) => tap.fn(...args));
  }
}

别的,tapable 中不仅有 Synchook,还有其他类型的 hook:

【Webpack Plugin】写了个插件跟喜爱的女生表达,成果......

【Webpack Plugin】写了个插件跟喜爱的女生表达,成果......

这儿详细说一下这几个类型的概念:

  • Basic(基本的):履行每一个工作函数,不关心函数的返回值
  • Waterfall(瀑布式的):假如前一个工作函数的成果result !== undefined,则 result 会作为后一个工作函数的第一个参数(也便是上一个函数的履行成果会成为下一个函数的参数)
  • Bail(保险的):履行每一个工作函数,遇到第一个成果result !== undefined则返回,不再持续履行(也便是只需其间一个有返回了,后边的就不履行了)
  • Loop(循环的):不停的循环履行工作函数,直到所有函数成果result === undefined

大家也不用死记硬背,遇到相关的需求时查文档就好了。

在上面的比如中咱们用的SyncHook,它便是一个同步的钩子。又由于并不关心返回值,所以也算是一个基本类型的 hook。

【Webpack Plugin】写了个插件跟喜爱的女生表达,成果......

tabpable 和 Webpack 的联系

要说它们俩的联系,可真有点像男女朋友之间的难舍难分……

【Webpack Plugin】写了个插件跟喜爱的女生表达,成果......

Webpack 本质上是一种工作流的机制,它的工作流程便是将各个插件串联起来,比如

  • 在打包前需求处理用户传过来的参数,判断是选用单进口仍是多进口打包,便是通过 EntryOptionPlugin 插件来做的
  • 在打包过程中,需求知道选用哪种读文件的方式便是通过 NodeEnvironmentPlugin 插件来做的
  • 在打包完结后,需求先清空 dist 文件夹,便是通过 CleanWebpackPlugin 插件来完结的
  • ……

而完成这一切的核心便是 tapable。Webpack 内部通过 tapable 会提前界说好一系列不同阶段的 hook ,然后在固定的时刻点去履行(触发 call 函数)。而插件要做的便是通过 tap 函数注册自界说工作,然后让其操控在 Webapack 工作流上运转:

【Webpack Plugin】写了个插件跟喜爱的女生表达,成果......

持续拿 Vue 和 React 举例,就如同结构内部界说了一系列的生命周期,而咱们要做的便是在需求的时候界说好这些生命周期函数就好。

【Webpack Plugin】写了个插件跟喜爱的女生表达,成果......

‍♀️ Compiler 和 Compilation

在插件开发中还有两个很重要的资源:compiler 和 compilation目标。了解它们是扩展 Webpack 引擎的第一步。

  • compiler 目标代表了完好的 webpack 生命周期。这个目标在发动 Webpack 时被一次性建立,并装备好所有可操作的设置,包含 optionsloaderplugin。当在 Webpack 环境中应用一个插件时,插件将收到此 compiler 目标的引用。能够使用它来访问 Webpack 的主环境。
  • compilation 目标代表了一次资源版别构建。当运转 Webpack 开发环境中间件( webpack-dev-server)时,每逢检测到一个文件改变,就会创立一个新的 compilation,然后生成一组新的编译资源。一个 compilation 目标表现了当前的模块资源、编译生成资源、改变的文件、以及被跟踪依靠的状况信息。compilation 目标也供给了许多关键机遇的回调,以供插件做自界说处理时选择使用。

【Webpack Plugin】写了个插件跟喜爱的女生表达,成果......

仍是拿 React 结构举比如…… React:

【Webpack Plugin】写了个插件跟喜爱的女生表达,成果......

compiler比喻成 React 组件,在 React 组件中有一系列的生命周期函数(componentDidMount()render()componentDidUpdate()等等),这些钩子函数都能够在组件中被界说。

compilation比喻成 componentDidUpdate()componentDidUpdate()仅仅组件中的某一个钩子,它专门担任重复渲染的工作(compilation仅仅compiler中某一阶段的 hook ,首要担任对模块资源的处理,只不过它的工作愈加细化,在它内部还有一些子生命周期函数)。

假如仍是不了解,这儿画个图帮助大家了解:

【Webpack Plugin】写了个插件跟喜爱的女生表达,成果......

图上的 entryOption、afterPlugins、beforeRun、compilation 等均是构建过程中的生命周期,而 compilation 仅仅该过程中的其间一部分,它首要担任对模块资源的处理。在 compilation 内部也有自己的一系列生命周期,例如图中的 buildModule、finishModules 等。

【Webpack Plugin】写了个插件跟喜爱的女生表达,成果......

至于为什么要这么处理,原因当然是为了解耦!!!

比如当咱们发动 Webpack 的 watch模式,当文件模块发生改变时会重新进行编译,这个时候并不需求每次都重新创立 compiler 实例,只需求重新创立一个 compilation 来记载编译信息即可

别的,图中并没有将悉数的 hook 展示出来,更多的hook能够自行查阅官网:compiler上挂载的 hook ,compilation上挂载的 hook 。

【Webpack Plugin】写了个插件跟喜爱的女生表达,成果......

怎样编写插件

说了这么多,到底要怎样写一个 Webpack 插件?小舒还等着我呢!!!

【Webpack Plugin】写了个插件跟喜爱的女生表达,成果......

刚才知道了在 Webpack 中的 compilercompilation 目标上挂载着一系列的生命周期 hook ,那接下来应该怎样在这些生命周期中注册自界说工作呢?

webpack 插件:

【Webpack Plugin】写了个插件跟喜爱的女生表达,成果......

Webpack Plugin 其实便是一个普通的函数,在该函数中需求咱们定制一个 apply 办法。当 Webpack 内部进行插件挂载时会履行 apply 函数。咱们能够在 apply 办法中订阅各种生命周期钩子,当到达对应的时刻点时就会履行。

【Webpack Plugin】写了个插件跟喜爱的女生表达,成果......

这儿可能有同学要问了,为什么非要定制一个apply办法?为什么不是其他的办法?

在这儿我贴下官方源码:github.com/webpack/web… , 大家一看便一望而知:

if (options.plugins && Array.isArray(options.plugins)) {
  //这儿的options.plugins便是webpack.config.js中的plugins
  for (const plugin of options.plugins) {
    plugin.apply(compiler); //履行插件的apply办法
  }
}

这儿官方写死了履行插件中的 apply 办法….,并没有什么很高深的原因…..

【Webpack Plugin】写了个插件跟喜爱的女生表达,成果......

那咱们就按照标准写一个简易版的插件赶紧来练练手:在构建完结后打印日志。

首要咱们需求知道构建完结后对应的的生命周期是哪个,通过 查阅文档得知是 complier 中的done 这个 hook :

【Webpack Plugin】写了个插件跟喜爱的女生表达,成果......

接下来创立一个新项目验证咱们的主意,时刻不早了!小舒现在肯定很着急!!!

装置依靠:

npm init //初始化项目
yarn add webpack webpack-cli -D

装置完项目依靠后,依据以下目录结构来增加对应的目录和文件:

├── dist # 打包输出目录
├── plugins # 自界说插件文件夹
│   └── demo-plugin.js
├── node_modules
├── package-lock.json
├── package.json
├── src # 源码目录
│   └── index.js # 进口文件
└── webpack.config.js # webpack装备文件

demo-plugin.js

class DemoPlugin {
  apply(compiler) {
    //在done(构建完结后履行)这个hook上注册自界说工作
    compiler.hooks.done.tap("DemoPlugin", () => {
      console.log("DemoPlugin:编译结束了");
    });
  }
}
module.exports = DemoPlugin;

package.json

{
  "name": "webpack-plugin",
  "version": "1.0.0",
  "description": "",
  "license": "ISC",
  "author": "",
  "main": "index.js",
  "scripts": {
    "build": "webpack"
  },
  "devDependencies": {
    "tapable": "^2.2.1",
    "webpack": "^5.74.0",
    "webpack-cli": "^4.10.0"
  }
}

src/index.js

console.log("author:""不要秃头啊");

webpack.config.js

const DemoPlugin = require("./plugins/demo-plugin");
module.exports = {
  mode: "development",
  entry: "./src/index.js",
  devtool: false,
  plugins: [new DemoPlugin()],
};

运转 yarn build,运转成果:

yarn build
$ webpack
DemoPlugin:编译结束了
asset main.js 643 bytes [emitted] (name: main)
./src/index.js 476 bytes [built] [code generated]
webpack 5.74.0 compiled successfully in 71 ms
✨  Done in 0.64s.

【Webpack Plugin】写了个插件跟喜爱的女生表达,成果......

开端我的表达之路….

好了,总算搞清楚怎样写插件了!!!

【Webpack Plugin】写了个插件跟喜爱的女生表达,成果......

直接把刚才学的的demo插件改造一下:

class DonePlugin {
  apply(compiler) {
    //在done(构建完结后履行)这个hook上注册自界说工作
    compiler.hooks.done.tap("DonePlugin", () => {
      console.log(
        "小姐姐,我知道此时你很意外。但不知道怎样回事,我看见你的第一眼就沦亡了...能够给我一个多了解了解你的机遇吗? ————来自一个热心帮你解决问题的人"
      );
    });
  }
}
module.exports = DonePlugin;

正准备提交代码,思来想去,直接叫小姐姐如同不太好吧?是不是显得我很轻浮?

再说了,小舒怎样知道我在跟她说呢?

想了一会,不如直接用她的 git 账号名吧(其时要是脑子不抽风就好了……),所以改成动态获取git 用户名,为了显眼甚至还加了点色彩:

const chalk = require("chalk");//给日志加色彩插件
const execSync = require("child_process").execSync;
const error = chalk.bold.red; //红色日志
const warning = chalk.keyword("orange"); //橘色日志
class DonePlugin {
  apply(compiler) {
    compiler.hooks.done.tap("DonePlugin", () => {
      //获取git账号信息的username
      let name = execSync("git config user.name").toString().trim();
      console.log(
        error(`${name},`),
        warning(
          "我知道此时你很意外。但不知道怎样回事,我看见你的第一眼就沦亡了...能够给我一个多了解了解你的机遇吗?  ————来自一个热心帮你解决问题的人"
        )
      );
    });
  }
}
module.exports = DonePlugin;

大致效果便是这样…

【Webpack Plugin】写了个插件跟喜爱的女生表达,成果......

【Webpack Plugin】写了个插件跟喜爱的女生表达,成果......

等候回应

把这一切都准备稳当后,剩下的就交给天意了。

成果是左等右等,到了下午四点迟迟没有等到小舒的回应……

【Webpack Plugin】写了个插件跟喜爱的女生表达,成果......

难道是没看到吗?不应该啊,日志还加了色彩,很明显了!!!

莫非是女孩子太宛转了,害羞了?

不行,我得主动出击!!

【Webpack Plugin】写了个插件跟喜爱的女生表达,成果......

乘兴而去,败兴而归!!!还在搭档圈里闹了个笑话!!!

但是为了下半生,豁出去了!!!

通过我的一番解释,小舒总算信任了我说的话,而我也赶紧去优化了一下代码……

自此以后,每天一句不重样的小情话,小舒甚至还和我互动了起来:

【Webpack Plugin】写了个插件跟喜爱的女生表达,成果......

就这样,咱们渐渐的发展成了无话不谈的男女朋友联系,直到前两天甚至还过了1000天纪念日,还给小舒送了点小礼物,虽然被骂直男…

【Webpack Plugin】写了个插件跟喜爱的女生表达,成果......

接下来也该考虑结婚了!!!

“滴~~~,滴~~~,滴~~~,不要命了!等个红绿灯都能睡着?“

“喂,醒醒,醒醒。我的尿黄,让我去渍醒他!”

只听旁边有人说到……

本来仅仅南柯一梦。

【Webpack Plugin】写了个插件跟喜爱的女生表达,成果......

最终的结局

最终,给大家一个忠告:追女孩子一定不要这样, 一定要舍得送花,一定要懂浪漫!!!没有哪个女孩子会由于你写个插件就跟你在一起的!!!

我决议勇敢的试一试:

【Webpack Plugin】写了个插件跟喜爱的女生表达,成果......

卒。

本文收录于 从零到亿系统性的建立前端构建常识系统✨ 中的第五篇。

引荐阅读

  1. 从零到亿系统性的建立前端构建常识系统✨
  2. 我是怎样带领团队从零到一建立前端标准的?
  3. 前端工程化基石 — AST(笼统语法树)以及AST的广泛应用
  4. 学会这些自界说hooks,让你摸鱼时刻再翻一倍
  5. 浅析前端异常及降级处理
  6. 前端重新部署后,领导跟我说页面溃散了…
  7. 前端场景下的查找框,你真的了解了吗?
  8. 手把手教你完成React数据耐久化机制
  9. 面试官:你确定多窗口之间sessionStorage不能同享状况吗???