成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

FE.SRC-webpack原理梳理

cfanr / 2050人閱讀

摘要:執(zhí)行完成后會返回如下圖的結(jié)果,根據(jù)返回數(shù)據(jù)把源碼和存儲在的屬性上的回調(diào)函數(shù)中調(diào)用類生成,并根據(jù)生成依賴后回調(diào)方法返回類。

webpack設(shè)計模式 一切資源皆Module

Module(模塊)是webpack的中的關(guān)鍵實體。Webpack 會從配置的 Entry 開始遞歸找出所有依賴的模塊. 通過Loaders(模塊轉(zhuǎn)換器),用于把模塊原內(nèi)容按照需求轉(zhuǎn)換成新模塊內(nèi)容.

事件驅(qū)動架構(gòu)

webpack整體是一個事件驅(qū)動架構(gòu),所有的功能都以Plugin(插件)的方式集成在構(gòu)建流程中,通過發(fā)布訂閱事件來觸發(fā)各個插件執(zhí)行。webpack核心使用tapable來實現(xiàn)Plugin(插件)的注冊和調(diào)用,Tapable是一個事件發(fā)布(tap)訂閱(call)庫

概念

Graph 模塊之間的Dependency(依賴關(guān)系)構(gòu)成的依賴圖

CompilerTapable實例)訂閱了webpack最頂層的生命周期事件

ComplilationTapable實例)該對象由Compiler創(chuàng)建, 負責構(gòu)建Graph,Seal,Render...是整個工作流程的核心生命周期,包含Dep Graph 遍歷算法,優(yōu)化(optimize),tree shaking...

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

ResolverTapable實例)資源路徑解析器

ModuleFactoryTapable實例) 被Resolver成功解析的資源需要被這個工廠類被實例化成Module

ParserTapable實例) 負責將Module(ModuleFactory實例化來的)轉(zhuǎn)AST的解析器 (webpack 默認用acorn),并解析出不同規(guī)范的require/import 轉(zhuǎn)成Dependency(依賴)

Template 模塊化的模板. Chunk,Module,Dependency都有各自的模塊模板,來自各自的工廠類的實例

bundlechunk區(qū)別:https://github.com/webpack/we...

bundle:由多個不同的模塊打包生成生成最終的js文件,一個js文件即是1個bundle。

chunk: Graph的組成部分。一般有n個入口=n個bundle=graph中有n個chunk。但假設(shè)由于n個入口有m個公共模塊會被重復打包,需要分離,最終=n+m個bundle=graph中有n+m個chunk

有3類chunk:

Entry chunk: 包含runtime code 的,就是開發(fā)模式下編譯出的有很長的/******/的部分 (是bundle)

Initial chunk:同步加載,不包含runtime code 的。(可能和entry chunk打包成一個bundle,也可能分離成多個bundle)

Normal chunk:延遲加載/異步 的module

chunk的依賴圖算法
https://medium.com/webpack/th...

整個工作流程

Compiler 讀取配置,創(chuàng)建Compilation

Compiler創(chuàng)建Graph的過程:

Compilation讀取資源入口

NMF(normal module factory)

Resolver 解析

輸出NM

Parser 解析 AST

js json 用acorn

其他用Loader (執(zhí)行l(wèi)oader runner)

如果有依賴, 重復步驟 2

Compilation優(yōu)化Graph

Compilation渲染Graph

根據(jù)Graph上的各類模塊用各自的Template渲染

chunk template

Dependency template

...

合成IIFE的最終資源

