国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

手寫一個(gè)webpack插件

cnio / 3355人閱讀

摘要:引入定義一個(gè)自己的插件。一個(gè)最基礎(chǔ)的的代碼是這樣的在構(gòu)造函數(shù)中獲取用戶給該插件傳入的配置會(huì)調(diào)用實(shí)例的方法給插件實(shí)例傳入對(duì)象導(dǎo)出在使用這個(gè)時(shí),相關(guān)配置代碼如下和在開發(fā)時(shí)最常用的兩個(gè)對(duì)象就是和,它們是和之間的橋梁。

本文示例源代碼請(qǐng)戳github博客,建議大家動(dòng)手敲敲代碼。

webpack本質(zhì)上是一種事件流的機(jī)制,它的工作流程就是將各個(gè)插件串聯(lián)起來(lái),而實(shí)現(xiàn)這一切的核心就是Tapable,webpack中最核心的負(fù)責(zé)編譯的Compiler和負(fù)責(zé)創(chuàng)建bundles的Compilation都是Tapable的實(shí)例。Tapable暴露出掛載plugin的方法,使我們能 將plugin控制在webapack事件流上運(yùn)行(如下圖)。

Tabable是什么?

tapable庫(kù)暴露了很多Hook(鉤子)類,為插件提供掛載的鉤子。

const {
    SyncHook,
    SyncBailHook,
    SyncWaterfallHook,
    SyncLoopHook,
    AsyncParallelHook,
    AsyncParallelBailHook,
    AsyncSeriesHook,
    AsyncSeriesBailHook,
    AsyncSeriesWaterfallHook
 } = require("tapable");

Tabable 用法

1.new Hook 新建鉤子

tapable 暴露出來(lái)的都是類方法,new 一個(gè)類方法獲得我們需要的鉤子。

class 接受數(shù)組參數(shù)options,非必傳。類方法會(huì)根據(jù)傳參,接受同樣數(shù)量的參數(shù)。

const hook1 = new SyncHook(["arg1", "arg2", "arg3"]);

2.使用 tap/tapAsync/tapPromise 綁定鉤子
tapable提供了同步&異步綁定鉤子的方法,并且他們都有綁定事件和執(zhí)行事件對(duì)應(yīng)的方法。

- Async* Sync*
綁定 tapAsync/tapPromise/tap tap
執(zhí)行 callAsync/promise call

3.call/callAsync 執(zhí)行綁定事件

const hook1 = new SyncHook(["arg1", "arg2", "arg3"]);

//綁定事件到webapck事件流
hook1.tap("hook1", (arg1, arg2, arg3) => console.log(arg1, arg2, arg3)) //1,2,3

//執(zhí)行綁定的事件
hook1.call(1,2,3)

舉個(gè)例子

定義一個(gè)Car方法,在內(nèi)部hooks上新建鉤子。分別是同步鉤子 accelerateaccelerate接受一個(gè)參數(shù))、break、異步鉤子calculateRoutes

使用鉤子對(duì)應(yīng)的綁定和執(zhí)行方法

calculateRoutes使用tapPromise可以返回一個(gè)promise對(duì)象。

//引入tapable
const { SyncHook, AsyncParallelHook } = require("tapable");

//創(chuàng)建類
class Car {
    constructor() {
        this.hooks = {
            accelerate: new SyncHook(["newSpeed"]),
            break: new SyncHook(),
            calculateRoutes: new AsyncParallelHook(["source", "target", "routesList"])
        };
    }
}

const myCar = new Car();

//綁定同步鉤子
myCar.hooks.break.tap("WarningLampPlugin", () => console.log("WarningLampPlugin"));

//綁定同步鉤子 并傳參
myCar.hooks.accelerate.tap("LoggerPlugin", newSpeed => console.log(`Accelerating to ${newSpeed}`));

//綁定一個(gè)異步Promise鉤子
myCar.hooks.calculateRoutes.tapPromise("calculateRoutes tapPromise", (source, target, routesList, callback) => {
    // return a promise
    return new Promise((resolve,reject)=>{
        setTimeout(()=>{
            console.log(`tapPromise to ${source} ${target} ${routesList}`)
            resolve();
        },1000)
    })
});

