webpack源码-loader的原理

版本

webpack :"version": "3.12.0",

webpack配置中的loaders配置是如何传递的

webpack/lib/NormalModuleFactory.js

//从webpack的参数中获取自定义的所有loaders 
this.ruleSet = new RuleSet(options.rules || options.loaders); 

经过ruleSet.exec处理找到处理当前模块的loader

const result = this.ruleSet.exec({
   resource: resourcePath,
   resourceQuery,
   issuer: contextInfo.issuer,
   compiler: contextInfo.compiler
});

到此处,result中的loader字段的值仍为babel-loader:
FE0E25CF-5772-48EC-B6DD-7517DC4A6D71
经过compiler.resolvers.loader处理之后变成了
/Users/cc/killer/webpack-L/wb/node_modules/babel-loader/lib/index.js

处理代码如下:

asyncLib.parallel([
   this.resolveRequestArray.bind(this, contextInfo, this.context, useLoadersPost, this.resolvers.loader),
   this.resolveRequestArray.bind(this, contextInfo, this.context, useLoaders, this.resolvers.loader),
   this.resolveRequestArray.bind(this, contextInfo, this.context, useLoadersPre, this.resolvers.loader)
]

....

经过多次回调传递

到达钩子factory 然后创建新的模块:new NormalModule(..,loader,..)

执行一系列回调函数

-->触发before-resolve NormalModuleFactory.js
-->触发factory NormalModuleFactory.js
-->触发resolver NormalModuleFactory.js
-->执行this.buildModule Compilation.js
-->执行module.build NormalModule.js
-->doBuild回调函数 NormalModule.js
到达runLoaders,执行LoaderRunner.js中的runLoaders方法
执行 iteratePitchingLoaders,该方法是个递归函数,先处理pitch阶段,然后再处理normal阶段

先加载loader的代码
加载:
require(loader.path)
定义currentLoaderObject的normal:
loader.normal = typeof module === "function" ? module : module.default; // normal阶段的执行方法
定义currentLoaderObject的pitch:
loader.pitch = module.pitch;
pitch阶段

loader-runner/blob/master/lib/LoaderRunner.js

获取loader提供的pitch方法:
var fn = currentLoaderObject.pitch;
调用pitch:
runSyncOrAsync(fn,loaderContext,[loaderContext.remainingRequest..],callback)
递归调用:
iteratePitchingLoaders
pitch阶段不进行读取资源文件(readResource),而是向loader传递了 remainingRequest,previousRequest等参数。比如在vue-style-loader仅接受了remainingRequest参数。
normal阶段

loader-runner/blob/master/lib/LoaderRunner.js

如果 loaderContext.loaderIndex >= loaderContext.loaders.length,进入normal阶段
根据loaderContext.resourcePath读取资源内容:
    options.readResource(resourcePath)
    iterateNormalLoaders递归调用

loader.raw = module.raw; //如果raw配置了,根据raw是否转为buffer
runSyncOrAsync方法是真正调用loader的api并完成转换的地方:

function runSyncOrAsync(fn, context, args, callback) {
    ......
    try {
      var result = (function LOADER_EXECUTION() {
          return fn.apply(context, args);
        }());
    } catch(e) {......}
}

结束之后通过层层回调,回到normalModule.js的runLloader的回调函数处,紧接着把转译之后内容复制给模块的_source属性,然后调用模块源码的解析,分析资源依赖:

this.parser.parse(this._source.source(), {
  current: this,
  module: this,
  compilation: compilation,
  options: options
});

根据资源类型使用对应的loader,直至make结束

posted @ 2019-10-25 15:27  空山与新雨  阅读(651)  评论(0编辑  收藏  举报