Tapable 鉤子列表
鉤子名 執(zhí)行方式 要點
SyncHook 同步串行 不關(guān)心監(jiān)聽函數(shù)的返回值
SyncBailHook 同步串行 只要監(jiān)聽函數(shù)中有一個函數(shù)的返回值不為null,則跳過剩下所有的邏輯
SyncWaterfallHook 同步串行 上一個監(jiān)聽函數(shù)的返回值可以傳給下一個監(jiān)聽函數(shù)
SyncLoopHook 同步循環(huán) 當監(jiān)聽函數(shù)被觸發(fā)的時候,如果該監(jiān)聽函數(shù)返回true時則這個監(jiān)聽函數(shù)會反復執(zhí)行,如果返回undefined則表示退出循環(huán)
AsyncParallelHook 異步并發(fā) 不關(guān)心監(jiān)聽函數(shù)的返回值
AsyncParallelBailHook 異步并發(fā) 只要監(jiān)聽函數(shù)的返回值不為null,就會忽略后面的監(jiān)聽函數(shù)執(zhí)行,直接跳躍到callAsync等觸發(fā)函數(shù)綁定的回調(diào)函數(shù),然后執(zhí)行這個被綁定的回調(diào)函數(shù)
AsyncSeriesHook 異步串行 不關(guān)心callback的參數(shù)
AsyncSeriesBailHook 異步串行 callback()的參數(shù)不為null,就會直接執(zhí)行callAsync等觸發(fā)函數(shù)綁定的回調(diào)函數(shù)
AsyncSeriesWaterfalllHook 異步串行 上一個監(jiān)聽函數(shù)中的callback(err,data)的第二個參數(shù),可以作為下一個監(jiān)聽函數(shù)的參數(shù)
示例
//創(chuàng)建一個發(fā)布訂閱中心
let Center=new TapableHook()
//注冊監(jiān)聽事件
Center.tap("eventName",callback)
//觸發(fā)事件
Center.call(...args)
//注冊攔截器
Center.intercept({
    context,//事件回調(diào)和攔截器的共享數(shù)據(jù)
    call:()=>{},//鉤子觸發(fā)前
    register:()=>{},//添加事件時
    tap:()=>{},//執(zhí)行鉤子前
    loop:()=>{},//循環(huán)鉤子
})

更多示例 https://juejin.im/post/5abf33...

Module

它有很多子類:RawModule, NormalModule ,MultiModule,ContextModule,DelegatedModule,DllModule,ExternalModule 等

ModuleFactory: 使用工廠模式創(chuàng)建不同的Module,有四個主要的子類: NormalModuleFactory,ContextModuleFactory , DllModuleFactory,MultiModuleFactory

Template

mainTemplate 和 chunkTemplate

if(chunk.entry) {
source = this.mainTemplate.render(this.hash, chunk, this.moduleTemplate, this.dependencyTemplates);
} else {
source = this.chunkTemplate.render(chunk, this.moduleTemplate, this.dependencyTemplates);
}

不同模塊規(guī)范封裝

MainTemplate.prototype.requireFn = "__webpack_require__";
MainTemplate.prototype.render = function(hash, chunk, moduleTemplate, dependencyTemplates) {
    var buf = [];
    // 每一個module都有一個moduleId,在最后會替換。
    buf.push("function " + this.requireFn + "(moduleId) {");
    buf.push(this.indent(this.applyPluginsWaterfall("require", "", chunk, hash)));
    buf.push("}");
    buf.push("");
    ... // 其余封裝操作
};

ModuleTemplate 是對所有模塊進行一個代碼生成

HotUpdateChunkTemplate 是對熱替換模塊的一個處理

