Skip to content

factorizeModule

make阶段第一步做的事是通过factorizeModuledependency创建成相应的module

_factorizeModule方法

找到_factorizeModule方法的定义:

javascript
_factorizeModule({ factory }) {
  factory.create(
    {
      // ...
    },
    (err, result) => {
      // ...
    }
  );
}
_factorizeModule({ factory }) {
  factory.create(
    {
      // ...
    },
    (err, result) => {
      // ...
    }
  );
}

该方法实际上调用的是factory方法。factory方法是一个工厂函数,用于创建module。这里以NormalModuleFactory为例。找到webpack/lib/NormalModuleFactory.js文件中create方法的定义,该方法最终会走到hooks.factorize:

javascript
this.hooks.factorize.callAsync(resolveData, (err, module) => {})
this.hooks.factorize.callAsync(resolveData, (err, module) => {})

而在NormalModuleFactoryconstructor中正好定义了factorize.tapAsync,并会执行hooks.resolve.callAsync(),进入到resolve的回调函数。回调函数内主要由两段逻辑,一段是对dependency进行resolve,另一段逻辑是处理loader

getResolver

resolve之前,会先获取处理dependencyresolver

javascript
const normalResolver = this.getResolver(
  "normal",
  dependencyType
  ? cachedSetProperty(
    resolveOptions || EMPTY_RESOLVE_OPTIONS,
    "dependencyType",
    dependencyType
  )
  : resolveOptions
);


getResolver(type, resolveOptions) {
  return this.resolverFactory.get(type, resolveOptions);
}
const normalResolver = this.getResolver(
  "normal",
  dependencyType
  ? cachedSetProperty(
    resolveOptions || EMPTY_RESOLVE_OPTIONS,
    "dependencyType",
    dependencyType
  )
  : resolveOptions
);


getResolver(type, resolveOptions) {
  return this.resolverFactory.get(type, resolveOptions);
}

resolverFactorycompiler对象初始化时就已经实例化。找到webpack/lib/ResolverFactory.js,调用get方法时如果缓存中没有resolver,就会调用_create方法创建一个resolver

javascript
_create(type, resolveOptionsWithDepType) {
  const resolver = /** @type {ResolverWithOptions} */ (Factory.createResolver(
    resolveOptions
  ));
  // ...
  return resolver;
};
_create(type, resolveOptionsWithDepType) {
  const resolver = /** @type {ResolverWithOptions} */ (Factory.createResolver(
    resolveOptions
  ));
  // ...
  return resolver;
};

createResolver

找到node_modules/enhanced-resolve/lib/ResolverFactory.js文件,里面定义了createResolver方法。该方法在实例化Resolver后,定义了一系列的钩子,用于整个dependencyresolve过程。

javascript
resolver.ensureHook("resolve");
resolver.ensureHook("internalResolve");
resolver.ensureHook("newInteralResolve");
resolver.ensureHook("parsedResolve");
resolver.ensureHook("describedResolve");
resolver.ensureHook("internal");
resolver.ensureHook("rawModule");
resolver.ensureHook("module");
resolver.ensureHook("resolveAsModule");
resolver.ensureHook("undescribedResolveInPackage");
resolver.ensureHook("resolveInPackage");
resolver.ensureHook("resolveInExistingDirectory");
resolver.ensureHook("relative");
resolver.ensureHook("describedRelative");
resolver.ensureHook("directory");
resolver.ensureHook("undescribedExistingDirectory");
resolver.ensureHook("existingDirectory");
resolver.ensureHook("undescribedRawFile");
resolver.ensureHook("rawFile");
resolver.ensureHook("file");
resolver.ensureHook("finalFile");
resolver.ensureHook("existingFile");
resolver.ensureHook("resolved");
resolver.ensureHook("resolve");
resolver.ensureHook("internalResolve");
resolver.ensureHook("newInteralResolve");
resolver.ensureHook("parsedResolve");
resolver.ensureHook("describedResolve");
resolver.ensureHook("internal");
resolver.ensureHook("rawModule");
resolver.ensureHook("module");
resolver.ensureHook("resolveAsModule");
resolver.ensureHook("undescribedResolveInPackage");
resolver.ensureHook("resolveInPackage");
resolver.ensureHook("resolveInExistingDirectory");
resolver.ensureHook("relative");
resolver.ensureHook("describedRelative");
resolver.ensureHook("directory");
resolver.ensureHook("undescribedExistingDirectory");
resolver.ensureHook("existingDirectory");
resolver.ensureHook("undescribedRawFile");
resolver.ensureHook("rawFile");
resolver.ensureHook("file");
resolver.ensureHook("finalFile");
resolver.ensureHook("existingFile");
resolver.ensureHook("resolved");

同时,后面还在对应的钩子上应用了一些插件用于resolve。比如:

javascript
// 处理 resolve 缓存
plugins.push(
  new UnsafeCachePlugin(
    source,
    cachePredicate,
    unsafeCache,
    cacheWithContext,
    `new-${source}`
  )
);

// 处理 路径
plugins.push(
  new ParsePlugin(`new-${source}`, resolveOptions, "parsed-resolve")
);

