天天看點

webpack系列6之深入淺出compiler和compilation

作者:web前端分享

本章概述

在本章節我會首先通過具體的執行個體向你展示我們的compilation對象具有的常用屬性,并告訴你這些屬性具體的含義。其中包含chunks,assets,modules等等。最後,我會深入的分析compiler和compilation對象具有的各種鈎子方法。通過本章節的學習,你将會了解webpack中module,chunk等重要的概念,并對webpack打包過程有一個宏觀的把握。

1.webpack的compilation常用内容

1.1 chunk的内容

為了更好的了解下面的概念,我先給大家看看webpack中所謂的chunk都包含哪些内部屬性,希望能對大家了解chunk有一定的幫助,下面展示的就是一個chunk的執行個體。

compilation.getStats().toJson().chunks
//擷取compilation所有的chunks:           

chunks所有的内容如下:

[ { id: 0,
   //chunk id
    rendered: true,
    //https://github.com/liangklfangl/commonchunkplugin-source-code
    initial: false,
    //require.ensure産生的chunk,非initial
    //initial表示是否是在頁面初始化就需要加載的子產品,而不是按需加載的子產品
    entry: false,
    //是否含有webpack的runtime環境,通過CommonChunkPlugin處理後,runtime環境被提到最高層級的chunk
    recorded: undefined,
    extraAsync: false,
    size: 296855,
    //chunk大小,比特
    names: [],
    //require.ensure不是通過webpack配置的,是以chunk的names是空
    files: [ '0.bundle.js' ],
    //該chunk産生的輸出檔案,即輸出到特定檔案路徑下的檔案名稱
    hash: '42fbfbea594ba593e76a',
    //chunk的hash,即chunkHash
    parents: [ 2 ],
    //父級chunk的id值
    origins: [ [Object] ] 
    //該chunk是如何産生的
    },
  { id: 1,
    rendered: true,
    initial: false,
    entry: false,
    recorded: undefined,
    extraAsync: false,
    size: 297181,
    names: [],
    files: [ '1.bundle.js' ],
    hash: '456d05301e4adca16986',
    parents: [ 2 ],
    origins: [ [Object] ] },
  { id: 2,
    rendered: true,
    initial: true,
    entry: false,
    recorded: undefined,
    extraAsync: false,
    size: 687,
    names: [ 'main' ],
    files: [ 'bundle.js' ],
    hash: '248029a0cfd99f46babc',
    parents: [ 3 ],
    origins: [ [Object] ] },
  { id: 3,
    rendered: true,
    initial: true,
    entry: true,
    recorded: undefined,
    extraAsync: false,
    size: 0,
    names: [ 'vendor' ],
    files: [ 'vendor.bundle.js' ],
    hash: 'fbf76c7c330eaf0de943',
    parents: [],
    origins: [] } ]           

而上面的每一個chunk還有一個origins參數,其含有的内容如下,它描述了某一個chunk是如何産生的:

{
  "loc": "", // Lines of code that generated this chunk
  "module": "(webpack)\\test\\browsertest\\lib\\index.web.js", // Path to the module
  "moduleId": 0, // The ID of the module
  "moduleIdentifier": "(webpack)\\test\\browsertest\\lib\\index.web.js", // Path to the module
  "moduleName": "./lib/index.web.js", // Relative path to the module
  "name": "main", // The name of the chunk
  "reasons": [
    // A list of the same `reasons` found in module objects
  ]
}           

如果對于chunk中某些屬性不懂的,可以點選我這裡對于webpack-common-chunk-plugin的分析。下面是給出的幾個關于chunks的例子:

例1:html-webpack-plugin中就使用到了多個chunks屬性,如names,initial等