webpack_require
function __webpack_require__(moduleId) {
    // 1.首先會檢查模塊緩存
    if(installedModules[moduleId]) {
        return installedModules[moduleId].exports;
    }
    
    // 2. 緩存不存在時,創(chuàng)建并緩存一個新的模塊對象,類似Node中的new Module操作
    var module = installedModules[moduleId] = {
        i: moduleId,
        l: false,
        exports: {},
        children: []
    };

    // 3. 執(zhí)行模塊,類似于Node中的:
    // result = compiledWrapper.call(this.exports, this.exports, require, this, filename, dirname);
    modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
    //需要引入模塊時,同步地將模塊從暫存區(qū)取出來執(zhí)行,避免使用網(wǎng)絡(luò)請求導致過長的同步等待時間。

    module.l = true;

    // 4. 返回該module的輸出
    return module.exports;
}
異步模塊加載
__webpack_require__.e = function requireEnsure(chunkId) {
    var promises = [];
    var installedChunkData = installedChunks[chunkId];
    
    // 判斷該chunk是否已經(jīng)被加載,0表示已加載。installChunk中的狀態(tài):
    // undefined:chunk未進行加載,
    // null:chunk preloaded/prefetched
    // Promise:chunk正在加載中
    // 0:chunk加載完畢
    if(installedChunkData !== 0) {
        // chunk不為null和undefined,則為Promise,表示加載中,繼續(xù)等待
        if(installedChunkData) {
            promises.push(installedChunkData[2]);
        } else {
            // 注意這里installChunk的數(shù)據(jù)格式
            // 從左到右三個元素分別為resolve、reject、promise
            var promise = new Promise(function(resolve, reject) {
                installedChunkData = installedChunks[chunkId] = [resolve, reject];
            });
            promises.push(installedChunkData[2] = promise);

            // 下面代碼主要是根據(jù)chunkId加載對應的script腳本
            var head = document.getElementsByTagName("head")[0];
            var script = document.createElement("script");
            var onScriptComplete;

            script.charset = "utf-8";
            script.timeout = 120;
            if (__webpack_require__.nc) {
                script.setAttribute("nonce", __webpack_require__.nc);
            }
            
            // jsonpScriptSrc方法會根據(jù)傳入的chunkId返回對應的文件路徑
            script.src = jsonpScriptSrc(chunkId);

            onScriptComplete = function (event) {
                script.onerror = script.onload = null;
                clearTimeout(timeout);
                var chunk = installedChunks[chunkId];
                if(chunk !== 0) {
                    if(chunk) {
                        var errorType = event && (event.type === "load" ? "missing" : event.type);
                        var realSrc = event && event.target && event.target.src;
                        var error = new Error("Loading chunk " + chunkId + " failed.
(" + errorType + ": " + realSrc + ")");
                        error.type = errorType;
                        error.request = realSrc;
                        chunk[1](error);
                    }
                    installedChunks[chunkId] = undefined;
                }
            };
            var timeout = setTimeout(function(){
                onScriptComplete({ type: "timeout", target: script });
            }, 120000);
            script.onerror = script.onload = onScriptComplete;
            head.appendChild(script);
        }
    }
    return Promise.all(promises);
};
異步模塊緩存
// webpack runtime chunk
function webpackJsonpCallback(data) {
    var chunkIds = data[0];
    var moreModules = data[1];
    var executeModules = data[2];

    var moduleId, chunkId, i = 0, resolves = [];
    // webpack會在installChunks中存儲chunk的載入狀態(tài),據(jù)此判斷chunk是否加載完畢
    for(;i < chunkIds.length; i++) {
        chunkId = chunkIds[i];
        if(installedChunks[chunkId]) {
            resolves.push(installedChunks[chunkId][0]);
        }
        installedChunks[chunkId] = 0;
    }
    
    // 注意,這里會進行“注冊”,將模塊暫存入內(nèi)存中
    // 將module chunk中第二個數(shù)組元素包含的 module 方法注冊到 modules 對象里
    for(moduleId in moreModules) {
        if(Object.prototype.hasOwnProperty.call(moreModules, moduleId)) {
            modules[moduleId] = moreModules[moduleId];
        }
    }

    if(parentJsonpFunction) parentJsonpFunction(data);

    //先根據(jù)模塊注冊時的chunkId,取出installedChunks對應的所有l(wèi)oading中的chunk,最后將這些chunk的promise進行resolve操作
    while(resolves.length) {
        resolves.shift()();
    }

    deferredModules.push.apply(deferredModules, executeModules || []);

    return checkDeferredModules();
};
保證chunk加載后才執(zhí)行模塊
function checkDeferredModules() {
    var result;
    for(var i = 0; i < deferredModules.length; i++) {
        var deferredModule = deferredModules[i];
        var fulfilled = true;
        // 第一個元素是模塊id,后面是其所需的chunk
        for(var j = 1; j < deferredModule.length; j++) {
            var depId = deferredModule[j];
            // 這里會首先判斷模塊所需chunk是否已經(jīng)加載完畢
            if(installedChunks[depId] !== 0) fulfilled = false;
        }
        // 只有模塊所需的chunk都加載完畢,該模塊才會被執(zhí)行(__webpack_require__)
        if(fulfilled) {
            deferredModules.splice(i--, 1);
            result = __webpack_require__(__webpack_require__.s = deferredModule[0]);
        }
    }
    return result;
}
Module 被 Loader 編譯的主要步驟

webpack的配置options

在Compiler.js中會為將用戶配置與默認配置(WebpackOptionsDefaulter)合并,其中就包括了loader的默認配置 module.defaultRules (OptionsDefaulter則是一個封裝的配置項存取器,封裝了一些特殊的方法來操作配置對象)

//lib/webpack.js
options = new WebpackOptionsDefaulter().process(options);
compiler = new Compiler(options.context);
compiler.options = options;
/*options:{
    entry: {},//入口配置
    output: {}, //輸出配置
    plugins: [], //插件集合(配置文件 + shell指令) 
    module: { loaders: [ [Object] ] }, //模塊配置
    context: //工程路徑
    ... 
}*/

創(chuàng)建Module

根據(jù)配置創(chuàng)建Module的工廠類Factory(Compiler.js)

通過loader的resolver來解析loader路徑

使用Factory創(chuàng)建 NormalModule實例

使用loaderResolver解析loader模塊路徑

根據(jù)rule.modules創(chuàng)建RulesSet規(guī)則集

Loader編譯過程(詳見Loader章節(jié))

NormalModule實例.build() 進行模塊的構(gòu)建

loader-runner 執(zhí)行編譯module

Compiler

Compiler源碼

compiler.hooks
class Compiler extends Tapable {
    constructor(context) {
        super();
        this.hooks = {
            shouldEmit: new SyncBailHook(["compilation"]),//此時返回 true/false。
            done: new AsyncSeriesHook(["stats"]),//編譯(compilation)完成。
            additionalPass: new AsyncSeriesHook([]),
            beforeRun: new AsyncSeriesHook(["compiler"]),//compiler.run() 執(zhí)行之前,添加一個鉤子。
            run: new AsyncSeriesHook(["compiler"]),//開始讀取 records 之前,鉤入(hook into) compiler。
            emit: new AsyncSeriesHook(["compilation"]),//輸出到dist目錄
            afterEmit: new AsyncSeriesHook(["compilation"]),//生成資源到 output 目錄之后。

            thisCompilation: new SyncHook(["compilation", "params"]),//觸發(fā) compilation 事件之前執(zhí)行(查看下面的 compilation)。
            compilation: new SyncHook(["compilation", "params"]),//編譯(compilation)創(chuàng)建之后,執(zhí)行插件。
            normalModuleFactory: new SyncHook(["normalModuleFactory"]),//NormalModuleFactory 創(chuàng)建之后,執(zhí)行插件。
            contextModuleFactory: new SyncHook(["contextModulefactory"]),//ContextModuleFactory 創(chuàng)建之后,執(zhí)行插件。

            beforeCompile: new AsyncSeriesHook(["params"]),//編譯(compilation)參數(shù)創(chuàng)建之后,執(zhí)行插件。
            compile: new SyncHook(["params"]),//一個新的編譯(compilation)創(chuàng)建之后,鉤入(hook into) compiler。
            make: new AsyncParallelHook(["compilation"]),//從入口分析依賴以及間接依賴模塊
            afterCompile: new AsyncSeriesHook(["compilation"]),//完成構(gòu)建,緩存數(shù)據(jù)

            watchRun: new AsyncSeriesHook(["compiler"]),//監(jiān)聽模式下,一個新的編譯(compilation)觸發(fā)之后,執(zhí)行一個插件,但是是在實際編譯開始之前。
            failed: new SyncHook(["error"]),//編譯(compilation)失敗。
            invalid: new SyncHook(["filename", "changeTime"]),//監(jiān)聽模式下,編譯無效時。
            watchClose: new SyncHook([]),//監(jiān)聽模式停止。
        }
    }
}
compiler其他屬性
this.name /** @type {string=} */
this.parentCompilation /** @type {Compilation=} */
this.outputPath = /** @type {string} */

this.outputFileSystem
this.inputFileSystem

this.recordsInputPath /** @type {string|null} */
this.recordsOutputPath  /** @type {string|null} */
this.records = {};
this.removedFiles //new Set();
this.fileTimestamps  /** @type {Map} */
this.contextTimestamps /** @type {Map} */
this.resolverFactory /** @type {ResolverFactory} */

this.options = /** @type {WebpackOptions} */
this.context = context;
this.requestShortener

this.running = false;/** @type {boolean} */
this.watchMode = false;/** @type {boolean} */

this._assetEmittingSourceCache /** @private @type {WeakMap }>} */

this._assetEmittingWrittenFiles/** @private @type {Map} */
compiler.prototype.run(callback)執(zhí)行過程

compiler.hooks.beforeRun

compiler.hooks.run

compiler.compile

params=this.newCompilationParams 創(chuàng)建NormalModuleFactory,contextModuleFactory實例。

NMF.hooks.beforeResolve

NMF.hooks.resolve 解析loader模塊的路徑(例如css-loader這個loader的模塊路徑是什么)

NMF.hooks.factory 基于resolve鉤子的返回值來創(chuàng)建NormalModule實例。

NMF.hooks.afterResolve

NMF.hooks.createModule

compiler.hooks.compile.call(params)

compilation = new Compilation(compiler)

this.hooks.thisCompilation.call(compilation, params)

this.hooks.compilation.call(compilation, params)

compiler.hooks.make

compilation.hooks.finish

compilation.hooks.seal

compiler.hooks.afterCompile
return callback(null, compilation)

Compilation

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

承接上文的compilation = new Compilation(compiler)

負責組織整個打包過程,包含了每個構(gòu)建環(huán)節(jié)及輸出環(huán)節(jié)所對應的方法

如 addEntry() , _addModuleChain() , buildModule() , seal() , createChunkAssets() (在每一個節(jié)點都會觸發(fā) webpack 事件去調(diào)用各插件)。

該對象內(nèi)部存放著所有 module ,chunk,生成的 asset 以及用來生成最后打包文件的 template 的信息。

compilation.addEntry()主要執(zhí)行過程

comilation._addModuleChain()

moduleFactory = comilation.dependencyFactories.get(Dep)

moduleFactory.create()

comilation.addModule(module)

comilation.buildModule(module)

afterBuild()

compilation.seal()主要執(zhí)行過程

comilation.hooks.optimizeDependencies

創(chuàng)建chunks

循環(huán) comilation.chunkGroups.push(entrypoint)

comilation.processDependenciesBlocksForChunkGroups(comilation.chunkGroups.slice())

comilation.sortModules(comilation.modules);

優(yōu)化modules

comilation.hooks.optimizeModules

優(yōu)化chunks

comilation.hooks.optimizeChunks

優(yōu)化tree

comilation.hooks.optimizeTree

comilation.hooks.optimizeChunkModules

comilation.sortItemsWithModuleIds

comilation.sortItemsWithChunkIds

comilation.createHash

comilation.createModuleAssets 添加到compildation.assets[fileName]

comilation.hooks.additionalChunkAssets

comilation.summarizeDependencies

comilation.hooks.additionalAssets

comilation.hooks.optimizeChunkAssets

comilation.hooks.optimizeAssets

comilation.hooks.afterSeal

Plugin

插件可以用于執(zhí)行范圍更廣的任務(wù)。包括:打包優(yōu)化,資源管理,注入環(huán)境變量

plugin: 一個具有 apply 方法的 JavaScript 對象。apply 方法會被 compiler 調(diào)用,并且 compiler 對象可在整個編譯生命周期訪問。這些插件包通常以某種方式擴展編譯功能。

編寫Plugin示例
class MyPlugin{
    apply(compiler){
        compiler.hooks.done.tabAsync("myPlugin",(stats,cb)=>{
            const assetsNames=[]
            for(let assetName in stats.compilation.assets)
                assetNames.push(assetName)
            console.log(assetsNames.join("
"))
            cb()
        })
        compiler.hooks.compilation.tap("MyPlugin",(compilation,params)=>{
            new MyCompilationPlugin().apply(compilation)
        })
    }
}

class MyCompilationPlugin{
    apply(compilation){
        compilation.hooks.additionalAssets.tapAsync("MyPlugin", callback => {
            download("https://img.shields.io/npm/v/webpack.svg", function(resp) {
                if(resp.status === 200) {
                    compilation.assets["webpack-version.svg"] = toAsset(resp);
                    callback()
                }
                else 
                    callback(new Error("[webpack-example-plugin] Unable to download the image"))
                
            });
        });
    }
}

module.exports=MyPlugin

其他聲明周期hooks和示例 https://webpack.docschina.org...

Resolver

在 NormalModuleFactory.js 的 resolver.resolve 中觸發(fā)

hooks在 WebpackOptionsApply.js的 compiler.resolverFactory.hooks中。

可以完全被替換,比如注入自己的fileSystem

Parser

在 CommonJSPulgin.js的new CommonJsRequireDependencyParserPlugin(options).appply(parser)觸發(fā),調(diào)用 CommonJsRequireDependencyParserPlugin.js 的apply(parser),負責添加Dependency,Template...

hooks在 CommonJsPlugin.js的 normarlModuleFactory.hooks.parser

Loader
在make階段build中會調(diào)用doBuild去加載資源,doBuild中會傳入資源路徑和插件資源去調(diào)用loader-runner插件的runLoaders方法去加載和執(zhí)行l(wèi)oader。執(zhí)行完成后會返回如下圖的result結(jié)果,根據(jù)返回數(shù)據(jù)把源碼和sourceMap存儲在module的_source屬性上;doBuild的回調(diào)函數(shù)中調(diào)用Parser類生成AST,并根據(jù)AST生成依賴后回調(diào)buildModule方法返回compilation類。
Loader的路徑

NormalModuleFactory將loader分為preLoader、postLoader和loader三種

對loader文件的路徑解析分為兩種:inline loader和config文件中的loader。

require的inline loader路徑前面的感嘆號作用:

! 禁用preLoaders (代碼檢查和測試,不生成module)

!! 禁用所有Loaders

-!禁用preLoaders和loaders,但不是postLoaders

前面提到NormalModuleFactory中的resolver鉤子中會先處理inline loader。

最終loader的順序:postinline、normalpre

然而loader是從右至左執(zhí)行的,真實的loader執(zhí)行順序是倒過來的,因此inlineLoader是整體后于config中normal loader執(zhí)行的。

路徑解析之 inline loader

正則解析loader和參數(shù)

//NormalModuleFactory.js
let elements = requestWithoutMatchResource
    .replace(/^-?!+/, "")
    .replace(/!!+/g, "!")
    .split("!");

將“解析模塊的loader數(shù)組”與“解析模塊本身”一起并行執(zhí)行,用到了neo-async這個庫(和async庫類似,都是為異步編程提供一些工具方法,但是會比async庫更快。)

解析返回結(jié)果:

[ 
    // 第一個元素是一個loader數(shù)組
    [ { 
        loader:
            "/workspace/basic-demo/home/node_modules/html-webpack-plugin/lib/loader.js",
        options: undefined
    } ],
    // 第二個元素是模塊本身的一些信息
    {
        resourceResolveData: {
            context: [Object],
            path: "/workspace/basic-demo/home/public/index.html",
            request: undefined,
            query: "",
            module: false,
            file: false,
            descriptionFilePath: "/workspace/basic-demo/home/package.json",
            descriptionFileData: [Object],
            descriptionFileRoot: "/workspace/basic-demo/home",
            relativePath: "./public/index.html",
            __innerRequest_request: undefined,
            __innerRequest_relativePath: "./public/index.html",
            __innerRequest: "./public/index.html"
        },
    resource: "/workspace/basic-demo/home/public/index.html"
    }
]

路徑解析之 config loader

NormalModuleFactory中有一個ruleSet的屬性,相當于一個規(guī)則過濾器,會將resourcePath應用于所有的module.rules規(guī)則,它可以根據(jù)模塊路徑名,匹配出模塊所需的loader。webpack編譯會根據(jù)用戶配置與默認配置,實例化一個RuleSet,它包含:

類靜態(tài)方法normalizeRule() 將配置值轉(zhuǎn)換為標準化的test對象,其上還會存儲一個this.references屬性

實例方法exec() 每次創(chuàng)建一個新的NormalModule時都會調(diào)用RuleSet實例的.exec()方法,只有當通過了各類測試條件,才會將該loader push到結(jié)果數(shù)組中。

references {map} key是loader在配置中的類型和位置,例如,ref-2表示loader配置數(shù)組中的第三個。

pitch & normal

同一匹配(test)資源有多l(xiāng)oader的時候:(類似先捕獲,再冒泡)

先順序loader.pitch()(源碼里是PitchingLoaders 不妨稱為 pitch 階段)

再倒序loader()(源碼里是NormalLoaders 不妨稱為 normal 階段).

這兩個階段(pitchnormal)就是loader-runner中對應的iteratePitchingLoaders()iterateNormalLoaders()兩個方法。

如果某個 loader 在 pitch 方法中return結(jié)果,會跳過剩下的 loader。那么pitch的遞歸就此結(jié)束,開始從當前位置從后往前執(zhí)行normal

normal loaders 結(jié)果示例(apply-loader, pug-loader)
//webpack.config.js
test: /.pug/,
use: [
    "apply-loader",
    "pug-loader",
]

先執(zhí)行pug-loader,得到 Module pug-loader/index.js!./src/index.pug的js代碼:

var pug = __webpack_require__(/*! pug-runtime/index.js */ "pug-runtime/index.js");

function template(locals) {var pug_html = "", pug_mixins = {}, pug_interp;pug_html = pug_html + "u003Cdiv class="haha"u003Easdu003Cu002Fdivu003E";return pug_html;};
module.exports = template;

//# sourceURL=webpack:///./src/index.pug?pug-loader

再執(zhí)行apply-loader,得到 Module "./src/index.pug" 的js代碼:

var req = __webpack_require__(/*! !pug-loader!./src/index.pug */ "pug-loader/index.js!./src/index.pug");
module.exports = (req["default"] || req).apply(req, [])

//# sourceURL=webpack:///./src/index.pug?

此時假設(shè)在入口文件./src/index.js引用

var html =__webpack_require__( "./index.pug")
console.log(html)
//
asd

這個入口文件 Module 的js代碼:

module.exports = __webpack_require__(/*! ./src/index.js */"./src/index.js");
//# sourceURL=webpack:///multi_./src/index.js?

build 后可看到控制臺輸出的 1個Chunk,2個Module(1個fs忽略),3個中間Module和一些隱藏Module

Asset    Size       Chunks             Chunk Names
main.js  12.9 KiB    main  [emitted]    main
Entrypoint main = main.js
[0] multi ./src/index.js 28 bytes {main} [built]
[1] fs (ignored) 15 bytes {main} [optional] [built]
[pug-loader/index.js!./src/index.pug] pug-loader!./src/index.pug 288 bytes {main} [built]
[./src/index.js] 51 bytes {main} [built]
[./src/index.pug] 222 bytes {main} [built]
pitching loaders 結(jié)果示例 (style-loader, css-loader)

pitch:順序執(zhí)行l(wèi)oader.pitch,例:

//webpack.config.js
test: /.css/,
use: [
    "style-loader",
    "css-loader",
]

style-loader(負責添加