//執(zhí)行同步鉤子
myCar.hooks.break.call();
myCar.hooks.accelerate.call("hello");

console.time("cost");

//執(zhí)行異步鉤子
myCar.hooks.calculateRoutes.promise("i", "love", "tapable").then(() => {
    console.timeEnd("cost");
}, err => {
    console.error(err);
    console.timeEnd("cost");
})

運(yùn)行結(jié)果

WarningLampPlugin
Accelerating to hello
tapPromise to i love tapable
cost: 1008.725ms

calculateRoutes也可以使用tapAsync綁定鉤子,注意:此時(shí)用callback結(jié)束異步回調(diào)。

myCar.hooks.calculateRoutes.tapAsync("calculateRoutes tapAsync", (source, target, routesList, callback) => {
    // return a promise
    setTimeout(() => {
        console.log(`tapAsync to ${source} ${target} ${routesList}`)
        callback();
    }, 2000)
});

myCar.hooks.calculateRoutes.callAsync("i", "like", "tapable", err => {
    console.timeEnd("cost");
    if(err) console.log(err)
})

運(yùn)行結(jié)果

WarningLampPlugin
Accelerating to hello
tapAsync to i like tapable
cost: 2007.045ms

進(jìn)階一下~
到這里可能已經(jīng)學(xué)會(huì)使用tapable了,但是它如何與webapck/webpack插件關(guān)聯(lián)呢?
我們將剛才的代碼稍作改動(dòng),拆成兩個(gè)文件:Compiler.jsMyplugin.js

Compiler.js

Class Car類名改成webpack的核心Compiler

接受options里傳入的plugins

Compiler作為參數(shù)傳給plugin

執(zhí)行run函數(shù),在編譯的每個(gè)階段,都觸發(fā)執(zhí)行相對(duì)應(yīng)的鉤子函數(shù)。

const {
    SyncHook,
    AsyncParallelHook
} = require("tapable");

class Compiler {
    constructor(options) {
        this.hooks = {
            accelerate: new SyncHook(["newSpeed"]),
            break: new SyncHook(),
            calculateRoutes: new AsyncParallelHook(["source", "target", "routesList"])
        };
        let plugins = options.plugins;
        if (plugins && plugins.length > 0) {
            plugins.forEach(plugin => plugin.apply(this));
        }
    }
    run(){
        console.time("cost");
        this.accelerate("hello")
        this.break()
        this.calculateRoutes("i", "like", "tapable")
    }
    accelerate(param){
        this.hooks.accelerate.call(param);
    }
    break(){
        this.hooks.break.call();
    }
    calculateRoutes(){
        const args = Array.from(arguments)
        this.hooks.calculateRoutes.callAsync(...args, err => {
            console.timeEnd("cost");
            if (err) console.log(err)
        });
    }
}

module.exports = Compiler

MyPlugin.js

引入Compiler

定義一個(gè)自己的插件。

apply方法接受 compiler參數(shù)。

compiler上的鉤子綁定方法。

仿照webpack規(guī)則,向 plugins 屬性傳入 new 實(shí)例。

webpack 插件是一個(gè)具有 apply 方法的 JavaScript 對(duì)象。apply 屬性會(huì)被 webpack compiler 調(diào)用,并且 compiler 對(duì)象可在整個(gè)編譯生命周期訪問(wèn)。
const Compiler = require("./Compiler")

class MyPlugin{
    constructor() {

    }
    apply(conpiler){//接受 compiler參數(shù)
        conpiler.hooks.break.tap("WarningLampPlugin", () => console.log("WarningLampPlugin"));
        conpiler.hooks.accelerate.tap("LoggerPlugin", newSpeed => console.log(`Accelerating to ${newSpeed}`));
        conpiler.hooks.calculateRoutes.tapAsync("calculateRoutes tapAsync", (source, target, routesList, callback) => {
            setTimeout(() => {
                console.log(`tapAsync to ${source}${target}${routesList}`)
                callback();
            }, 2000)
        });
    }
}


//這里類似于webpack.config.js的plugins配置
//向 plugins 屬性傳入 new 實(shí)例

const myPlugin = new MyPlugin();

