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
:

经过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.j
s中的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结束