1.はじめに
プラグイン(プラグイン)があるwebpack
ファーストクラスの英雄。多くのウィジェットが行うがあるからだwebpack
全能。ではwebpack
、ソースコードも内蔵プラグイン、プラグインの多くを使用して、よく使用する場合は、より少ない効率的に作業を行うことができます。とてもクールプラグと他の誰かによって書かれたので、なぜ私たちは、独自のプラグインを書く考慮しませんか?この記事では、どのように独自の記述方法をお教えしますwebpack
プラグイン。
2. WebPACKの操作機構
プラグインを書く前に、私たちは最初に見る必要がありwebpack
、全体的な操作機構。Webpack
操作機構の性質上のイベントの流れのための機構でwebpack
操作、初期化パラメータ、ファイルをコンパイル開始、エントリを決定する、モジュールをコンパイルし、コンパイルされ、リソースを生成し、エクスポートリソース、等、一連の処理を完了し、中各ノードのこの全体のプロセスは、webpack
我々はによって適切なタイミングで、各イベントのために聞くことができ、ブロードキャストイベント外となりますwebpack
で提供API
正しいことを行います。
説明を理解しやすいです。
あなたは上記のテキストを理解していない場合は、ユーザーフレンドリーなプラスの私たちの例を説明するために、次のように:
私たちは、webpack
コックを比較し、入れてwebpack
揚げた料理、シルクと呼ば辛くて酸っぱいポテト料理を調理するために比較して全体の演算処理を。野菜- -クッキング-料理野菜:まあ、皿から皿に次の手順の合計を通過する程度の良い一品を行う準備ができています。そして、次のプロセスのそれぞれは、この手順の名前から彼の大きな叫びましょうときに我々は料理が上手、シェフと合意した、それは、そのような野菜が起動するときに料理として、ブロードキャストイベントを調理することで、シェフが叫んだてみましょう: 「野菜。」他の要件が存在しない場合は、上記の4つのステップを通過し、このチャネル酸っぱいジャガイモのワイヤーは、後に行うことができます。しかし、我々は辛くて酸っぱいポテトのシルクロードにもっと辛いのいくつかを聞かせすることがある追加の要件を、持っています。元シェフが行うには、通常の手順に従いますし、それを行うにはどのようにして、余分なコショウを追加していないのだろうか?この時、私たちのプラグインがそれをデビュー、我々は「プラスペッパー」プラグインを作成する必要があります。これを書く前に、私たちは私たちがどのような手順を監視することを、どのような時にプラス唐辛子適切に考慮する必要がある、料理に追加する場合、ことは明らかです。その後、我々は、我々は唐辛子を入れたとき、すなわち「料理」を叫んで、このイベントのシェフ放送後に、このプロセスを「調理する」聞く最後はそれより辛い辛くて酸っぱいジャガイモのワイヤーは、いくつかのものであろうを行うためになるように、追加。
要約すると:
Webpack
生産ラインと同様に、処理の一連のプロセスを経て出力するソースファイルを変換します。
このプロセスの職務の各々は、単一の生産ラインであり、複数のプロセス間の依存関係が存在し、そして現在のプロセスに対処するために、次の工程にハンドオーバすることができる後にのみ完了しました。
機能生産ラインに挿入されたプラグのように、特定の時間に生産ライン上の処理リソースを行います。
Webpack
Tapableこの複雑な生産ラインを整理します。
Webpack
それだけで興味のあるイベントに耳を傾ける必要が差し込み、動作中のイベントをブロードキャストします、生産ラインの動作を変更するには、この生産ラインに追加することができます。
Webpack
イベントフローの仕組みがよく、システム全体のスケーラビリティを作り、秩序のプラグインを確認します。
OK、今私はあなたがしなければならないと信じているwebpack
メカニズムは、予備的な理解を持って実行します。次に、我々は徐々にプラグインを書き始めました。
3.プラグインの作成
私たちはしばしば、我々が通常使用する方法として使用されることを知っている誰かのプラグイン:
module.exports = {
entry:'',
output:{},
module: {},
plugins: [
new XXXPlugin(options)
]
}
我々は、頻繁に使用される上記のコードから見ることができる場合、プラグnew XXXPlugin()
、プラグイン、それがクラスであるすなわち、プラグインの使用であるnew
プラグ、プラグおよびクラスのコンストラクタに必要な設定パラメータの一例機能。次のようにその後、我々は、最初のステップで書かれたプラグインを書くことができます。
class XXXPlugin{
// 在构造函数中获取用户给该插件传入的配置
constructor(options) {
this.options=options;
}
}
// 导出 Plugin
module.exports=XXXPlugin;
その後、読み取ることによって、webpack
ソースコードのwebpack.jsを:46行を私たちは知っています:
if (options.plugins && Array.isArray(options.plugins)) {
for (const plugin of options.plugins) {
if (typeof plugin === "function") {
plugin.call(compiler, compiler);
} else {
plugin.apply(compiler);
}
}
}
ときにwebpack
内部コールプラグインプラグインのインスタンスを呼び出す場合apply
の方法、及びその着信compiler
オブジェクト、OK、クラス内のプラグインを追加するために、来るようにプラグインを書き込む第2ステップapply
の方法を。
class XXXPlugin{
// 在构造函数中获取用户给该插件传入的配置
constructor(options) {
this.options=options;
}
// Webpack 会调用 XXXPlugin 实例的 apply 方法给插件实例传入 compiler 对象
apply(compiler) {
}
}
// 导出 Plugin
module.exports=XXXPlugin;
これはcomplier
、その後、何ですか?ここでは詳しく説明する必要があります。
開発にはPlugin
2つのオブジェクトが最も一般的であるとき、Compiler
そしてCompilation
それらがから継承されTapable
ているPlugin
とWebpack
の間のブリッジ。
Compiler
そして、Compilation
次のような意味があります:
Compiler
オブジェクトは完全表しwebpack
環境構成を。このオブジェクトは、起動webpack
を含め、すべての動作設定を確立し、設定する使い捨てであることoptions
、loader
及びplugin
。ときにwebpack
プラグイン環境を適用する、プラグインは、この受信するCompiler
基準オブジェクト。あなたはアクセスするためにそれを使用することができwebpack
、メイン環境を。Compilation
オブジェクトは、バージョン建設リソースを表します。実行している場合はwebpack
、開発環境のミドルウェアを、ファイルの変更が検出されるたびに、それは新しい作成されますCompilation
コンパイル、リソースの新しいセットを生成するために。Compilation
現在のリソースモジュールの目標性能は、コンパイルされたリソースは、ファイルを変更し、依存ステータス情報を追跡しました。Compilation
オブジェクトはまた、重要なタイミング補正の数を提供し、処理するためのカスタムプラグインを実行するときに使用することを選択しました。
Compiler
そして、Compilation
の違いは次のとおりです。Compiler
全体の代表Webpack
開始からは、ライフサイクルを閉じますが、するCompilation
だけで、新しいコンパイルを表します。
使用Compiler
あなたがアクセスできるオブジェクトをwebpack
メインの環境を、あなたが聞くことができるwebpack
放送のうち実行中の一連のイベント。OK、ここに私たちは徹底的に理解し、元のコア部分は、プラグインであるapply
方法、リスニングすることで、この中の方法webpack
とのうち、右側のタイムイベント放送で正しいことをやって。この時点で、あなたも疑問を持っていることがあります。私たちは、どのようなイベントを監視しますか?またはWebPACKのは、どのイベントプロセスを通じて放送されていますか?次に、私たちは、ソースコードを読む必要があるwebpack
イベントが放送されている実行全体のアウトを参照してください。
4. WebPACKのワークフロー
Webpack
ワークフローはシリアルプロセスで、次の手順では、最初から最後まで続きます。
- 初期化パラメータ:設定ファイルからと
Shell
連結損益計算書のパラメータを読み込み、最後のパラメータを得ます。 - コンパイルを開始します取得するには、パラメータの初期化ステップを過ごす
Compiler
、すべてのプラグインの設定をロード、オブジェクトの実装オブジェクトのrun
コンパイルを開始する方法を。 - OKエントランス:構成によれば
entry
、すべてのエントリ文書を識別するために、 - モジュールをコンパイルします。入り口ファイルから、コンパイルするすべての設定されたローダーモジュールに呼びかけ、その後、モジュールに依存するモジュールを検索して、再帰的にすべてのファイルまでこのステップは、このステップでの処理の入り口に依存していました。
- 完了モジュールのコンパイル:使用してステップ4の後
Loader
、すべての翻訳モジュールの後、最終的なコンテンツの後に得られた各モジュールが翻訳され、それらの間の依存関係。 - 出力リソースが:入口と、モジュール間の依存関係が複数のモジュールを含む1つに組み立てられ
Chunk
、その後の各Chunk
単一のファイルに切り替えるための最後の機会を出力リストに追加され、このステップは、出力内容に変更することができます。 - 出力完了:ウェル出力の内容を決定した後、パスとファイル名の出力を決定するための構成によれば、ファイルの内容は、ファイルシステムに書き込まれます。
上記プロセスにおいて、Webpack
関心のあるイベントをプラグインを聞いた後に、特定のロジックを実行し、プラグインを呼び出すことができ、特定の時点における特定のイベントが放送されるWebpack
設けAPI
変更Webpack
操作結果。
初期化フェーズ、コンパイル段、出力段:イベントの放送による3つの段階に大別することができます。次のように3つの段階に発生したいくつかの一般的なイベント。
4.1初期化フェーズ
イベント名 | 説明 |
---|---|
初期化パラメータ | コンフィギュレーションファイルからパラメータを読み、シェルステートメントをマージし、最終的なパラメータを取得します。 |
インスタンス化コンパイラ | ファイルの責任2.Compilerステップ例で得られた1支出コンパイラの初期化パラメータと3.Compilerインスタンスを聴き始める完全なコンパイルのWebPACK構成、唯一のグローバルコンパイラのインスタンスが含まれています。 |
読み込みプラグイン | プラグは、すべてのイベントの後続ノードを監視できるように順番に1プラグは、適用方法を呼び出します。コンパイラプラグインのインスタンスを渡すと同時に、コンパイラが提供するプラグインAPIのWebPACKの呼び出しを容易にするために、引用しました。 |
環境 | 以下のための後続の検索を容易にし、文書を読むことをコンパイラオブジェクトにNode.jsの形式のファイルシステムを使うようになりました |
エントリー・オプション | 対応するエントリEntryPluginの各インスタンス用に構成さEntrysを読み、エントリの後仕事の準備をする再帰解像度 |
後のプラグイン | メソッド呼び出しは、すべての設定を内蔵しており、プラグインを終え適用 |
アフターリゾルバ | 設定の初期化リゾルバによっては、リゾルバは、ファイル・システム・ファイルに指定されたパスを見つけるための責任があります |
4.2コンパイル相
イベント名 | 説明 |
---|---|
ラン | 新しいコンパイルを開始します |
-見て実行します | そして、それはリスニングモードでコンパイルを開始していることを、あなたは新しいコンパイルを再起動する大手のファイルが変更されたこのイベントに取得することができますを除いて、同様の実行 |
コンパイル | イベントが開始するために、新しいプラグインにコンパイラに伝えることですが、コンパイラプラグインのオブジェクトを渡します。 |
編集 | WebPACKのが開発モードで実行している場合場合は、すべての時間は、ファイルの変更を検出し、新しいコンパイルが作成されます。コンパイルオブジェクトは、現在のモジュールのリソース、コンパイルされたリソース、ファイルの変更が含まれています。コンパイルオブジェクトは、プラグインのコールバックが拡大しない多くのイベントを提供します。 |
作ります | コンパイルは、新たなマスターがコンパイルし始め作成されています |
アフターコンパイル | 実装は、コンパイルが完了したら |
無効 | 存在しないファイルに直面したときに、ファイルのコンパイルエラーは、この異常なイベントをトリガすると、イベントはWebPACKの出口が発生することはありません |
4.3出力段
イベント名 | 説明 |
---|---|
シール | 変換によってすべてのモジュールとその依存関係ローダーモジュールが完了すると、依存関係のチャンクの生成を開始 |
Addcudak | リソースの生成 |
createChunkAssets | リソースの作成 |
getRenderManifest | レンダリングされるファイルの説明 |
与えます | ソースの描画 |
afterCompile | エンドをコンパイルします |
shouldEmit | 必要な出力は良い生成されたすべてのファイルは、出力を接続する必要があり、そうでないどのファイル尋ねます。 |
発します | 良い出力するファイルを決定した後、あなたがアクセスして出力を変更することができ、ファイル出力を行います。 |
emitRecords | 書かれたレコード |
完了 | フル・コンパイルと出力のプロセスが正常に完了 |
failed | 如果在编译和输出的流程中遇到异常,导致Webpack 退出, 就会直接跳转到本步骤,插件可以在本事件中获取具体的错误原因 |
更多的事件钩子请求查看这里:Compiler对象事件、Compilation对象事件。
5. 继续编写插件
知道了广播了哪些事件之后,我们就可以在apply
中监听事件并编写相应的逻辑啦。如下:
class XXXPlugin{
// 在构造函数中获取用户给该插件传入的配置
constructor(options) {
this.options=options;
}
// Webpack 会调用 XXXPlugin 实例的 apply 方法给插件实例传入 compiler 对象
apply(compiler) {
// 监听某个事件
compiler.hooks.'compiler事件名称'.tap('XXXPlugin', (compilation)=> {
//编写相应逻辑
});
}
}
// 导出 Plugin
module.exports=XXXPlugin;
当我们监听Compiler
对象事件中的compilation
事件时,此时可以在回调函数中再次监听compilation
对象里的事件,如下:
class XXXPlugin{
// 在构造函数中获取用户给该插件传入的配置
constructor(options) {
this.options=options;
}
// Webpack 会调用 XXXPlugin 实例的 apply 方法给插件实例传入 compiler 对象
apply(compiler) {
// 监听某个事件
compiler.hooks.compilation.tap('XXXPlugin', (compilation)=> {
compilation.hooks.'compilation事件名称'.tap('optimize', () => {
//编写相应逻辑
});
});
}
}
// 导出 Plugin
module.exports=XXXPlugin;
5.1 异步插件
有一些事件使用了异步钩子AsyncHook
,此时在监听这些事件的时候我们就需要使用tapAsync
或 tapPromise
,并且还需要额外传入一个 callback
回调函数,在插件运行结束时,必须调用这个回调函数,如下:
class XXXPlugin{
// 在构造函数中获取用户给该插件传入的配置
constructor(options) {
this.options=options;
}
// Webpack 会调用 XXXPlugin 实例的 apply 方法给插件实例传入 compiler 对象
apply(compiler) {
// 监听某个事件
compiler.hooks.emit.tapAsync('XXXPlugin', (compilation,callback)=> {
setTimeout(function () {
console.log('异步任务完成');
callback();
},500);
});
}
}
// 导出 Plugin
module.exports=XXXPlugin;
OK,以上就是编写一个webpack
插件所有需要用到的东西,接下里我们就来编写一个简单的插件做个示例。
6. 插件示例
在这里我们编写一个简单的插件,该插件的功能是:将webpack
输出的所有文件打包压缩成一个zip
压缩包,该插件接收一个配置参数filename
,表示最终生成的压缩包文件名称。其代码如下:
定义ZipPlugin
插件:
const { RawSource } = require("webpack-sources");
const JSZip = require("jszip");
class ZipPlugin {
constructor(options) {
this.options = options;
}
apply(compiler) {
compiler.hooks.emit.tapAsync("ZipPlugin", (compilation, callback) => {
var zip = new JSZip();
for (let filename in compilation.assets) {
const source = compilation.assets[filename].source();
zip.file(filename, source);
}
zip.generateAsync({ type: "nodebuffer" }).then(content => {
compilation.assets[this.options.filename || 'fileList.zip'] = new RawSource(content);
callback();
});
});
}
}
module.exports = ZipPlugin;
代码说明:
- 首先获取到用户传入的配置对象,将其存入
this.options
内。 - 我们要想在
webpack
输出完成后将输出的所有文件打包成zip
压缩包,那么就必须监听webpack
输出完成后的那个事件,即emit
事件。并且该事件为异步事件,所以使用tabAsync
监听。 - 通过
webpack
的输出资源对象compilation.assets
获取到输出的所有文件,遍历这些文件,使用JSZip
包将这些文件都放入一个zip
压缩包内。 - 然后将这个压缩包挂载到
webpack
的输出资源对象compilation.assets
上,使其跟随输出资源一同被输出,该压缩包的文件名使用用户传入的filename
,如果用户未传入则使用默认的名字fileList.zip
。 - 最后由于是异步事件,在运行结束时,必须调用
callback()
使用ZipPlugin
插件:
const ZipPlugin = require('./src/plugins/ZipPlugin');
module.exports = {
entry: './src/index.js',
output: {
path: path.resolve('dist'),
filename: 'bundle.js'
},
plugins: [
// 使用ZipPlugin插件
new ZipPlugin({
filename:'NLRX.zip'
})
]
}
7. 总结
那么我们可以简单总结一下,一个插件的构成部分:
- 一个插件就是一个类。
- 在插件类上定义一个
apply
方法。 - 在
apply
方法中监听一个webpack
自身的事件钩子。 - 处理
webpack
内部实例的特定数据。 - 功能完成后调用
webpack
提供的回调。
相信读完这篇文章,你应该就能写出一个自己的插件了。
(完)