const options = {
    plugins: [myPlugin]
}
let compiler = new Compiler(options)
compiler.run()

運(yùn)行結(jié)果

Accelerating to hello
WarningLampPlugin
tapAsync to iliketapable
cost: 2009.273ms

改造后運(yùn)行正常,仿照Compiler和webpack插件的思路慢慢得理順插件的邏輯成功。
更多其他Tabable方法

Plugin基礎(chǔ)

Webpack 通過(guò) Plugin 機(jī)制讓其更加靈活,以適應(yīng)各種應(yīng)用場(chǎng)景。 在 Webpack 運(yùn)行的生命周期中會(huì)廣播出許多事件,Plugin 可以監(jiān)聽這些事件,在合適的時(shí)機(jī)通過(guò) Webpack 提供的 API 改變輸出結(jié)果。

一個(gè)最基礎(chǔ)的 Plugin 的代碼是這樣的:

class BasicPlugin{
  // 在構(gòu)造函數(shù)中獲取用戶給該插件傳入的配置
  constructor(options){
  }

  // Webpack 會(huì)調(diào)用 BasicPlugin 實(shí)例的 apply 方法給插件實(shí)例傳入 compiler 對(duì)象
  apply(compiler){
    compiler.hooks.compilation.tap("BasicPlugin", compilation => {
     
    });
  }
}

// 導(dǎo)出 Plugin
module.exports = BasicPlugin;

在使用這個(gè) Plugin 時(shí),相關(guān)配置代碼如下:

const BasicPlugin = require("./BasicPlugin.js");
module.export = {
  plugins:[
    new BasicPlugin(options),
  ]
}

Compiler 和 Compilation
在開發(fā) Plugin 時(shí)最常用的兩個(gè)對(duì)象就是 Compiler Compilation,它們是 Plugin Webpack 之間的橋梁。 CompilerCompilation 的含義如下:

Compiler 對(duì)象包含了 Webpack 環(huán)境所有的的配置信息,包含 options,loaders,plugins 這些信息,這個(gè)對(duì)象在 Webpack 啟動(dòng)時(shí)候被實(shí)例化,它是全局唯一的,可以簡(jiǎn)單地把它理解為 Webpack 實(shí)例;

Compilation 對(duì)象包含了當(dāng)前的模塊資源、編譯生成資源、變化的文件等。當(dāng) Webpack 以開發(fā)模式運(yùn)行時(shí),每當(dāng)檢測(cè)到一個(gè)文件變化,一次新的 Compilation 將被創(chuàng)建。Compilation 對(duì)象也提供了很多事件回調(diào)供插件做擴(kuò)展。通過(guò) Compilation 也能讀取到 Compiler 對(duì)象。

Compiler 和 Compilation 的區(qū)別在于:Compiler 代表了整個(gè) Webpack 從啟動(dòng)到關(guān)閉的生命周期,而 Compilation 只是代表了一次新的編譯。

常用 API

插件可以用來(lái)修改輸出文件、增加輸出文件、甚至可以提升 Webpack 性能、等等,總之插件通過(guò)調(diào)用 Webpack 提供的 API 能完成很多事情。 由于 Webpack 提供的 API 非常多,有很多 API 很少用的上,又加上篇幅有限,下面來(lái)介紹一些常用的 API。

1、讀取輸出資源、代碼塊、模塊及其依賴

有些插件可能需要讀取 Webpack 的處理結(jié)果,例如輸出資源、代碼塊、模塊及其依賴,以便做下一步處理。

在 emit 事件發(fā)生時(shí),代表源文件的轉(zhuǎn)換和組裝已經(jīng)完成,在這里可以讀取到最終將輸出的資源、代碼塊、模塊及其依賴,并且可以修改輸出資源的內(nèi)容。 插件代碼如下:

