webpack 4 源碼主流程分析(六):構建 module(上)

原文首發於 blog.flqin.com。若有錯誤,請聯繫筆者。分析碼字不易,轉載請代表出處,謝謝!node

初始化 module

接上文,在 resolver 函數回調裏,觸發 normalModuleFactory.hooks:afterResolve 以後,回調裏執行:webpack

let createdModule = this.hooks.createModule.call(result); // result 即爲 resolver 返回的組合對象 data
if (!createdModule) {
  if (!result.request) {
    return callback(new Error('Empty dependency (no request)'));
  }

  createdModule = new NormalModule(result);
}
createdModule = this.hooks.module.call(createdModule, result);

return callback(null, createdModule);
複製代碼

這裏觸發 normalModuleFactory.hooks:createModule,若是鉤子裏沒有項目配置的自定義 module,則使用 webpack 生成的 moduleweb

add module

獲得 module 實例,接着觸發 normalModuleFactory.hooks:module 以後,跳出 factory 函數,執行 factory 函數回調進行依賴緩存後,跳出 create 函數執行 moduleFactory.create 的回調。回調裏執行:api

const addModuleResult = this.addModule(module); // 將這個 `module` 保存到全局的 `Compilation.modules` 數組中和 `_modules` 對象中,判斷`_modules`是否有該 module 來設置是否已加載的標識
module = addModuleResult.module;

onModule(module); // 若是是入口文件還會將 modules 保存到 `Compilation.entries`

dependency.module = module;
module.addReason(null, dependency); // 添加該 `module` 被哪些模塊依賴
複製代碼

而後調用 this.buildModule 進入 build 階段。該方法作了回調緩存後,觸發 compilation.hooks:buildModule,而後執行 module.build()數組

構建 module

/node_modules/webpack/lib/NormalModule.js 文件裏執行 module.build, 設置一些屬性後,直接調用了 this.doBuild緩存

該方法裏先執行了 this.createLoaderContext 獲得loaderContext,爲全部的 loader 提供上下文環境並共享,而後調用了 runLoadersapp

runLoaders(
  {
    resource: this.resource,
    loaders: this.loaders,
    context: loaderContext,
    readResource: fs.readFile.bind(fs)
  },
  (err, result) => {
    //...
  }
);
複製代碼

loader-runner

該方法來自 loader-runner,經過各類 loader 處理源碼後,獲得一個處理後的 stringbuffer(可能還有個 sourcemap)。異步

還能夠解析自定義 loader 編寫一個 loaderasync

主要流程爲:函數

runLoaders -> iteratePitchingLoaders(按正序 require 每一個 loader) -> loadLoader(對應的 loader 導出的函數賦值到 loaderContext.loader[].normal、pitch函數賦值到loaderContext.loader[].pitch,而後執行pitch函數(若是有的話)) -> processResource(轉換 buffer 和設置 loaderIndex) -> iterateNormalLoaders(倒序執行全部 loader)-> runSyncOrAsync(同步或者異步執行 loader)

pitch 函數

  • 每一個 loader 能夠掛載一個 pitch 函數,該函數主要是用於利用 modulerequest,來提早作一些攔截處理的工做,並不實際處理 module 內容文檔

  • 正序 require loader 並執行其 pitch 方法( loadLoader 裏),在執行後的回調裏,若是有除了 err 的參數還有其餘參數,則執行 iterateNormalLoaders 越過剩下的未 requireloader 直接進入到執行 loader 的步驟。若是想沒有其餘參數,則執行 iteratePitchingLoaders 進行下個 loaderrequire。如代碼所示:

    if (args.length > 0) {
      loaderContext.loaderIndex--;
      iterateNormalLoaders(options, loaderContext, args, callback);
    } else {
      iteratePitchingLoaders(options, loaderContext, callback);
    }
    複製代碼
  • 倒序執行每一個 loadernormal 方法 。

核心代碼解析