//該chunk要被選中的條件是:有名稱,不是懶加載,在includedChunks中但是不在excludedChunks中
HtmlWebpackPlugin.prototype.filterChunks = function (chunks, includedChunks, excludedChunks) {
  return chunks.filter(function (chunk) {
    var chunkName = chunk.names[0];
    // This chunk doesn't have a name. This script can't handled it.
    //通過require.ensure産生的chunk不會被保留,names是一個數組
    if (chunkName === undefined) {
      return false;
    }
    // Skip if the chunk should be lazy loaded
    //如果是require.ensure産生的chunk直接忽略
    if (!chunk.initial) {
      return false;
    }
    // Skip if the chunks should be filtered and the given chunk was not added explicity
    //這個chunk必須在includedchunks裡面
    if (Array.isArray(includedChunks) && includedChunks.indexOf(chunkName) === -1) {
      return false;
    }
    // Skip if the chunks should be filtered and the given chunk was excluded explicity
    //這個chunk不能在excludedChunks中
    if (Array.isArray(excludedChunks) && excludedChunks.indexOf(chunkName) !== -1) {
      return false;
    }
    // Add otherwise
    return true;
  });
};           

例2:通過id對chunks進行排序

/**
 * Sorts the chunks based on the chunk id.
 *
 * @param  {Array} chunks the list of chunks to sort
 * @return {Array} The sorted list of chunks
 * entry chunk在前,兩個都是entry那麼id大的在前
 */
module.exports.id = function (chunks) {
  return chunks.sort(function orderEntryLast (a, b) {
    if (a.entry !== b.entry) {
      return b.entry ? 1 : -1;
    } else {
      return b.id - a.id;
    }
  });
};           

例3:通過chunk.parents(全部是parentId數組)來擷取拓排序

/*
  Sorts dependencies between chunks by their "parents" attribute.
  This function sorts chunks based on their dependencies with each other.
  The parent relation between chunks as generated by Webpack for each chunk
  is used to define a directed (and hopefully acyclic) graph, which is then
  topologically sorted in order to retrieve the correct order in which
  chunks need to be embedded into HTML. A directed edge in this graph is
  describing a "is parent of" relationship from a chunk to another (distinct)
  chunk. Thus topological sorting orders chunks from bottom-layer chunks to
  highest level chunks that use the lower-level chunks.

  @param {Array} chunks an array of chunks as generated by the html-webpack-plugin.
  It is assumed that each entry contains at least the properties "id"
  (containing the chunk id) and "parents" (array containing the ids of the
  parent chunks).
  @return {Array} A topologically sorted version of the input chunks
  因為最上面的通過commonchunkplugin産生的chunk具有webpack的runtime,是以排列在前面
*/
module.exports.dependency = function (chunks) {
  if (!chunks) {
    return chunks;
  }
  // We build a map (chunk-id -> chunk) for faster access during graph building.
  // 通過chunk-id -> chunk這種Map結構更加容易繪制圖
  var nodeMap = {};
  chunks.forEach(function (chunk) {
    nodeMap[chunk.id] = chunk;
  });
  // Next, we add an edge for each parent relationship into the graph
  var edges = [];
  chunks.forEach(function (chunk) {
    if (chunk.parents) {
      // Add an edge for each parent (parent -> child)
      chunk.parents.forEach(function (parentId) {
        // webpack2 chunk.parents are chunks instead of string id(s)
        var parentChunk = _.isObject(parentId) ? parentId : nodeMap[parentId];
        // If the parent chunk does not exist (e.g. because of an excluded chunk)
        // we ignore that parent
        if (parentChunk) {
          edges.push([parentChunk, chunk]);
        }
      });
    }
  });
  // We now perform a topological sorting on the input chunks and built edges
  return toposort.array(chunks, edges);
};           

通過這種方式可以把各個chunk公有的子產品排列在前面,進而提前加載,這是合理的!

1.2 assets内容

compilation.getStats().toJson().assets
// 擷取compilation所有的assets:           

assets内部結構如下:

[ { name: '0.bundle.js',
    size: 299109,
    chunks: [ 0, 3 ],
    
    chunkNames: [],
    emitted: undefined,
    isOverSizeLimit: undefined },
  { name: '1.bundle.js',
    size: 299469,
    chunks: [ 1, 3 ],
    chunkNames: [],
    emitted: undefined,
    isOverSizeLimit: undefined },
  { name: 'bundle.js',
    
    size: 968,
    
    chunks: [ 2, 3 ],
    
    chunkNames: [ 'main' ],
    
    emitted: undefined,
    
    isOverSizeLimit: undefined },
  { name: 'vendor.bundle.js',
    size: 5562,
    chunks: [ 3 ],
    chunkNames: [ 'vendor' ],
    emitted: undefined,
    isOverSizeLimit: undefined }]
           