class MyPlugin {
  apply(compiler) {

    compiler.hooks.emit.tabAsync("MyPlugin", (compilation, callback) => {
      // compilation.chunks 存放所有代碼塊,是一個(gè)數(shù)組
      compilation.chunks.forEach(function (chunk) {
        // chunk 代表一個(gè)代碼塊
        // 代碼塊由多個(gè)模塊組成,通過(guò) chunk.forEachModule 能讀取組成代碼塊的每個(gè)模塊
        chunk.forEachModule(function (module) {
          // module 代表一個(gè)模塊
          // module.fileDependencies 存放當(dāng)前模塊的所有依賴的文件路徑,是一個(gè)數(shù)組
          module.fileDependencies.forEach(function (filepath) {
          });
        });

        // Webpack 會(huì)根據(jù) Chunk 去生成輸出的文件資源,每個(gè) Chunk 都對(duì)應(yīng)一個(gè)及其以上的輸出文件
        // 例如在 Chunk 中包含了 CSS 模塊并且使用了 ExtractTextPlugin 時(shí),
        // 該 Chunk 就會(huì)生成 .js 和 .css 兩個(gè)文件
        chunk.files.forEach(function (filename) {
          // compilation.assets 存放當(dāng)前所有即將輸出的資源
          // 調(diào)用一個(gè)輸出資源的 source() 方法能獲取到輸出資源的內(nèi)容
          let source = compilation.assets[filename].source();
        });
      });

      // 這是一個(gè)異步事件,要記得調(diào)用 callback 通知 Webpack 本次事件監(jiān)聽處理結(jié)束。
      // 如果忘記了調(diào)用 callback,Webpack 將一直卡在這里而不會(huì)往后執(zhí)行。
      callback();
    })

  }
}

2、監(jiān)聽文件變化

Webpack 會(huì)從配置的入口模塊出發(fā),依次找出所有的依賴模塊,當(dāng)入口模塊或者其依賴的模塊發(fā)生變化時(shí), 就會(huì)觸發(fā)一次新的 Compilation。

在開發(fā)插件時(shí)經(jīng)常需要知道是哪個(gè)文件發(fā)生變化導(dǎo)致了新的 Compilation,為此可以使用如下代碼:

// 當(dāng)依賴的文件發(fā)生變化時(shí)會(huì)觸發(fā) watch-run 事件
compiler.hooks.watchRun.tap("MyPlugin", (watching, callback) => {
  // 獲取發(fā)生變化的文件列表
  const changedFiles = watching.compiler.watchFileSystem.watcher.mtimes;
  // changedFiles 格式為鍵值對(duì),鍵為發(fā)生變化的文件路徑。
  if (changedFiles[filePath] !== undefined) {
    // filePath 對(duì)應(yīng)的文件發(fā)生了變化
  }
  callback();
});

默認(rèn)情況下 Webpack 只會(huì)監(jiān)視入口和其依賴的模塊是否發(fā)生變化,在有些情況下項(xiàng)目可能需要引入新的文件,例如引入一個(gè) HTML 文件。 由于 JavaScript 文件不會(huì)去導(dǎo)入 HTML 文件,Webpack 就不會(huì)監(jiān)聽 HTML 文件的變化,編輯 HTML 文件時(shí)就不會(huì)重新觸發(fā)新的 Compilation。 為了監(jiān)聽 HTML 文件的變化,我們需要把 HTML 文件加入到依賴列表中,為此可以使用如下代碼:

compiler.hooks.afterCompile.tap("MyPlugin", (compilation, callback) => {
  // 把 HTML 文件添加到文件依賴列表,好讓 Webpack 去監(jiān)聽 HTML 模塊文件,在 HTML 模版文件發(fā)生變化時(shí)重新啟動(dòng)一次編譯
  compilation.fileDependencies.push(filePath);
  callback();
});

3、修改輸出資源
有些場(chǎng)景下插件需要修改、增加、刪除輸出的資源,要做到這點(diǎn)需要監(jiān)聽 emit 事件,因?yàn)榘l(fā)生 emit 事件時(shí)所有模塊的轉(zhuǎn)換和代碼塊對(duì)應(yīng)的文件已經(jīng)生成好, 需要輸出的資源即將輸出,因此 emit 事件是修改 Webpack 輸出資源的最后時(shí)機(jī)。

所有需要輸出的資源會(huì)存放在 compilation.assets 中,compilation.assets 是一個(gè)鍵值對(duì),鍵為需要輸出的文件名稱,值為文件對(duì)應(yīng)的內(nèi)容。

設(shè)置 compilation.assets 的代碼如下:

// 設(shè)置名稱為 fileName 的輸出資源
  compilation.assets[fileName] = {
    // 返回文件內(nèi)容
    source: () => {
      // fileContent 既可以是代表文本文件的字符串,也可以是代表二進(jìn)制文件的 Buffer
      return fileContent;
      },
    // 返回文件大小
      size: () => {
      return Buffer.byteLength(fileContent, "utf8");
    }
  };
  callback();

讀取 compilation.assets 的代碼如下:

  // 讀取名稱為 fileName 的輸出資源
  const asset = compilation.assets[fileName];
  // 獲取輸出資源的內(nèi)容
  asset.source();
  // 獲取輸出資源的文件大小
  asset.size();
  callback();
實(shí)戰(zhàn)!寫一個(gè)插件

怎么寫一個(gè)插件?參照webpack官方教程Writing a Plugin。 一個(gè)webpack plugin由一下幾個(gè)步驟組成:

一個(gè)JavaScript類函數(shù)。

在函數(shù)原型 (prototype)中定義一個(gè)注入compiler對(duì)象的apply方法。

apply函數(shù)中通過(guò)compiler插入指定的事件鉤子,在鉤子回調(diào)中拿到compilation對(duì)象

使用compilation操縱修改webapack內(nèi)部實(shí)例數(shù)據(jù)。

異步插件,數(shù)據(jù)處理完后使用callback回調(diào)

下面我們舉一個(gè)實(shí)際的例子,帶你一步步去實(shí)現(xiàn)一個(gè)插件。
該插件的名稱取名叫 EndWebpackPlugin,作用是在 Webpack 即將退出時(shí)再附加一些額外的操作,例如在 Webpack 成功編譯和輸出了文件后執(zhí)行發(fā)布操作把輸出的文件上傳到服務(wù)器。 同時(shí)該插件還能區(qū)分 Webpack 構(gòu)建是否執(zhí)行成功。使用該插件時(shí)方法如下:

module.exports = {
  plugins:[
    // 在初始化 EndWebpackPlugin 時(shí)傳入了兩個(gè)參數(shù),分別是在成功時(shí)的回調(diào)函數(shù)和失敗時(shí)的回調(diào)函數(shù);
    new EndWebpackPlugin(() => {
      // Webpack 構(gòu)建成功,并且文件輸出了后會(huì)執(zhí)行到這里,在這里可以做發(fā)布文件操作
    }, (err) => {
      // Webpack 構(gòu)建失敗,err 是導(dǎo)致錯(cuò)誤的原因
      console.error(err);        
    })
  ]
}

要實(shí)現(xiàn)該插件,需要借助兩個(gè)事件:

done:在成功構(gòu)建并且輸出了文件后,Webpack 即將退出時(shí)發(fā)生;

failed:在構(gòu)建出現(xiàn)異常導(dǎo)致構(gòu)建失敗,Webpack 即將退出時(shí)發(fā)生;

實(shí)現(xiàn)該插件非常簡(jiǎn)單,完整代碼如下:

class EndWebpackPlugin {

  constructor(doneCallback, failCallback) {
    // 存下在構(gòu)造函數(shù)中傳入的回調(diào)函數(shù)
    this.doneCallback = doneCallback;
    this.failCallback = failCallback;
  }

  apply(compiler) {
    compiler.hooks.done.tab("EndWebpackPlugin", (stats) => {
      // 在 done 事件中回調(diào) doneCallback
      this.doneCallback(stats);
    });
    compiler.hooks.failed.tab("EndWebpackPlugin", (err) => {
      // 在 failed 事件中回調(diào) failCallback
      this.failCallback(err);
    });
  }
}
// 導(dǎo)出插件
module.exports = EndWebpackPlugin;

從開發(fā)這個(gè)插件可以看出,找到合適的事件點(diǎn)去完成功能在開發(fā)插件時(shí)顯得尤為重要。 在 工作原理概括 中詳細(xì)介紹過(guò) Webpack 在運(yùn)行過(guò)程中廣播出常用事件,你可以從中找到你需要的事件。

