webpack 4 源码主流程分析(六):构建 module(上)

初始化 module

接上文,在 resolver 函数回调里,触发 normalModuleFactory.hooks:afterResolve 之后,回调里执行:

let createdModule = this.hooks.createModule.call(result); // result 即为 resolver 返回的组合对象 dataif (!createdModule) {
  if (!result.request) {
    returncallback(newError('Empty dependency (no request)'));
  }

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

returncallback(null, createdModule);
复制代码

这里触发 normalModuleFactory.hooks:createModule,如果钩子里没有项目配置的自定义 module,则使用 webpack 生成的 module。

得到 module 实例,接着触发 normalModuleFactory.hooks:module 之后,跳出 factory 函数,执行 factory 函数回调进行依赖缓存后,跳出 create 函数执行 moduleFactory.create 的回调。回调里执行:

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 提供上下文环境并共享,然后调用了 runLoaders:

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

loader-runner

该方法来自 loader-runner,通过各种 loader 处理源码后,得到一个处理后的 string 或 buffer(可能还有个 sourcemap)。

还可以解析自定义 loader编写一个 loader

主要流程为:

runLoaders -> iteratePitchingLoaders(按正序 require 每个 loader) -> loadLoader(对应的 loader 导出的函数赋值到 loaderContext.loader[].normal、pitch函数赋值到loaderContext.loader[].pitch,然后执行pitch函数(如果有的话)) -> processResource(转换 buffer 和设置 loaderIndex) -> iterateNormalLoaders(倒序执行所有 loader)-> runSyncOrAsync(同步或者异步执行 loader)

pitch 函数

  • 每个 loader 可以挂载一个 pitch 函数,该函数主要是用于利用 module 的 request,来提前做一些拦截处理的工作,并不实际处理 module 内容文档

  • 正序 requireloader 并执行其 pitch 方法( loadLoader 里),在执行后的回调里,如果有除了 err 的参数还有其他参数,则执行 iterateNormalLoaders 越过剩下的未 require 的 loader 直接进入到执行 loader 的步骤。如果想没有其他参数,则执行 iteratePitchingLoaders 进行下个 loader 的 require。如代码所示:

if (args.length > 0) {
  loaderContext.loaderIndex--;
  iterateNormalLoaders(options, loaderContext, args, callback);
} else {
  iteratePitchingLoaders(options, loaderContext, callback);
}
复制代码
  • 倒序执行每个 loader 的 normal 方法 。

核心代码解析

// 该方法按正序 require 每个 loaderfunctioniteratePitchingLoaders(options, loaderContext, callback) {
  // abort after last loader 读取所有 loader 后,执行 processResource 方法if (loaderContext.loaderIndex >= loaderContext.loaders.length) returnprocessResource(options, loaderContext, callback);

  var currentLoaderObject = loaderContext.loaders[loaderContext.loaderIndex]; //选择第一个  loader// iterate 增序后递归读取下一个 loaderif (currentLoaderObject.pitchExecuted) {
    loaderContext.loaderIndex++;
    returniteratePitchingLoaders(options, loaderContext, callback);
  }

  // load loader module 加载该 loader 模块// 对应的 loader 导出的函数赋值到 loaderContext.loader[].normalloadLoader(currentLoaderObject, function (err) {
    if (err) {
      loaderContext.cacheable(false);
      returncallback(err);
    }
    var fn = currentLoaderObject.pitch; //loadLoader 里会把 module 赋值到 loader.normal, pitch 赋值到 loader.pitch
    currentLoaderObject.pitchExecuted = true;
    if (!fn) returniteratePitchingLoaders(options, loaderContext, callback);

    // 如果有的话,开始执行 pitch 函数,根据参数情况决定是否继续读取剩下的loaderrunSyncOrAsync(fn, loaderContext, [loaderContext.remainingRequest, loaderContext.previousRequest, (currentLoaderObject.data = {})], function (err) {
      if (err) returncallback(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 和设置 loaderIndexfunctionprocessResource(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) returncallback(err);
      options.resourceBuffer = buffer; //得到bufferiterateNormalLoaders(options, loaderContext, [buffer], callback);
    });
  } else {
    iterateNormalLoaders(options, loaderContext, [null], callback);
  }
}

//倒序执行所有 loaderfunctioniterateNormalLoaders(options, loaderContext, args, callback) {
  if (loaderContext.loaderIndex < 0) returncallback(null, args); //执行完所有 loader 后退出,去执行 iteratePitchingLoaders 回调即 runLoaders 的回调var currentLoaderObject = loaderContext.loaders[loaderContext.loaderIndex]; //获取对应 loader// iterate 减序后递归执行下一个 loaderif (currentLoaderObject.normalExecuted) {
    loaderContext.loaderIndex--;
    returniterateNormalLoaders(options, loaderContext, args, callback);
  }

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

  convertArgs(args, currentLoaderObject.raw);

  //执行 loader 函数runSyncOrAsync(fn, loaderContext, args, function (err) {
    //loader 执行结果的回调if (err) returncallback(err);

    var args = Array.prototype.slice.call(arguments, 1); // arg:[] 为 loader 转换结果(字符串或者buffer+可能有的sourcemap)iterateNormalLoaders(options, loaderContext, args, callback); //递归,并将转换结果一并传入
  });
}

//同步或者异步执行 pitch/loader 函数functionrunSyncOrAsync(fn, context, args, callback) {
  var isSync = true;
  var isDone = false;
  var isError = false; // internal errorvar reportedError = false;
  //异步处理
  context.async = functionasync() {
    if (isDone) {
      if (reportedError) return; // ignorethrownewError('async(): The callback was already called.');
    }
    isSync = false;
    return innerCallback;
  };
  // 异步后会执行此方法,loader 的结果会作为参数传导出来var innerCallback = (context.callback = function () {
    if (isDone) {
      if (reportedError) return; // ignorethrownewError('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 = (functionLOADER_EXECUTION() {
      return fn.apply(context, args); //*** 入口:执行 loader 函数,参数传递前一个 loader 的执行结果  ***
    })();
    if (isSync) {
      isDone = true;
      if (result === undefined) returncallback();
      if (result && typeof result === 'object' && typeof result.then === 'function') {
        return result.then(function (r) {
          callback(null, r);
        }, callback);
      }
      returncallback(null, result);
    }
  } catch (e) {
    //...
  }
}
复制代码

本章小结

  1. 实例化 NormalModule 得到初始化的 module(方法链:moduleFactory.create 回调里->buildModule->module.build->module.doBuild->runLoaders),然后在 build 过程中先 run loader 处理源码,得到一个编译后的字符串或 buffer。

  1. 在 run loader 的过程中,先正序执行了每个 loader 的 pitch ,然后倒序执行了每个 loader 的 normal。

作者:Korey

链接:https://juejin.cn/post/6844904047229550605

来源:稀土掘金

著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值