上次看到webpack-dev-server源碼的時候看到了如何判斷該assets是否變化的判斷,其就是通過上面的emitted來判斷的:

compiler.plugin("done", function(stats) {
    this._sendStats(this.sockets, stats.toJson(clientStats));
    //clientStats表示用戶端stats要輸出的内容過濾
    this._stats = stats;
  }.bind(this));
Server.prototype._sendStats = function(sockets, stats, force) {
  if(!force &&
    stats &&
    (!stats.errors || stats.errors.length === 0) &&
    stats.assets &&
    stats.assets.every(function(asset) {
      return !asset.emitted;
      //每一個asset都是沒有emitted屬性,表示沒有發生變化。如果發生變化那麼這個assets肯定有emitted屬性。是以emitted屬性表示是否又重新生成了一遍assets資源
    })
  )
    return this.sockWrite(sockets, "still-ok");
  this.sockWrite(sockets, "hash", stats.hash);
  //正常情況下首先發送hash,然後發送ok
  if(stats.errors.length > 0)
    this.sockWrite(sockets, "errors", stats.errors);
  else if(stats.warnings.length > 0)
    this.sockWrite(sockets, "warnings", stats.warnings);
  else
  //發送hash後再發送ok
    this.sockWrite(sockets, "ok");
}           

我們下面分析下什麼是assets?其實每一個asset表示輸出到webpack輸出路徑的具體的檔案,包括的主要屬性如下,我們做下說明:

{
  "chunkNames": [], 
  // The chunks this asset contains
  //這個輸出資源包含的chunks名稱。對于圖檔的require或者require.ensure動态産生的chunk是不會有chunkNames的,但是在entry中配置的都是會有的
  "chunks": [ 10, 6 ],
   // The chunk IDs this asset contains
   //這個輸出資源包含的chunk的ID。通過require.ensure産生的chunk或者entry配置的檔案都會有該chunks數組,require圖檔不會有
  "emitted": true,
   // Indicates whether or not the asset made it to the `output` directory
   //使用這個屬性辨別這個assets是否應該輸出到output檔案夾
  "name": "10.web.js", 
  // The `output` filename
  //表示輸出的檔案名
  "size": 1058 
  // The size of the file in bytes
  //輸出的這個資源的檔案大小
}           

是以你經常會看到通過下面的方式來為compilation.assets添加内容:

compilation.assets['filelist.md'] = {
      source: function() {
        return filelist;
      },
      size: function() {
        return filelist.length;
      }
};           

此時你将看到我們的輸出檔案夾下将會多了一個輸出檔案filelist.md。這裡你可能還有一個疑問,我們的assets和chunk有什麼差別?你可以運作并檢視我的這個例子,你會看到我們的dest目錄下有如下的檔案�清單生成:

webpack系列6之深入淺出compiler和compilation

我們可以發現assets除了包含那些chunks内容以外,還包含那些子產品中對圖檔等的引用等。這個你可以檢視我們的filelist.md檔案内容。結合這個例子,我們深入分析下上面說到的那些屬性,我們在這個Plugin中輸出下面的内容:

const assets = compilation.getStats().toJson().assets;
    assets.forEach(function(asset,i){
      console.log('asset.name====',asset.name);
      console.log('asset.chunkNames====',asset.chunkNames);
      console.log('asset.chunks====',asset.chunks);
      console.log("----------------");
    });           

此時你會看到如下的輸出結果:

asset.name==== 12f93f748739150b542661e8677d0870.png
  asset.chunkNames==== []
  asset.chunks==== []
  ----------------
  asset.name==== 0.js
  asset.chunkNames==== []
  asset.chunks==== [ 0 ]
  ----------------
  asset.name==== 1.js
  asset.chunkNames==== []
  asset.chunks==== [ 1 ]
  ----------------
  asset.name==== main1.js
  asset.chunkNames==== [ 'main1' ]
  asset.chunks==== [ 2 ]
  ----------------
  asset.name==== main.js
  asset.chunkNames==== [ 'main' ]
  //注意:這裡的chunkName隻會包含main,而不會包含require.ensure産的chunk名稱
  asset.chunks==== [ 3 ]           