參考
tapable
compiler-hooks
Compilation Hooks
writing-a-plugin
深入淺出 Webpack
干貨!擼一個(gè)webpack插件

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://m.specialneedsforspecialkids.com/yun/103948.html

相關(guān)文章

  • 9102年:手寫一個(gè)React腳手架 【優(yōu)化極致版】

    摘要:馬上要出了,完全手寫一個(gè)優(yōu)化后的腳手架是不可或缺的技能。每個(gè)依賴項(xiàng)隨即被處理,最后輸出到稱之為的文件中,我們將在下一章節(jié)詳細(xì)討論這個(gè)過(guò)程。的事件流機(jī)制保證了插件的有序性,使得整個(gè)系統(tǒng)擴(kuò)展性很好。 webpack馬上要出5了,完全手寫一個(gè)優(yōu)化后的腳手架是不可或缺的技能。 本文書寫時(shí)間 2019年5月9日 , webpack版本 4.30.0最新版本 本人所有代碼均手寫,親自試驗(yàn)過(guò)可...

    Kylin_Mountain 評(píng)論0 收藏0
  • 9102年:手寫一個(gè)React腳手架 【優(yōu)化極致版】

    摘要:馬上要出了,完全手寫一個(gè)優(yōu)化后的腳手架是不可或缺的技能。每個(gè)依賴項(xiàng)隨即被處理,最后輸出到稱之為的文件中,我們將在下一章節(jié)詳細(xì)討論這個(gè)過(guò)程。的事件流機(jī)制保證了插件的有序性,使得整個(gè)系統(tǒng)擴(kuò)展性很好。 webpack馬上要出5了,完全手寫一個(gè)優(yōu)化后的腳手架是不可或缺的技能。 本文書寫時(shí)間 2019年5月9日 , webpack版本 4.30.0最新版本 本人所有代碼均手寫,親自試驗(yàn)過(guò)可...

    whatsns 評(píng)論0 收藏0
  • 9102年:手寫一個(gè)React腳手架 【優(yōu)化極致版】

    摘要:馬上要出了,完全手寫一個(gè)優(yōu)化后的腳手架是不可或缺的技能。每個(gè)依賴項(xiàng)隨即被處理,最后輸出到稱之為的文件中,我們將在下一章節(jié)詳細(xì)討論這個(gè)過(guò)程。的事件流機(jī)制保證了插件的有序性,使得整個(gè)系統(tǒng)擴(kuò)展性很好。 webpack馬上要出5了,完全手寫一個(gè)優(yōu)化后的腳手架是不可或缺的技能。 本文書寫時(shí)間 2019年5月9日 , webpack版本 4.30.0最新版本 本人所有代碼均手寫,親自試驗(yàn)過(guò)可...

    bingo 評(píng)論0 收藏0
  • 理解webpack原理,手寫一個(gè)100行的webpack

    摘要:什么是可以引用官網(wǎng)的一幅圖解釋,我們可以看到,可以分析各個(gè)模塊的依賴關(guān)系,最終打包成我們常見(jiàn)的靜態(tài)文件,。我們暫時(shí)把通過(guò)傳文件路徑能返回文件信息的這個(gè)函數(shù)叫。 什么是webpack 可以引用官網(wǎng)的一幅圖解釋,我們可以看到webpack,可以分析各個(gè)模塊的依賴關(guān)系,最終打包成我們常見(jiàn)的靜態(tài)文件,.js 、 .css 、 .jpg 、.png。今天我們先不弄那么復(fù)雜,我們就介紹webpac...

    王偉廷 評(píng)論0 收藏0
  • Webpack入門到精通(1)

    前言 什么是webpack 本質(zhì)上,webpack 是一個(gè)現(xiàn)代 JavaScript 應(yīng)用程序的靜態(tài)模塊打包器(module bundler)。當(dāng) webpack 處理應(yīng)用程序時(shí),它會(huì)遞歸地構(gòu)建一個(gè)依賴關(guān)系圖(dependency graph),其中包含應(yīng)用程序需要的每個(gè)模塊,然后將所有這些模塊打包成一個(gè)或多個(gè) bundle。webpack 有哪些功能(代碼轉(zhuǎn)換 文件優(yōu)化 代碼分割 模塊合并 自...

    wangbinke 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<