<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          【W(wǎng)ebpack Plugin】寫(xiě)了個(gè)插件跟喜歡的女生表白,結(jié)果......

          共 10253字,需瀏覽 21分鐘

           ·

          2023-02-25 13:34

          歡迎關(guān)注前端早茶,與廣東靚仔攜手共同進(jìn)階~

          一、前言

          插件效果如下:

          用了chalk、child_process、tapable
          廣東靚仔看到這篇文章,感覺(jué)挺有意思的,故事情節(jié)如何,下面我們一起來(lái)看看。

          二、正文

           事情是這樣的

          作為一名母胎 solo 二十幾年的我,平平無(wú)奇的一直活在別人的狗糧之下。漸漸的,我好像活成了一個(gè)隨時(shí)見(jiàn)證別人愛(ài)情,也隨時(shí)能為失戀的人排憂解難的角色。

          直到前兩天,公司新來(lái)了一個(gè)前端妹子。

          相視的第一眼,我神迷了,我知道,終究是躲不過(guò)去了......

          相逢卻似曾相識(shí),未曾相識(shí)已相思!

          當(dāng)晚,徹夜未眠...

          第二天早上,從同事的口中得知了女生的名字,我們暫且叫她小舒吧。

          為了不暴露我的狼子野心(欲擒故縱拿捏的死死的),我決定出于同事的關(guān)心詢問(wèn)一下項(xiàng)目了解的怎么樣了,有沒(méi)有需要我?guī)兔Φ摹?/p>

          沒(méi)想到小舒像抓到了救命稻草一樣:“小哥,你來(lái)得正好,過(guò)來(lái)幫我看看項(xiàng)目怎么跑不起來(lái)??”

          我回到座位上,很快得發(fā)現(xiàn)是由于項(xiàng)目中部分包的版本不兼容導(dǎo)致的,更新下版本就可以了。

          正準(zhǔn)備起身去找小舒時(shí),一個(gè)奇怪的念頭閃過(guò)......

          我決定給我們的第一次交流一個(gè)驚喜:借著這次解決問(wèn)題的機(jī)會(huì),好好拉近一下我們之間的關(guān)系!!!

          想法一來(lái)便擋也擋不住。我決定在項(xiàng)目中運(yùn)行一個(gè)插件:當(dāng)啟動(dòng)項(xiàng)目時(shí),直接在控制臺(tái)中向小舒表達(dá)我的心意!!!??????

          沒(méi)辦法,單身這么多年肯定是有原因的!一定是我不夠主動(dòng)!這次我可要好好把握這個(gè)機(jī)會(huì)!!!

           說(shuō)干就干

          有了想法就開(kāi)干,哥從來(lái)不是一個(gè)拖拖拉拉的人。

          小舒的項(xiàng)目用的是 Webpack + React 技術(shù)棧,既然想要在項(xiàng)目啟動(dòng)的時(shí)候做事情,那肯定是得寫(xiě)個(gè) Webpack 插件了。

          先去官網(wǎng)了解一下 Webpack Plugin 的概念:

          Webpack Plugin:向第三方開(kāi)發(fā)者提供了 Webpack 引擎中完整的能力。使用階段式的構(gòu)建回調(diào),開(kāi)發(fā)者可以在 Webpack 構(gòu)建流程中引入自定義的行為。創(chuàng)建插件比創(chuàng)建 loader 更加高級(jí),因?yàn)槟阈枰斫?Webpack 底層的特性來(lái)處理相應(yīng)的鉤子

          通俗點(diǎn)說(shuō)就是可以在構(gòu)建流程中插入我們的自定義的行為,至于在哪個(gè)階段插入或者做什么事情都可以通過(guò) Webpack Plugin 來(lái)完成。

          另外官網(wǎng)還提到,想要弄清楚 Webpack 插件 得先弄清楚這三個(gè)東西:tapablecompiler 和 compilation對(duì)象,先快點(diǎn)花幾分鐘去了解一下,爭(zhēng)取在中午吃飯前搞定!

          tapable的使用姿勢(shì)

          tapable是一個(gè)類似于 Node.js 中的 EventEmitter 的庫(kù),但它更專注于自定義事件的觸發(fā)和處理。通過(guò) tapable 我們可以注冊(cè)自定義事件,然后在適當(dāng)?shù)臅r(shí)機(jī)去觸發(fā)執(zhí)行。

          舉個(gè)例子??:類比到 Vue 和 React 框架中的生命周期函數(shù),它們就是到了固定的時(shí)間節(jié)點(diǎn)就執(zhí)行對(duì)應(yīng)的生命周期,tapable 做的事情就和這個(gè)差不多,可以先注冊(cè)一系列的生命周期函數(shù),然后在合適的時(shí)間點(diǎn)執(zhí)行。

          概念了解得差不多了,接下來(lái)去實(shí)操一下。初始化項(xiàng)目,安裝依賴:
          npm init //初始化項(xiàng)目
          yarn add tapable -D //安裝依賴
          安裝完項(xiàng)目依賴后,根據(jù)以下目錄結(jié)構(gòu)來(lái)添加對(duì)應(yīng)的目錄和文件:
          ├── dist # 打包輸出目錄
          ├── node_modules
          ├── package-lock.json
          ├── package.json
          └── src # 源碼目錄
               └── index.js # 入口文件

          根據(jù)官方介紹,tapable 使用起來(lái)還是挺簡(jiǎn)單的,只需三步:

          1. 實(shí)例化鉤子函數(shù)( tapable會(huì)暴露出各種各樣的 hook,這里以同步鉤子Synchook為例)

          2. 注冊(cè)事件

          3. 觸發(fā)事件


          src/index.js
          const { SyncHook } = require("tapable"); //這是一個(gè)同步鉤子

          //第一步:實(shí)例化鉤子函數(shù),可以在這里定義形參
          const syncHook = new SyncHook(["author"]);

          //第二步:注冊(cè)事件1
          syncHook.tap("監(jiān)聽(tīng)器1", (name) => {
            console.log("監(jiān)聽(tīng)器1:", name);
          });

          //第二步:注冊(cè)事件2
          syncHook.tap("監(jiān)聽(tīng)器2", (name) => {
            console.log("監(jiān)聽(tīng)器2", name);
          });

          //第三步:觸發(fā)事件
          syncHook.call("不要禿頭啊");
          運(yùn)行 node ./src/index.js,拿到執(zhí)行結(jié)果:
          監(jiān)聽(tīng)器1 不要禿頭啊
          監(jiān)聽(tīng)器2 不要禿頭啊

          從上面的例子中可以看出 tapable 采用的是發(fā)布訂閱模式通過(guò) tap 函數(shù)注冊(cè)監(jiān)聽(tīng)函數(shù),然后通過(guò) call 函數(shù)按順序執(zhí)行之前注冊(cè)的函數(shù)

          大致原理:
          class SyncHook {
            constructor() {
              this.taps = [];
            }

            //注冊(cè)監(jiān)聽(tīng)函數(shù),這里的name其實(shí)沒(méi)啥用
            tap(name, fn) {
              this.taps.push({ name, fn });
            }

            //執(zhí)行函數(shù)
            call(...args) {
              this.taps.forEach((tap) => tap.fn(...args));
            }
          }

          另外,tapable 中不僅有 Synchook,還有其他類型的 hook:



          這里詳細(xì)說(shuō)一下這幾個(gè)類型的概念:

          • Basic(基本的):執(zhí)行每一個(gè)事件函數(shù),不關(guān)心函數(shù)的返回值

          • Waterfall(瀑布式的):如果前一個(gè)事件函數(shù)的結(jié)果 result !== undefined,則 result 會(huì)作為后一個(gè)事件函數(shù)的第一個(gè)參數(shù)(也就是上一個(gè)函數(shù)的執(zhí)行結(jié)果會(huì)成為下一個(gè)函數(shù)的參數(shù))

          • Bail(保險(xiǎn)的):執(zhí)行每一個(gè)事件函數(shù),遇到第一個(gè)結(jié)果 result !== undefined 則返回,不再繼續(xù)執(zhí)行(也就是只要其中一個(gè)有返回了,后面的就不執(zhí)行了)

          • Loop(循環(huán)的)不停循環(huán)執(zhí)行事件函數(shù),直到所有函數(shù)結(jié)果 result === undefined

          大家也不用死記硬背,遇到相關(guān)的需求時(shí)查文檔就好了。

          在上面的例子中我們用的SyncHook,它就是一個(gè)同步的鉤子。又因?yàn)椴⒉魂P(guān)心返回值,所以也算是一個(gè)基本類型的 hook。

           tabpable 和 Webpack 的關(guān)系

          要說(shuō)它們倆的關(guān)系,可真有點(diǎn)像男女朋友之間的難舍難分......

          Webpack 本質(zhì)上是一種事件流的機(jī)制,它的工作流程就是將各個(gè)插件串聯(lián)起來(lái),比如

          • 在打包前需要處理用戶傳過(guò)來(lái)的參數(shù),判斷是采用單入口還是多入口打包,就是通過(guò) EntryOptionPlugin 插件來(lái)做的

          • 在打包過(guò)程中,需要知道采用哪種讀文件的方式就是通過(guò) NodeEnvironmentPlugin 插件來(lái)做的

          • 在打包完成后,需要先清空 dist 文件夾,就是通過(guò) CleanWebpackPlugin 插件來(lái)完成的

          • ......


          而實(shí)現(xiàn)這一切的核心就是 tapable。Webpack 內(nèi)部通過(guò) tapable 會(huì)提前定義好一系列不同階段的 hook ,然后在固定的時(shí)間點(diǎn)去執(zhí)行(觸發(fā) call 函數(shù))。而插件要做的就是通過(guò) tap 函數(shù)注冊(cè)自定義事件,從而讓其控制在 Webapack 事件流上運(yùn)行:

          繼續(xù)拿 Vue 和 React 舉例,就好像框架內(nèi)部定義了一系列的生命周期,而我們要做的就是在需要的時(shí)候定義好這些生命周期函數(shù)就好。

          Compiler 和 Compilation 

          在插件開(kāi)發(fā)中還有兩個(gè)很重要的資源:compiler 和 compilation對(duì)象。理解它們是擴(kuò)展 Webpack 引擎的第一步。
          • compiler 對(duì)象代表了完整的 webpack 生命周期。這個(gè)對(duì)象在啟動(dòng) Webpack 時(shí)被一次性建立,并配置好所有可操作的設(shè)置,包括 optionsloader 和 plugin當(dāng)在 Webpack 環(huán)境中應(yīng)用一個(gè)插件時(shí),插件將收到此 compiler 對(duì)象的引用。可以使用它來(lái)訪問(wèn) Webpack 的主環(huán)境。

          • compilation 對(duì)象代表了一次資源版本構(gòu)建。當(dāng)運(yùn)行 Webpack 開(kāi)發(fā)環(huán)境中間件( webpack-dev-server)時(shí),每當(dāng)檢測(cè)到一個(gè)文件變化,就會(huì)創(chuàng)建一個(gè)新的 compilation,從而生成一組新的編譯資源。一個(gè) compilation 對(duì)象表現(xiàn)了當(dāng)前的模塊資源、編譯生成資源、變化的文件、以及被跟蹤依賴的狀態(tài)信息。compilation 對(duì)象也提供了很多關(guān)鍵時(shí)機(jī)的回調(diào),以供插件做自定義處理時(shí)選擇使用。

          還是拿 React 框架舉例子...... React:

          compiler比喻成 React 組件,在 React 組件中有一系列的生命周期函數(shù)(componentDidMount()render()componentDidUpdate()等等),這些鉤子函數(shù)都可以在組件中被定義。
          compilation比喻成 componentDidUpdate()componentDidUpdate()只是組件中的某一個(gè)鉤子,它專門負(fù)責(zé)重復(fù)渲染的工作(compilation只是compiler中某一階段的 hook ,主要負(fù)責(zé)對(duì)模塊資源的處理,只不過(guò)它的工作更加細(xì)化,在它內(nèi)部還有一些子生命周期函數(shù))。

          如果還是不理解,這里畫(huà)個(gè)圖幫助大家理解:

          圖上的 entryOptionafterPluginsbeforeRuncompilation 等均是構(gòu)建過(guò)程中的生命周期,而 compilation 只是該過(guò)程中的其中一部分,它主要負(fù)責(zé)對(duì)模塊資源的處理。在 compilation 內(nèi)部也有自己的一系列生命周期,例如圖中的 buildModulefinishModules 等。

          至于為什么要這么處理,原因當(dāng)然是為了解耦!!!

          比如當(dāng)我們啟動(dòng) Webpack 的 watch模式,當(dāng)文件模塊發(fā)生變化時(shí)會(huì)重新進(jìn)行編譯,這個(gè)時(shí)候并不需要每次都重新創(chuàng)建 compiler 實(shí)例,只需要重新創(chuàng)建一個(gè) compilation 來(lái)記錄編譯信息即可
          另外,圖中并沒(méi)有將全部的 hook 展示出來(lái),更多的hook可以自行查閱官網(wǎng):compiler上掛載的 hook ,compilation上掛載的 hook 

          如何編寫(xiě)插件

          說(shuō)了這么多,到底要怎么寫(xiě)一個(gè) Webpack 插件?小舒還等著我呢!!!

          剛才知道了在 Webpack 中的 compiler 和 compilation 對(duì)象上掛載著一系列的生命周期 hook ,那接下來(lái)應(yīng)該怎么在這些生命周期中注冊(cè)自定義事件呢?

          webpack 插件:

          Webpack Plugin 其實(shí)就是一個(gè)普通的函數(shù),在該函數(shù)中需要我們定制一個(gè) apply 方法。當(dāng) Webpack 內(nèi)部進(jìn)行插件掛載時(shí)會(huì)執(zhí)行 apply 函數(shù)。我們可以在 apply 方法中訂閱各種生命周期鉤子,當(dāng)?shù)竭_(dá)對(duì)應(yīng)的時(shí)間點(diǎn)時(shí)就會(huì)執(zhí)行。

          這里可能有同學(xué)要問(wèn)了,為什么非要定制一個(gè)apply方法?為什么不是其他的方法?

          在這里我貼下官方源碼, 大家一看便一目了然:
          if (options.plugins && Array.isArray(options.plugins)) {
            //這里的options.plugins就是webpack.config.js中的plugins
            for (const plugin of options.plugins) {
              plugin.apply(compiler); //執(zhí)行插件的apply方法
            }
          }
          這里官方寫(xiě)死了執(zhí)行插件中的 apply 方法....,并沒(méi)有什么很高深的原因.....

          那我們就按照規(guī)范寫(xiě)一個(gè)簡(jiǎn)易版的插件趕緊來(lái)練練手:在構(gòu)建完成后打印日志。

          首先我們需要知道構(gòu)建完成后對(duì)應(yīng)的的生命周期是哪個(gè),通過(guò) 查閱文檔得知是 complier 中的done 這個(gè) hook :

          接下來(lái)創(chuàng)建一個(gè)新項(xiàng)目驗(yàn)證我們的想法,時(shí)間不早了!小舒現(xiàn)在肯定很著急!!!

          安裝依賴:

          npm init //初始化項(xiàng)目
          yarn add webpack webpack-cli -D
          安裝完項(xiàng)目依賴后,根據(jù)以下目錄結(jié)構(gòu)來(lái)添加對(duì)應(yīng)的目錄和文件:
          ├── 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(構(gòu)建完成后執(zhí)行)這個(gè)hook上注冊(cè)自定義事件
              compiler.hooks.done.tap("DemoPlugin", () => {
                console.log("DemoPlugin:編譯結(jié)束了");
              });
            }
          }

          module.exports = DemoPlugin;

          package.json
          {
            "name""my_webpack_plugin",
            "version""1.0.0",
            "description""",
            "main""index.js",
            "scripts": {
              "build""webpack"
            },
            "author""",
            "license""ISC",
            "devDependencies": {
              "tapable""^2.2.1",
              "webpack""^5.75.0",
              "webpack-cli""^5.0.1"
            },
            "dependencies": {
              "chalk""^4.2.0",
              "child_process""^1.0.2"
            }
          }

          src/index.js
          console.log("author:""不要禿頭啊");

          webpack.config.js
          const DemoPlugin = require("./plugins/demo-plugin");

          module.exports = {
            mode"development",
            entry"./src/index.js",
            devtoolfalse,
            plugins: [new DemoPlugin()],
          };
          運(yùn)行 yarn build,運(yùn)行結(jié)果:
          yarn build
          $ webpack
          DemoPlugin:編譯結(jié)束了
          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.

          開(kāi)始我的表白之路....

          好了,終于搞清楚怎么寫(xiě)插件了!!!

          直接把剛才學(xué)的的demo插件改造一下:

          class DonePlugin {
            apply(compiler) {
              //在done(構(gòu)建完成后執(zhí)行)這個(gè)hook上注冊(cè)自定義事件
              compiler.hooks.done.tap("DonePlugin", () => {
                console.log(
                  "小姐姐,我知道此刻你很意外。但不知道怎么回事,我看見(jiàn)你的第一眼就淪陷了...可以給我一個(gè)多了解了解你的機(jī)會(huì)嗎? ————來(lái)自一個(gè)熱心幫你解決問(wèn)題的人"
                );
              });
            }
          }
          module.exports = DonePlugin;

          正準(zhǔn)備提交代碼,思來(lái)想去,直接叫小姐姐好像不太好吧?是不是顯得我很輕浮?

          再說(shuō)了,小舒怎么知道我在跟她說(shuō)呢?

          想了一會(huì),不如直接用她的 git 賬號(hào)名吧(當(dāng)時(shí)要是腦子不抽風(fēng)就好了......??),于是改成動(dòng)態(tài)獲取git 用戶名,為了顯眼甚至還加了點(diǎn)顏色:
          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賬號(hào)信息的username
                let name = execSync("git config user.name").toString().trim();

                console.log(
                  error(`${name},`),
                  warning(
                    "我知道此刻你很意外。但不知道怎么回事,我看見(jiàn)你的第一眼就淪陷了...可以給我一個(gè)多了解了解你的機(jī)會(huì)嗎?  ————來(lái)自一個(gè)熱心幫你解決問(wèn)題的人"
                  )
                );
              });
            }
          }

          module.exports = DonePlugin;

          大致效果就是這樣...

          等待回應(yīng)

          把這一切都準(zhǔn)備妥當(dāng)后,剩下的就交給天意了。

          結(jié)果是左等右等,到了下午四點(diǎn)遲遲沒(méi)有等到小舒的回應(yīng)......

          難道是沒(méi)看到嗎?不應(yīng)該啊,日志還加了顏色,很明顯了!!!

          莫非是女孩子太含蓄了,害羞了?

          不行,我得主動(dòng)出擊!!

          乘興而去,敗興而歸!!!還在同事圈里鬧了個(gè)笑話!!!

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

          經(jīng)過(guò)我的一番解釋,小舒總算相信了我說(shuō)的話,而我也趕緊去優(yōu)化了一下代碼......

          自此以后,每天一句不重樣的小情話,小舒甚至還和我互動(dòng)了起來(lái):

          就這樣,我們慢慢的發(fā)展成了無(wú)話不談的男女朋友關(guān)系,直到前兩天甚至還過(guò)了1000天紀(jì)念日,還給小舒送了點(diǎn)小禮物,雖然被罵直男...

          接下來(lái)也該考慮結(jié)婚了!!!

          “滴~~~,滴~~~,滴~~~,不要命了!等個(gè)紅綠燈都能睡著?“

          “喂,醒醒,醒醒。我的尿黃,讓我去漬醒他!”

          只聽(tīng)旁邊有人說(shuō)到......

          原來(lái)只是黃粱一夢(mèng)。

          最后的結(jié)局

          最后,給大家一個(gè)忠告:追女孩子一定不要這樣, 一定要舍得送花,一定要懂浪漫!!!沒(méi)有哪個(gè)女孩子會(huì)因?yàn)槟銓?xiě)個(gè)插件就跟你在一起的!!!

          我決定勇敢的試一試:

          本文轉(zhuǎn)載自:https://juejin.cn/post/7160467329334607908

          面試題庫(kù)推薦


            百度某部門面試原題
            某中型公司面試原題
            【精品】前端知識(shí)梳理


          三、最后

          關(guān)注我,一起攜手進(jìn)階

          瀏覽 74
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  影音先锋黄片 | 六月婷婷综合 | 六月婷婷七月丁香 | 日批网站全免费 | 无码人妻AⅤ一区二区三区A片一 |