// 处理 描述文件
plugins.push(
  new DescriptionFilePlugin(
    "parsed-resolve",
    descriptionFiles,
    false,
    "described-resolve"
  )
);

// 处理 别名
if (alias.length > 0) {
  plugins.push(new AliasPlugin("normal-resolve", alias, "internal-resolve"));
}

// 等等其他插件...
// 处理 resolve 缓存
plugins.push(
  new UnsafeCachePlugin(
    source,
    cachePredicate,
    unsafeCache,
    cacheWithContext,
    `new-${source}`
  )
);

// 处理 路径
plugins.push(
  new ParsePlugin(`new-${source}`, resolveOptions, "parsed-resolve")
);

// 处理 描述文件
plugins.push(
  new DescriptionFilePlugin(
    "parsed-resolve",
    descriptionFiles,
    false,
    "described-resolve"
  )
);

// 处理 别名
if (alias.length > 0) {
  plugins.push(new AliasPlugin("normal-resolve", alias, "internal-resolve"));
}

// 等等其他插件...

这些插件正好对应了webpack中的resolve配置:webpack resolve 配置

img

resolver.resolve

获取完resolver后会调用resolver.resolve解析dependency,包括文件引用路径解析(比如是绝对路径还是相对路径还是模块,有哪些参数等等),文件路径查找,文件描述文件读取,文件别名替换等等操作。

loader 匹配

解析完当前dependency之后进入到回调函数,这里调用的是continueCallback方法。

javascript
const result = this.ruleSet.exec({
  resource: resourceDataForRules.path,
  realResource: resourceData.path,
  resourceQuery: resourceDataForRules.query,
  resourceFragment: resourceDataForRules.fragment,
  mimetype: matchResourceData ? "" : resourceData.data.mimetype || "",
  dependency: dependencyType,
  descriptionData: matchResourceData
  ? undefined
  : resourceData.data.descriptionFileData,
  issuer: contextInfo.issuer,
  compiler: contextInfo.compiler,
  issuerLayer: contextInfo.issuerLayer || ""
});
const result = this.ruleSet.exec({
  resource: resourceDataForRules.path,
  realResource: resourceData.path,
  resourceQuery: resourceDataForRules.query,
  resourceFragment: resourceDataForRules.fragment,
  mimetype: matchResourceData ? "" : resourceData.data.mimetype || "",
  dependency: dependencyType,
  descriptionData: matchResourceData
  ? undefined
  : resourceData.data.descriptionFileData,
  issuer: contextInfo.issuer,
  compiler: contextInfo.compiler,
  issuerLayer: contextInfo.issuerLayer || ""
});

该方法会对dependencyloader的配置进行匹配,返回该dependency使用到的loaders。随后使用loaderResolver对每个loader进行文件解析,返回loader的文件路径。

javascript
this.resolveRequestArray(
  contextInfo,
  this.context,
  useLoadersPost,
  loaderResolver,
  resolveContext,
  (err, result) => {
    postLoaders = result;
    continueCallback(err);
  }
);
this.resolveRequestArray(
  contextInfo,
  this.context,
  useLoadersPost,
  loaderResolver,
  resolveContext,
  (err, result) => {
    postLoaders = result;
    continueCallback(err);
  }
);

createModule

dependency解析完成后,执行resolve回调:

javascript
this.hooks.factorize.tapAsync(
  {
    name: "NormalModuleFactory",
    stage: 100
  },
  (resolveData, callback) => {
    this.hooks.resolve.callAsync(resolveData, (err, result) => {
      this.hooks.afterResolve.callAsync(resolveData, (err, result) => {
        const createData = resolveData.createData;
        this.hooks.createModule.callAsync(
          createData,
          resolveData,
          (err, createdModule) => {
            if (!createdModule) {
              createdModule = new NormalModule(createData);
            }

            createdModule = this.hooks.module.call(
              createdModule,
              createData,
              resolveData
            );

            return callback(null, createdModule);
          }
        );
      });
    });
  }
);
this.hooks.factorize.tapAsync(
  {
    name: "NormalModuleFactory",
    stage: 100
  },
  (resolveData, callback) => {
    this.hooks.resolve.callAsync(resolveData, (err, result) => {
      this.hooks.afterResolve.callAsync(resolveData, (err, result) => {
        const createData = resolveData.createData;
        this.hooks.createModule.callAsync(
          createData,
          resolveData,
          (err, createdModule) => {
            if (!createdModule) {
              createdModule = new NormalModule(createData);
            }

            createdModule = this.hooks.module.call(
              createdModule,
              createData,
              resolveData
            );

            return callback(null, createdModule);
          }
        );
      });
    });
  }
);

最终会通过createdModule = new NormalModule(createData)创建一个module,并将该module作为回调函数的参数传递给下一阶段,也就是addModule阶段。

总结

factorizeModule调用xxxModuleFactory.create方法将dependency转化成module

在这个过程中会实例化resolver,通过不同的resolverdependency进行解析,包括文件路径,文件描述,文件别名等等进行解析。

解析完成后与配置的loader匹配规则进行匹配。如果与该dependency匹配成功,那么会使用loaderResolver解析loader文件路径,存放到createData.loaders当中。

最后根据解析好的createData创建一个module