通過輸出我們可以知道,每一個assets都會有一個name屬性,而chunks表示該輸出資源包含的chunk的ID值清單。而chunkNames表示該輸出資源包含的chunk的名稱,但是不包含require.ensure産生的chunk的名稱,除非require.ensure的時候自己指定了。比如下面的例子:

require.ensure([],function(require){
     require('vue');
  },"hello")
  //此時chunkName就是是['hello']           

1.3 擷取stats中所有的modules

compilation.getStats().toJson().modules
// 擷取compilation所有的modules:           

modules内部結構如下:

{ id: 10,
 //該子產品的id,和`module.id`一樣
   identifier: 'C:\\Users\\Administrator\\Desktop\\webpack-chunkfilename\\node_
odules\\html-loader\\index.js!C:\\Users\\Administrator\\Desktop\\webpack-chunkf
lename\\src\\Components\\Header.html',
   //webpack内部使用這個唯一的ID來表示這個子產品
   name: './src/Components/Header.html',
   //子產品名稱,已經轉化為相對于根目錄的路徑
   index: 10,
   index2: 8,
   size: 62,
   cacheable: true,
   //表示這個子產品是否可以緩存,調用this.cacheable()
   built: true,
   //表示這個子產品通過Loader,Parsing,Code Generation階段
   optional: false,
   //是以對該子產品的加載全部通過try..catch包裹
   prefetched: false,
   //表示該子產品是否是預加載的。即,在第一個import,require調用之前我們就開始解析和打包該子產品。https://webpack.js.org/plugins/prefetch-plugin/
   chunks: [ 0 ],
   //該子產品在那個chunk中出現
   assets: [],
   //該子產品包含的所有的資源檔案集合
   issuer: 'C:\\Users\\Administrator\\Desktop\\webpack-chunkfilename\\node_modu
es\\eslint-loader\\index.js!C:\\Users\\Administrator\\Desktop\\webpack-chunkfil
name\\src\\Components\\Header.js',
//是誰開始本子產品的調用的,即子產品調用發起者
   issuerId: 1,
   //發起者的moduleid
   issuerName: './src/Components/Header.js',
   //發起者相對于根目錄的路徑
   profile: undefined,
   failed: false,
   //在解析或者處理該子產品的時候是否失敗
   errors: 0,
   //在解析或者處理該子產品的是否出現的錯誤數量
   warnings: 0,
   //在解析或者處理該子產品的是否出現的�警告數量
   reasons: [ [Object] ],
   usedExports: [ 'default' ],
   providedExports: null,
   depth: 2,
   source: 'module.exports = "<header class=\\"header\\">{{text}}</header>";' }
   //source是子產品内容,但是已經變成了字元串了           

而�每一個子產品都包含一個Reason對象表示該子產品為什麼會出現在依賴圖譜中,這個Reason對象和上面說的chunk的origins類似,其内部簽名如下:

{
  "loc": "33:24-93",
   // Lines of code that caused the module to be included
  "module": "./lib/index.web.js",
   // Relative path to the module based on context
  "moduleId": 0, 
  // The ID of the module
  "moduleIdentifier": "(webpack)\\test\\browsertest\\lib\\index.web.js", 
  // Path to the module
  "moduleName": "./lib/index.web.js", 
  // A more readable name for the module (used for "pretty-printing")
  "type": "require.context", 
  // The type of request used
  "userRequest": "../../cases" 
  // Raw string used for the `import` or `require` request
}           

2.webpack中的Compiler對象

2.1 在插件中使用compiler執行個體

webpack的Compiler子產品是webpack主要引擎,通過它可以建立一個compilation執行個體,而且你所有通過cli或者webpack的API或者webpack的配置檔案傳入的配置都會作為參數來建構一個compilation執行個體。你可以通過webpack.Compiler來通路它。webpack通過執行個體化一個compiler對象,然後調用它的run方法來開始一次完整的編譯過程,下面的例子示範如何使用Compiler對象,其實webpack内部也是這樣處理的:

// Can be imported from webpack package
import {Compiler} from 'webpack';
// Create a new compiler instance
const compiler = new Compiler();
// Populate all required options
compiler.options = {...};
// Creating a plugin.
class LogPlugin {
  apply (compiler) {
    compiler.plugin('should-emit', compilation => {
      console.log('should I emit?');
      return true;
    })
  }
}
// Apply the compiler to the plugin
new LogPlugin().apply(compiler);
/* Add other supporting plugins */
// Callback to be executed after run is complete
const callback = (err, stats) => {
  console.log('Compiler has finished execution.');
  // Display stats...
};
// call run on the compiler along with the callback
//compiler.Compiler上的watch方法用于開啟一次監聽模式的編譯,但是在正常的編譯方法中不會調用(callback);           

我們的Compiler對象本身是一個Tapable執行個體,內建了Tapable類的一些功能。大多數使用者看到的插件都是注冊到Compiler執行個體上的,我們的Compiler作用可以濃縮為下面幾點:

  • 一般隻有一個父Compiler,而子Compiler可以用來處理一些特殊的事件,比如htmlWebpackplugin
  • 建立Compiler的難點在于傳入所有相關的配置
  • webpack使用WebpackOptionsDefaulter和WebpackOptionsApply來預設為Compiler設定參數
  • Compiler對象其實隻是一個函數,其提供的功能非常有限,隻是負責生命周期相關的部分。它将加載/打包/寫檔案等工作配置設定給不同的插件
  • 通過new LogPlugin(args).apply(compiler)用于在Compiler的聲明周期方法中注冊特定的鈎子函數
  • Compiler提供的run方法用于開始整個webpack的打包過程。如果打包完成,就會調用callback回調函數,這個方法可以用來擷取編譯的資訊

每一個plugin都需要在原型鍊上面有用apply方法,這個apply方法中會被傳入Compiler對象執行個體:

//MyPlugin.js
function MyPlugin() {};
MyPlugin.prototype.apply = function (compiler) {
    //now you have access to all the compiler instance methods
}
module.exports = MyPlugin;           

當然,你也可以使用下面這種方式:

//MyFunction.js
function apply(options, compiler) {
    //now you have access to the compiler instance
    //and options
}
//this little trick makes it easier to pass and check options to the plugin
module.exports = function(options) {
    if (options instanceof Array) {
        options = {
            include: options
        };
    }

    if (!Array.isArray(options.include)) {
        options.include = [ options.include ];
    }
    return {
        apply: apply.bind(this, options)
    };
};           

2.2 compiler上的鈎子函數

  • run方法 Compiler上的run方法用于開始一次編譯過程,但是在"監聽"模式下不會調用
  • watch-run Compiler上的watch方法用于開啟一次監聽模式的編譯,但是在正常的模式下不會調用,隻在watch模式下使用
  • compilation(c: Compilation, params: Object) 此時compilation執行個體對象已經被建立。此時你的插件可以使用該鈎子函數來擷取一個Compilation執行個體對象。此時的params對象也包含很多有用的引用
  • normal-module-factory(nmf: NormalModuleFactory) 此時NormalModuleFactory對象被建立,此時你的插件可以使用這個鈎子函數擷取NormalModuleFactory執行個體。比如下面的例子:
compiler.plugin("normal-module-factory", function(nmf) {
    nmf.plugin("after-resolve", function(data) {
        data.loaders.unshift(path.join(__dirname, "postloader.js"));
        //添加一個loader
    });
});           
  • context-module-factory(cmf: ContextModuleFactory) 此時一個ContextModuleFactory對象被建立,我們的插件可以使用該鈎子函數擷取ContextModuleFactory對象執行個體
  • compile(params) 此時我們的Compiler對象開始編譯,這個鈎子函數不管在正常模式還是監聽模式下都會被調用。此時我們的插件可以修改params對象
compiler.plugin("compile", function(params) {
    //you are now in the "compile" phase
});           
  • make(c: Compilation) 此時我們的插件可以添加入口檔案或者預加載的子產品。可以通過調用compilation上的addEntry(context, entry, name, callback)或者prefetch(context, dependency, callback)來完成。
  • after-compile(c: Compilation) 此時,我們的compile階段已經完成了,很多子產品已經被密封(sealed),下一個步驟就是開始輸出産生的檔案。
  • emit(c: Compilation) 我們的Compiler開始輸出檔案。此時我們的插件是添加輸出檔案到compilation.assets的最後時機
  • after-emit(c: Compilation) 我們的Compiler已經輸出了所有的資源
  • done(stats: Stats) 打包已經完成
  • failed(err: Error) Compiler對象在watch模式,compilation失敗即打包失敗
  • invalid() Compiler對象在watch模式,而且檔案已經發生變化。下options.watchDelay時間後将會重新編譯
  • after-plugins() 所有從options中抽取出來的插件全部被添加到compiler執行個體上了
  • after-resolvers() 所有從options中抽取出來的插件全部被添加到resolver上了