// 該方法按正序 require 每一個 loader
function iteratePitchingLoaders(options, loaderContext, callback) {
  // abort after last loader 讀取全部 loader 後,執行 processResource 方法
  if (loaderContext.loaderIndex >= loaderContext.loaders.length) return processResource(options, loaderContext, callback);

  var currentLoaderObject = loaderContext.loaders[loaderContext.loaderIndex]; //選擇第一個 loader

  // iterate 增序後遞歸讀取下一個 loader
  if (currentLoaderObject.pitchExecuted) {
    loaderContext.loaderIndex++;
    return iteratePitchingLoaders(options, loaderContext, callback);
  }

  // load loader module 加載該 loader 模塊
  // 對應的 loader 導出的函數賦值到 loaderContext.loader[].normal
  loadLoader(currentLoaderObject, function(err) {
    if (err) {
      loaderContext.cacheable(false);
      return callback(err);
    }
    var fn = currentLoaderObject.pitch; //loadLoader 裏會把 module 賦值到 loader.normal, pitch 賦值到 loader.pitch
    currentLoaderObject.pitchExecuted = true;
    if (!fn) return iteratePitchingLoaders(options, loaderContext, callback);

    // 若是有的話,開始執行 pitch 函數,根據參數狀況決定是否繼續讀取剩下的loader
    runSyncOrAsync(fn, loaderContext, [loaderContext.remainingRequest, loaderContext.previousRequest, (currentLoaderObject.data = {})], function(err) {
      if (err) return callback(err);
      var args = Array.prototype.slice.call(arguments, 1);
      if (args.length > 0) {
        loaderContext.loaderIndex--;
        iterateNormalLoaders(options, loaderContext, args, callback);
      } else {
        iteratePitchingLoaders(options, loaderContext, callback);
      }
    });
  });
}

// 轉換 buffer 和設置 loaderIndex
function processResource(options, loaderContext, callback) {
  // set loader index to last loader 獲取最後一個 loader 的 index
  loaderContext.loaderIndex = loaderContext.loaders.length - 1;

  var resourcePath = loaderContext.resourcePath;
  if (resourcePath) {
    loaderContext.addDependency(resourcePath);
    // 轉換爲 buffer
    options.readResource(resourcePath, function(err, buffer) {
      if (err) return callback(err);
      options.resourceBuffer = buffer; //獲得buffer
      iterateNormalLoaders(options, loaderContext, [buffer], callback);
    });
  } else {
    iterateNormalLoaders(options, loaderContext, [null], callback);
  }
}

//倒序執行全部 loader
function iterateNormalLoaders(options, loaderContext, args, callback) {
  if (loaderContext.loaderIndex < 0) return callback(null, args); //執行完全部 loader 後退出,去執行 iteratePitchingLoaders 回調即 runLoaders 的回調

  var currentLoaderObject = loaderContext.loaders[loaderContext.loaderIndex]; //獲取對應 loader

  // iterate 減序後遞歸執行下一個 loader
  if (currentLoaderObject.normalExecuted) {
    loaderContext.loaderIndex--;
    return iterateNormalLoaders(options, loaderContext, args, callback);
  }

  var fn = currentLoaderObject.normal;
  currentLoaderObject.normalExecuted = true;
  if (!fn) {
    return iterateNormalLoaders(options, loaderContext, args, callback);
  }

  convertArgs(args, currentLoaderObject.raw);

  //執行 loader 函數
  runSyncOrAsync(fn, loaderContext, args, function(err) {
    //loader 執行結果的回調
    if (err) return callback(err);

    var args = Array.prototype.slice.call(arguments, 1); // arg:[] 爲 loader 轉換結果(字符串或者buffer+可能有的sourcemap)
    iterateNormalLoaders(options, loaderContext, args, callback); //遞歸,並將轉換結果一併傳入
  });
}

//同步或者異步執行 loader 函數
function runSyncOrAsync(fn, context, args, callback) {
  var isSync = true;
  var isDone = false;
  var isError = false; // internal error
  var reportedError = false;
  //異步處理
  context.async = function async() {
    if (isDone) {
      if (reportedError) return; // ignore
      throw new Error('async(): The callback was already called.');
    }
    isSync = false;
    return innerCallback;
  };
  // 異步後會執行此方法,loader 的結果會做爲參數傳導出來
  var innerCallback = (context.callback = function() {
    if (isDone) {
      if (reportedError) return; // ignore
      throw new Error('callback(): The callback was already called.');
    }
    isDone = true;
    isSync = false;
    try {
      callback.apply(null, arguments); // arguments 爲 loader 結果,第一個值爲 null 第二個爲字符串或者 buffer,第三個爲 SourceMap
    } catch (e) {
      //...
    }
  });
  try {
    var result = (function LOADER_EXECUTION() {
      return fn.apply(context, args); //執行 loader 函數,參數傳遞前一個 loader 的執行結果
    })();
    if (isSync) {
      isDone = true;
      if (result === undefined) return callback();
      if (result && typeof result === 'object' && typeof result.then === 'function') {
        return result.then(function(r) {
          callback(null, r);
        }, callback);
      }
      return callback(null, result);
    }
  } catch (e) {
    //...
  }
}
複製代碼

本章小結

  1. 實例化 NormalModule 獲得初始化的 module,而後在 build 過程當中先 run loader 處理源碼,獲得一個編譯後的字符串或 buffer
  2. run loader 的過程當中,先正序執行了每一個 loaderpitch ,而後倒序執行了每一個 loadernormal
相關文章
相關標籤/搜索