2.Watching對象

Compiler本身支援"監聽"模式,在該模式下,如果檔案發生變化那麼webpack會自動重新編譯。如果在watch模式下,我們的webpack會調用一些特定的方法,比如 "watch-run", "watch-close"和"invalid"。這些鈎子函數常用于開發模式,一般和webpack-dev-server等配合,這樣每次檔案變化,開發者不用手動編譯。

3.Compilation對象

我們的Compilation對象繼承于Compiler。compiler.compilation是打包過程中的compilation對象。這個屬性可以擷取到所有的子產品和依賴,但是大多數都是循環引用的模式。在compilation階段,子產品被加載,封存,優化,chunked,hased和存儲。這些都是compilation對象上的主要聲明周期方法:

compiler.plugin("compilation", function(compilation) {
    //the main compilation instance
    //all subsequent methods are derived from compilation.plugin
})           

下面介紹主要的聲明周期方法:

  • normal-module-loader 這個loader是一個函數,用于依次加載所有依賴圖譜中出現的子產品
compilation.plugin('normal-module-loader', function(loaderContext, module) {
    //this is where all the modules are loaded
    //one by one, no dependencies are created yet
    //沒有建立依賴
});           
  • seal 在這個階段你不會再擷取到任何沒有加載過的子產品
compilation.plugin('seal', function() {
    //you are not accepting any more modules
    //no arguments
});           
  • optimize 此時用于優化本次的打包過程
compilation.plugin('optimize', function() {
    //webpack is begining the optimization phase
    // no arguments
});           
  • optimize-tree(chunks, modules) 用于優化子產品或者chunks依賴圖譜
compilation.plugin('optimize-tree', function(chunks, modules) {
});           
  • optimize-modules(modules: Module[]) 用于優化子產品
compilation.plugin('optimize-modules', function(modules) {
    //handle to the modules array during tree optimization
});           
  • after-optimize-modules(modules: Module[]) 所有的子產品已經優化完畢
  • optimize-chunks(chunks: Chunk[]) 優化所有的chunks
//optimize chunks may be run several times in a compilation
compilation.plugin('optimize-chunks', function(chunks) {
    //unless you specified multiple entries in your config
    //there's only one chunk at this point
    chunks.forEach(function (chunk) {
        //chunks have circular references to their modules
        chunk.modules.forEach(function (module){
            //module.loaders, module.rawRequest, module.dependencies, etc.
        }); 
    });
});           
  • after-optimize-chunks(chunks: Chunk[])
  • chunks優化完畢
  • revive-modules(modules: Module[], records)
  • 從記錄中重新加載子產品資訊
  • optimize-module-order(modules: Module[])
  • 根據子產品的重要性來對子產品排序。最重要的排列在前面,并得到最小的id
  • optimize-module-ids(modules: Module[])
  • 優化子產品的id
  • after-optimize-module-ids(modules: Module[])
  • 子產品id優化完畢
  • record-modules(modules: Module[], records)
  • 将子產品資訊重新儲存到records中
  • revive-chunks(chunks: Chunk[], records)
  • 将chunk資訊從records中加載出來
  • optimize-chunk-order(chunks: Chunk[])
  • 根據chunk的重要性來對chunk排序。最重要的排列在前面,并得到最小的id
  • optimize-chunk-ids(chunks: Chunk[])
  • 優化chunk的id
  • after-optimize-chunk-ids(chunks: Chunk[])
  • chunk的id優化完畢
  • record-chunks(chunks: Chunk[], records)
  • 将chunk的資訊重新儲存到records中
  • before-hash
  • 此時本次編譯還沒有産生hash
  • after-hash
  • 此時,我們本次編譯已經産生hash
  • before-chunk-assets
  • 此時,特定的chunk的資源還沒有被建立
  • additional-chunk-assets(chunks: Chunk[])
  • 為特定的chunk建立額外的資源
  • record(compilation, records)
  • 将我們的compilation的打包資訊儲存到records中
  • optimize-chunk-assets(chunks: Chunk[])
  • 優化每一個chunk依賴的資源檔案。我們的資源檔案全部儲存在this.assets(this指向compilation)中,但是并不是所有檔案都會是一個chunk,每一個chunk都會有特定的files屬性,這個files屬性指向的是這個chunk建立的所有的檔案資源。所有額外的chunk資源都儲存在this.additionalChunkAssets屬性中。下面的例子展示為每一個chunk添加banner資訊:
compilation.plugin("optimize-chunk-assets", function(chunks, callback) {
    chunks.forEach(function(chunk) {
        chunk.files.forEach(function(file) {
            compilation.assets[file] = new ConcatSource("\/**Sweet Banner**\/", "\n", compilation.assets[file]);
        });
    });
    callback();
});           

那麼上面的this.additionalChunkAssets指的是什麼呢,我們看看下面的例子:

compiler.plugin('compilation', function(compilation) {
  compilation.plugin('additional-assets', function(callback) {
    download('https://img.shields.io/npm/v/webpack.svg', function(resp) {
      if(resp.status === 200) {
        //下載下傳檔案添加到assets中
        compilation.assets['webpack-version.svg'] = toAsset(resp);
        callback();
      } else {
        callback(new Error('[webpack-example-plugin] Unable to download the image'));
      }
    })
  });
});           

所有this.additionalChunkAssets其實指的就是外部依賴的檔案,比如網絡資源文本,而不是本地的檔案。

  • after-optimize-chunk-assets(chunks: Chunk[]) 所有chunk産生的資源都被優化過了。下面的例子展示每一個chunk都有哪些内容:
var PrintChunksPlugin = function() {};
PrintChunksPlugin.prototype.apply = function(compiler) {
    compiler.plugin('compilation', function(compilation, params) {
        compilation.plugin('after-optimize-chunk-assets', function(chunks) {
            console.log(chunks.map(function(c) {
                return {
                    id: c.id,
                    name: c.name,
                    includes: c.modules.map(function(m) {
                        return m.request;
                    })
                };
            }));
        });
    });
};           
  • optimize-assets(assets: Object{name: Source})
  • 優化所有的assets資源,所有的資源都儲存在this.assets中
  • after-optimize-assets(assets: Object{name: Source})
  • 所有的assets資源被優化完畢
  • build-module(module)
  • 某一個子產品的編譯還沒有開始
compilation.plugin('build-module', function(module){
    console.log('build module');
    console.log(module);
});           
  • succeed-module(module)
  • 子產品打包成功
compilation.plugin('succeed-module', function(module){
    console.log('succeed module');
    console.log(module);
});           
  • failed-module(module)
  • 子產品打包失敗
compilation.plugin('failed-module', function(module){
    console.log('failed module');
    console.log(module);
});           
  • module-asset(module, filename)

所有子產品的資源被添加到compilation中

  • chunk-asset(chunk, filename)
  • 所有的chunk中的資源被添加到compilation中

關于上面提到的compiler和compilation的鈎子函數,你可以繼續閱讀我的這篇文章。

4.MultiCompiler

MultiCompiler允許webpack在不同的Compiler中運作多個配置檔案。比如在webpack方法中第一個參數是一個數組,那麼webpack就會啟動多個compiler執行個體對象,這樣隻有當所有的compiler完成執行後才會運作callback方法:

var webpack = require('webpack');
var config1 = {
  entry: './index1.js',
  output: {filename: 'bundle1.js'}
}
var config2 = {
  entry: './index2.js',
  output: {filename:'bundle2.js'}
}
//第一個參數為數組
webpack([config1, config2], (err, stats) => {
  process.stdout.write(stats.toString() + "\n");
})           

對于webpack提供的事件鈎子函數你可以檢視這裡,比如下面的例子展示了異步的事件處理,其中emit方法表示準備将資源輸出到output目錄:

compiler.plugin("emit", function(compilation, callback) {
  // Do something async...
  setTimeout(function() {
    console.log("Done with async work...");
    callback();
  }, 1000);
});           

繼續閱讀