头图

webpack5 源码详解 - 编译模块

Hazlank

构建模块

上一篇讲了关于webpack初始化做了哪些工作,之后会调用make hook分步骤进行处理模块。

Make

make hooks注册了EntryPlugin,它会调用compilation.addEntry处理入口模块

//Compiler.js
this.hooks.make.callAsync(compilation, err=> {
    //...
})

//EntryPlugin.js
const { entry, options, context } = this;
const dep = EntryPlugin.createDependency(entry, options);

compiler.hooks.make.tapAsync("EntryPlugin", (compilation, callback) => {
    //context是运行目录,dep是 Entrydependecy,包含name, 入口路径
    compilation.addEntry(context, dep, options, err => {
        callback(err);
    });
});

首先会创建依赖,这个依赖叫EntryDependency,之后会将dep,options,context作为传参到compilation.addEntry

addEntry

//Compilation.js

addEntry(context, entry, optionsOrName, callback) {
  //如果options不为对象就转换成对象
  const options =
    typeof optionsOrName === "object"
      ? optionsOrName
      : { name: optionsOrName };

  this._addEntryItem(context, entry, "dependencies", options, callback);
}

_addEntryItem(context, entry, target, options, callback) {
    const { name } = options;
  //如果从entries拿不到就会创建entryData
    let entryData =
        name !== undefined ? this.entries.get(name) : this.globalEntry;
    if (entryData === undefined) {
        entryData = {
            dependencies: [],
            includeDependencies: [],
            options: {
                name: undefined,
                ...options
            }
        };
    //target: dependencies | includeDependencies
        entryData[target].push(entry);
    //保存到Map
        this.entries.set(name, entryData);
    } else {
        //..
    }
    
this.addModuleTree(//...);
}

因为支持MPA(multiple entry points),所以会判断有无设置过的entry。因为配置的时候是只有一个入口为entry: "./index.js",所以在前面处理options时会转成对象,name为"main"

const getNormalizedEntryStatic = entry => { 
    if (typeof entry === "string") { 
        return { main: { import: [entry] }
    } 
};

addModuleTree

保存完EntryData后就会执行addModuleTree



addModuleTree({ context, dependency, contextInfo }, callback) {
    const moduleFactory = this.dependencyFactories.get(Dep);
    
    this.handleModuleCreation(
        {
            factory: moduleFactory,
            dependencies: [dependency],
            originModule: null,
            contextInfo,
            context
        },
        (err, result) => {
      //...
        }
    );
}

首先会拿到对应的moduleFactory,它可以为NormalModuleFactoryContextModuleFactory 。不同的依赖对应不同的处理方式 比如EntryDependency和ImportDependency对应NormalModuleFactory, AMDRequireContextDependency 对应ContextModuleFactory ,它们都是之前初始化插件的时候注入到dependencyFactories里的。

img

handleModuleCreation

得到对应的moduleFactory后,handleModuleCreation将正式开始处理模块了,包括后续递归处理引入的外部依赖也是调用它

handleModuleCreation(
    {
        factory,
        dependencies,
        originModule,
        contextInfo,
        context,
        recursive = true,
        connectOrigin = recursive
    },
    callback
) {
    const moduleGraph = this.moduleGraph;

    this.factorizeModule(
        {
            currentProfile,
            factory,
            dependencies,
            factoryResult: true,
            originModule,
            contextInfo,
            context
        },
        () => {
            //...
        }
    )
}

factorizeModule =  (
  function (options, callback) {
    this.factorizeQueue.add(options, callback);
  }
);

moduleGraph是个实例化对象,用于保存依赖,模块之间的引用信息等,用以后续的分析。

moduleGraph: {
  /** @type {WeakMap<Dependency, ModuleGraphConnection>} */
    _dependencyMap:WeakMap

  /** @type {Map<Module, ModuleGraphModule>} */
    _moduleMap:Map(0) {size: 0}
  //...
}

factorizeModule会调用this.factorizeQueue.add,用来解析模块,它是一个AsyncQueue。 Compilation包含几个AsyncQueue用来分步骤处理模块,每一个AsyncQueue就是一个task

AsyncQueue

Compilation为模块不同的操作分别定义了异步队列,队列提供了很多方法,比如可以判断队列是否在运行,停止,清除队列等, 可以传入parallelism控制并行运行的任务数量等(用于微调性能或者让处理模块时序正确保证分析结果)。AsyncQueue是webpack5才有的代码,Commit message如下

add queues to Compilation

remove Semaphore and use AsyncQueue instead
deprecate Module.needRebuild, add Module.needBuild
remove Module.unbuild
add Module.invalidateBuild

//AsyncQueue

//处理模块内的外部依赖
this.processDependenciesQueue = new AsyncQueue({
        name: "processDependencies",
        parallelism: options.parallelism || 100,
        processor: this._processModuleDependencies.bind(this)
    });

//添加依赖到对应的moduleGraph
this.addModuleQueue = new AsyncQueue({
    name: "addModule",
    parent: this.processDependenciesQueue,
    getKey: module => module.identifier(),
    processor: this._addModule.bind(this)
});

//解析模块信息
this.factorizeQueue = new AsyncQueue({
    name: "factorize",
    parent: this.addModuleQueue,
    processor: this._factorizeModule.bind(this)
});

//编译模块
this.buildQueue = new AsyncQueue({
    name: "build",
    parent: this.factorizeQueue,
    processor: this._buildModule.bind(this)
});

//重构建
this.rebuildQueue = new AsyncQueue({
    name: "rebuild",
    parallelism: options.parallelism || 100,
    processor: this._rebuildModule.bind(this)
});

this.factorizeQueue.add会调用AsyncQueue的add方法。接着创建AsyncQueueEntry保存在AsyncQueue的_entries和_queued里。当可以运行时,会执行setImmediate(root._ensureProcessing)。

//AsyncQueue.js
add(item, callback) {
    if (this._stopped) return callback(new WebpackError("Queue was stopped"));
    this.hooks.beforeAdd.callAsync(item, err => {
        //实例化AsyncQueueEntry
        const newEntry = new AsyncQueueEntry(item, callback);
        if (this._stopped) {
            //...
        } else {
            this._entries.set(key, newEntry);
            this._queued.enqueue(newEntry);
            const root = this._root;
            root._needProcessing = true;
            if (root._willEnsureProcessing === false) {
                root._willEnsureProcessing = true;
                setImmediate(root._ensureProcessing);
            }
            this.hooks.added.call(item);
        }
    });
}

_ensureProcessing() {
      //...
  this._willEnsureProcessing = false;
  if (this._queued.length > 0) return;
  if (this._children !== undefined) {
  //_children是根据实例化AysncQueue的传参parent生成的

  //_children: Array<addModuleQueue , factorizeQueue , buildQueue >
  for (const child of this._children) {
    while (this._activeTasks < this._parallelism) {

      //将队列任务取出
      const entry = child._queued.dequeue();  
      if (entry === undefined) break;
      this._activeTasks++;
      entry.state = PROCESSING_STATE;
      
      //将entry丢进_startProcessing里
      child._startProcessing(entry);
    }
    if (child._queued.length > 0) return;
    }
  }
  if (!this._willEnsureProcessing) this._needProcessing = false;
}

_startProcessing(entry) {
  this.hooks.beforeStart.callAsync(entry.item, err => {
      if (err) {
        //...
      }
      let inCallback = false;
      try {
          //Async.add 最终会调用Async._processor
          this._processor(entry.item, (e, r) => {
              inCallback = true;
              this._handleResult(entry, e, r);
          });
      } catch (err) {
          //...
      }
      this.hooks.started.call(entry.item);
    });
}

当执行_ensureProcessing的时候,会循环将队列拿出来,因为我们调用的是factorizeQueue,所以第一次循环取出来的addModule的队列是空的会被跳过。所以会执行factorizeQueue的_processor, 它是this._factorizeModule.bind(this)

factorizeModule

factorizeModule用于resolve模块信息,比如相对,绝对路径,packjson内容等。

_factorizeModule(
  {
    currentProfile,
    factory,
    dependencies,
    originModule,
    factoryResult,
    contextInfo,
    context
  },
  callback
) {
  //因为EntryDdependency对应的是NormalModuleFactory,所以调用NormalModuleFactory.create
  factory.create(
    {
      contextInfo: {
        issuer: originModule ? originModule.nameForCondition() : "",
        issuerLayer: originModule ? originModule.layer : null,

        compiler: this.compiler.name,
        ...contextInfo
      },
      resolveOptions: originModule ? originModule.resolveOptions : undefined,
      context: context
        ? context
        : originModule
        ? originModule.context
        : this.compiler.context,
      dependencies: dependencies
    },
    (err, result) => {
      //...
      callback(null, factoryResult ? result : result.module);
    }
  );
}

第一步会先调用factory.create,然后将结果传回去,originModule表示当前模块是被谁引用的,因为处理的是入口文件,所以originModule为undefiend。所以factory.created的参数只用关注dependencies为Array<EntryDependency>,context为程序运行目录。

NormalModuleFactory.create

create(data, callback) {
  //dependencies: Array<EntryDependency>
  const dependencies = /** @type {ModuleDependency[]} */ (data.dependencies);
  //运行目录
  const context = data.context || this.context;
  //resolve配置
  const resolveOptions = data.resolveOptions || EMPTY_RESOLVE_OPTIONS;
  //依赖
  const dependency = dependencies[0];
  //request: "/index.js"
  const request = dependency.request;
  const assertions = dependency.assertions;
  const contextInfo = data.contextInfo;
  //依赖类型 如:esm
  const dependencyType = (dependencies.length > 0 && dependencies[0].category) || "";

  //解析数据
  const resolveData = {
    contextInfo,
    resolveOptions,
    context,
    request,
    assertions,
    dependencies,
    dependencyType,
    createData: {},
    cacheable: true
  };

  this.hooks.beforeResolve.callAsync(resolveData, (err, result) => {
    //...
    this.hooks.factorize.callAsync(resolveData, (err, module) => {
      const factoryResult = {
        module,
        cacheable: resolveData.cacheable
      };

      callback(null, factoryResult);
    });
  });
}

create会初始化属性并调用钩子this.hooks.factorize.callAsync一共有两个插件。第一个是ExternalModuleFactoryPlugin,会对external配置的模块进行处理。第二个插件的名字叫NormalModuleFactory,他是之前实例化NormalModuleFactory时注册进来的

class NormalModuleFactory {
    constructor () {
    //...

    //Compiler.js实例化NormalModuleFactory的时候注册
    this.hooks.factorize.tapAsync(
      {
        name: "NormalModuleFactory",
        stage: 100
      },
      (resolveData, callback) => {
        //调用resolve钩子
        this.hooks.resolve.callAsync(resolveData, (err, result) => {
          //...
        });
      }
    );
   //...
      
  this.hooks.resolve.tapAsync(...)
    }
    //...
}

在实例化NormalModuleFactory的时候会注册两个插件,一个在factorize阶段,一个在resolve阶段。factorize钩子会调用resolve钩子。resolve下注册的hook代码如下

NormalModuleFactory.hooks.resolve

this.hooks.resolve.tapAsync(
  {
    name: "NormalModuleFactory",
    stage: 100
  },
  (data, callback) => {
    const {
      contextInfo,
      context,
      dependencies,
      dependencyType,
      request,
      assertions,
      resolveOptions,
    } = data;
    //获取loader的解析器
    const loaderResolver = this.getResolver("loader");

    /** @type {ResourceData | undefined} */
    let matchResourceData = undefined;
    /** @type {string} */
    let unresolvedResource;
    /** @type {ParsedLoaderRequest[]} */
    let elements;
    let noPreAutoLoaders = false;
    let noAutoLoaders = false;
    let noPrePostAutoLoaders = false;

    //Scheme表示为URL方案,data,file等
    const contextScheme = getScheme(context);
    /** @type {string | undefined} */
    let scheme = getScheme(request);
    
    if (!scheme) {
      //...
    }
    //...
    
  }
);

首先会对context和request调用getScheme。context是执行目录,request就是我们的入口文件/index.js。getScheme会解析对应的字符串是否是某些URL方案。比如可能是file:///user/webpack/index.js或者data:text/javascript;base64...,如果是的话会丢到相应的dataUrlPluginfileUrlPlugin处理

Get scheme if specifier is an absolute URL specifier

继续resolve hook之后的代码

//this.hooks.resolve.tapAsync

if (!scheme) {
    /** @type {string} */
    
    let requestWithoutMatchResource = request;
    //...

    scheme = getScheme(requestWithoutMatchResource);

    if (!scheme && !contextScheme) {
    //判断是否有inline-loader
        const firstChar = requestWithoutMatchResource.charCodeAt(0);
        const secondChar = requestWithoutMatchResource.charCodeAt(1);
        noPreAutoLoaders = firstChar === 45 && secondChar === 33; // startsWith "-!"
        noAutoLoaders = noPreAutoLoaders || firstChar === 33; // startsWith "!"
        noPrePostAutoLoaders = firstChar === 33 && secondChar === 33; // startsWith "!!";
        const rawElements = requestWithoutMatchResource
            .slice(
                noPreAutoLoaders || noPrePostAutoLoaders
                    ? 2
                    : noAutoLoaders
                    ? 1
                    : 0
            )
            .split(/!+/);
        unresolvedResource = rawElements.pop();
        elements = rawElements.map(el => {
            const { path, query } = cachedParseResourceWithoutFragment(el);
            return {
                loader: path,
                options: query ? query.slice(1) : undefined
            };
        });
        scheme = getScheme(unresolvedResource);
    } else {
        unresolvedResource = requestWithoutMatchResource;
        elements = EMPTY_ELEMENTS;
    }
} else {
    unresolvedResource = request;
    elements = EMPTY_ELEMENTS;
}

如果没有scheme,会判断是否含有inline-loader,如果有会被拆分开来。比如!style-loader!css-loader?modules!./styles.css会拆成如下对象,然后丢进resolveRequestArray进行相关处理

[
    {
        loader: 'style-loader', options: undefined
    },
    {
        loader: 'css-loader', options: 'modules'
    }
]

接着会调用defaultResolve(context)生成模块解析器。

//this.hooks.resolve

if (scheme) {
    //...
}

else if (contextScheme) {
    //...
}

// resource without scheme and without path
defaultResolve(context);

const defaultResolve = context => {
    if (/^($|\?)/.test(unresolvedResource)) {
        //...
    }

    // resource without scheme and with path
    else {
        //创建解析器
        const normalResolver = this.getResolver(
            "normal",
            //dependencyType: "esm"
            dependencyType
                ? cachedSetProperty(
                        resolveOptions || EMPTY_RESOLVE_OPTIONS,
                        "dependencyType",
                        dependencyType
                        )
                : resolveOptions
        );
        
        this.resolveResource(
          contextInfo,
          context,
          unresolvedResource,
          normalResolver,
          resolveContext,
          (err, resolvedResource, resolvedResourceResolveData) => {
              //...
              continueCallback();
          });
    }
};

this.getResolver会调用ResolverFactory生成解析器,解析器用于解析文件绝对路径等信息。

ResolverFactory内部的功能扩展于enhanced-resolve。enhanced-resolve是一个高度可配置的resolve库,比如我们经常配置resolve.alias用别名代替某些路径,配置resolve.extensions用于扩展名等。

根据getResolver传进来的参数会实例化enhanced-resolve并调用resolverFactory的钩子合并options

//WebpackOptionsApply.js

//getResolver('normal', 'esm')会生成Resolver,然后触发hooks进行合并用户配置的options.resolve

compiler.resolverFactory.hooks.resolveOptions
    .for("normal")
    .tap("WebpackOptionsApply", resolveOptions => {
        resolveOptions = cleverMerge(options.resolve, resolveOptions);
        resolveOptions.fileSystem = compiler.inputFileSystem;
        return resolveOptions;
    });
compiler.resolverFactory.hooks.resolveOptions
    .for("context")
    .tap("WebpackOptionsApply", resolveOptions => {
        //...
    });
compiler.resolverFactory.hooks.resolveOptions
    .for("loader")
    .tap("WebpackOptionsApply", resolveOptions => {
        //...
    });

接着调用this.resolveResource,它会调用刚刚生成的resolver解析模块

resolveResource(
        contextInfo,
        context,
        unresolvedResource,
        resolver,
        resolveContext,
        callback
    ) {
    resolver.resolve(
        contextInfo,
        context,
        unresolvedResource,
        resolveContext,
        (err, resolvedResource, resolvedResourceResolveData) => {
            if (err) {
                //...
            }
            callback(err, resolvedResource, resolvedResourceResolveData);
        }
    );
}

resolver.resolve会得到resolvedResource和resolvedResourceResolveData。

resolvedResource为c:\\Users\\Administrator\\Desktop\\webpack\\index.js,是执行webpack的入口文件的绝对路径。resolvedResourceResolveData会包含一些解析相关信息。

img

接着回到this.resolveResource回调执行continueCallback函数

const continueCallback = needCalls(2, err => {
    //...
    const settings = {};
    const useLoadersPost = [];
    const useLoaders = [];
    const useLoadersPre = [];

    // handle .webpack[] suffix
    let resource;
    let match;
    if (
        //...
    ) {
        //...
    } else {
        settings.type = "javascript/auto";
        const resourceDataForRules = matchResourceData || resourceData;
        const result = this.ruleSet.exec({
      //模块路径
            resource: resourceDataForRules.path,
            realResource: resourceData.path,
            resourceQuery: resourceDataForRules.query,
            resourceFragment: resourceDataForRules.fragment,
      //url方案
            scheme,
            assertions,
      //模块的mimetype
            mimetype: matchResourceData
                ? ""
                : resourceData.data.mimetype || "",
      //依赖类型
            dependency: dependencyType,
      //模块的描述文件,列如packjson.js
            descriptionData: matchResourceData
                ? undefined
                : resourceData.data.descriptionFileData,
      
      //模块发起者
            issuer: contextInfo.issuer,
            compiler: contextInfo.compiler,
            issuerLayer: contextInfo.issuerLayer || ""
        });
    //loader类型
        for (const r of result) {
            if (r.type === "use") {
                if (!noAutoLoaders && !noPrePostAutoLoaders) {
                    useLoaders.push(r.value);
                }
            } else if (r.type === "use-post") {
                if (!noPrePostAutoLoaders) {
                    useLoadersPost.push(r.value);
                }
            } else if (r.type === "use-pre") {
                //...
            } else if (
                //...
            ) {
                //...
            }
        }
    }

    let postLoaders, normalLoaders, preLoaders;

    //...
    this.resolveRequestArray(
        //...
        useLoadersPost,
        (err, result) => {
            //...
        }
    );

    this.resolveRequestArray(
        contextInfo,
        this.context,
        useLoaders,
        loaderResolver,
        resolveContext,
        (err, result) => {
            normalLoaders = result;
            continueCallback(err);
        }
    );

    this.resolveRequestArray(
        //...
        (err, result) => {
            //...
        }
    );
    
    //
    defaultResolve = () {
        //...
    }
});

首先会执行this.ruleSet.exec,之前的初始化篇讲过它是用来根据资源输出对应匹配的loader。解析出来的结果有三种类型,use,use-postuse-pre,然后放置在对应的useLoadersPost, useLoadersuseLoadersPre

在webpack1的时候有相应的配置,可以在loader处理之前或之后执行其他 预/后 处理loader。但是在v2后就被移除了。

之后会对每个loaders Array进行resolveRequestArray处理,因为没有preLoader和postLoader,所以我们只用关注中间的resolveRequestArray

resolveRequestArray(
        contextInfo,
        context,
        array,
        resolver,
        resolveContext,
        callback
    ) {
  if (array.length === 0) return callback(null, array);
  //异步库
  asyncLib.map(
    array,
    (item, callback) => {
      //调用解析器的resolve方法
      resolver.resolve(
        contextInfo,
        context,
        item.loader,
        resolveContext,
        (err, result) => {
          //...
          const parsedResult = this._parseResourceWithoutFragment(result);
          const resolved = {
            loader: parsedResult.path,
            options:
              item.options === undefined
                ? parsedResult.query
                  ? parsedResult.query.slice(1)
                  : undefined
                : item.options,
            ident: item.options === undefined ? undefined : item.ident
          };
          return callback(null, resolved);
        }
      );
    },
    callback
  );
}

async是一个异步库,map方法会循环array,然后把每个item给resolver.resolve执行,最后调用callback。resolver是之前调用的 this.getResolver("loader")创建的LoaderResolver,resolver解析loader相应的绝对路径,比如传进去的为babel-loader, 出来的就是node_modules包里入口地址C:\\Users\\Administrator\\Desktop\\webpack\\node_modules\\.pnpm\\babel-loader@8.2.3_ed870ac3ba52c4ec230ba2bc3dbb311c\\node_modules\\babel-loader\\lib\\index.js。然后将结果回调给continueCallback

const continueCallback = needCalls(3, err => {
    if (err) {
        return callback(err);
    }
    const allLoaders = postLoaders;
    if (matchResourceData === undefined) {
        for (const loader of loaders) allLoaders.push(loader);
        for (const loader of normalLoaders) allLoaders.push(loader);
    } else {
        for (const loader of normalLoaders) allLoaders.push(loader);
        for (const loader of loaders) allLoaders.push(loader);
    }
    for (const loader of preLoaders) allLoaders.push(loader);
    let type = settings.type;
    const resolveOptions = settings.resolve;
    const layer = settings.layer;
    if (layer !== undefined && !layers) {
        return callback(
            new Error(
                "'Rule.layer' is only allowed when 'experiments.layers' is enabled"
            )
        );
    }
    try {
        Object.assign(data.createData, {
            layer: layer === undefined ? contextInfo.issuerLayer || null : layer,                //资源发起者
            request: stringifyLoadersAndResource(allLoaders, resourceData.resource),        //loader绝对路径和模块资源绝对路径
            userRequest,                                                                                        //用户请求绝对路径
            rawRequest: request,                                                                            //未经处理的请求路径
            loaders: allLoaders,                                                                                //资源所需要的所有loader
            resource: resourceData.resource,                                                        //资源绝对路径        
            context: resourceData.context || getContext(resourceData.resource),
            matchResource: matchResourceData ? matchResourceData.resource : undefined,
            resourceResolveData: resourceData.data,            //enhance-resolve返回的数据
            settings,                                                
            type,                                    //资源类型
            parser: this.getParser(type, settings.parser),            //根据类型和设置返回的Parser
            parserOptions: settings.parser,
            generator: this.getGenerator(type, settings.generator),                    //根据类型和设置返回的generator
            generatorOptions: settings.generator,
            resolveOptions
        });
    } catch (e) {
        return callback(e);
    }
    callback();
});

img

continueCallback会整合所有之前resolve的数据(资源信息,loader信息)到createData

createData还会初始化对应的parser和generator,对于不同的type会有不同的parser和generator,用于webpack处理不同的资源和输出不同代码

Parser

JavascriptParser

  • javascript/auto
  • javascript/esm
  • javascript/dynamic

AssetParser

  • asset
  • asset/inline
  • asset/resource
  • asset/source

JsonParser

WebAssemblyParser

CssParser

Generator

  • JavascriptGenerator
  • JsonGenerator
  • WebAssemblyGenerator
  • CssGenerator

webpack5也允许对ParserGenerator进行配置以获得某些功能或者更改输出配置

NormalModuleFactory.hooks.factorize

至此,resolve hooks的工作都做完了,将会回到factorize hooks。

// this.hooks.factorize.tapAsync

 this.hooks.resolve.callAsync(resolveData, (err, result) => {
    //resolveData: createData

        //...
      this.hooks.afterResolve.callAsync(resolveData, (err, result) => {
          if (err) return callback(err);

          //enhanse-resolve解析的数据
          const createData = resolveData.createData;

          this.hooks.createModule.callAsync(
              createData,
              resolveData,
              (err, createdModule) => {
                  //...

                    //实例化NormalModule
                    createdModule = new NormalModule(
                        /** @type {NormalModuleCreateData} */ (createData)
                    );
                }                

          //调用插件判断是否配置sideEffects
                  createdModule = this.hooks.module.call(
                      createdModule,
                      createData,
                      resolveData
                  );

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

之后便会根据createData实例化NormalModule,这样NormalModule就保存了当前模块的解析信息。一个模块会对应一个NormalModule,之后模块相关的操作都会在NormalModule进行。

this.hooks.module.call会有两个插件 ,一个判断packjson的有无sideEffects 配置,一个判断Rules有无sideEffects 配置。对模块设置sideEffects能够更友好的让webpack进行tree shaking

// SideEffectsFlagPlugin

//package.json存在sideEffects就进行相关设置
(module, data) => {
  //resolve数据
    const resolveData = data.resourceResolveData;
    if (
        resolveData &&
        resolveData.descriptionFileData &&
        resolveData.relativePath
    ) {
    //packjson.sideEffects
        const sideEffects = resolveData.descriptionFileData.sideEffects;
        if (sideEffects !== undefined) {
            if (module.factoryMeta === undefined) {
                module.factoryMeta = {};
            }
            const hasSideEffects =
                SideEffectsFlagPlugin.moduleHasSideEffects(
                    resolveData.relativePath,
                    sideEffects,
                    cache
                );
            module.factoryMeta.sideEffectFree = !hasSideEffects;
        }
    }

    return module;
}

//Rules里包含sideEffects就进行设置
(module, data) => {
    if (typeof data.settings.sideEffects === "boolean") {
        if (module.factoryMeta === undefined) {
            module.factoryMeta = {};
        }
        module.factoryMeta.sideEffectFree = !data.settings.sideEffects;
    }
    return module;
}

到这里,整个模块相关信息就解析完了, 将会开始回收栈,把结果返回给this.hooks.factorize,回到factory.create,再回到_startProcessing里的_processor

_startProcessing(entry) {
  this.hooks.beforeStart.callAsync(entry.item, err => {
    //...
      try {
          this._processor(entry.item, (e, r) => {
              inCallback = true;
              //回到_processor回调执行_handleResult
              this._handleResult(entry, e, r);
          });
      } catch (err) {
          //...
      }
    
    });
}

_handleResult(entry, err, result) {
    this.hooks.result.callAsync(entry.item, err, result, hookError => {
        const error = hookError
            ? makeWebpackError(hookError, `AsyncQueue(${this._name}).hooks.result`)
            : err;

        const callback = entry.callback;
        const callbacks = entry.callbacks;
        entry.state = DONE_STATE;
        entry.callback = undefined;
        entry.callbacks = undefined;
        entry.result = result;
        entry.error = error;
        
        //...
        //执行callback
        callback(error, result);
        inHandleResult--;
    });
}

_handleResult会将一些属性赋值给AsyncQueueEntry对象并执行callback,还记得最初是由this.factorizeModule调用的吗

handleModuleCreation () {
  //...
  this.factorizeModule(
    {
      currentProfile,
      factory,
      dependencies,
      factoryResult: true,
      originModule,
      contextInfo,
      context
    },
    (err, factoryResult) => {
      //...

      const newModule = factoryResult.module;
      //...

      this.addModule(newModule, (err, module) => {
        //...
      }
    }
  );

它将会用resolveData创建的NormalModule丢进this.addModule处理,

addModule AsyncQueue

this.addModule和this.factorizeModule一样也是AsyncQueue。

addModule(module, callback) {
    this.addModuleQueue.add(module, callback);
}

_addModule(module, callback) {
    const identifier = module.identifier();
    //...

    this._modulesCache.get(identifier, null, (err, cacheModule) => {
        //...
        this._modules.set(identifier, module);
        this.modules.add(module);
        if (this._backCompat)
            ModuleGraph.setModuleGraphForModule(module, this.moduleGraph);
        if (currentProfile !== undefined) {
            currentProfile.markIntegrationEnd();
        }
        callback(null, module);
    });
}


//ModuleGraph.js
const moduleGraphForModuleMap = new WeakMap();

static setModuleGraphForModule(module, moduleGraph) {
    moduleGraphForModuleMap.set(module, moduleGraph);
}

首先会获取模块的identifier,它会拼凑模块的type,request和layer。然后将module和对应的moduleGraph加入到map里。然后执行的this.addModule的回调

this.addModule(newModule, (err, module) => {

    if (...) {
        //...
    } else {
        //...
        for (let i = 0; i < dependencies.length; i++) {
            const dependency = dependencies[i];
            moduleGraph.setResolvedModule(
                connectOrigin ? originModule : null,
                dependency,
                module
            );
        }
    }

    //...
});

首先会循环dependencies并将当前module和依赖丢进setResolvedModule,originModule是父模块,比如index.jsmodule_c.js引入了个变量,module_c的originModule就是index

ModuleGraph

setResolvedModule会执行一些ModuleGraph操作

ModuleGraph.js里会包含ModuleGraph、ModuleGraphConnection、ModuleGraphModule,它们会收集模块间的依赖关系,并为后续处理提供信息

  • ModuleGraph

    • _dependencyMap
    • _moduleMap

ModuleGraph会被实例化,然后赋值到Compilation对象里,之后只要调用Compilation.ModuleGraph就能进行相关操作。

ModuleGraph有两个重要的Map,_dependencyMap_moduleMap

_dependencyMap接收Dependency为键,ModuleGraphConnection为值,Dependency比如为EntryDependency,ImportDependency等。

_moduleMap接收Module为键,ModuleGraphModule为值 , Module就是我们resloveData创建的NormalModule。

对于Map数据结构,之后就可以通过Module和Dependency对象直接方便找到对应的相关ModuleGraphConnection或ModuleGraphModule信息

  • ModuleGraphConnection

    • originModule;
    • dependency;
    • module
    • weak

ModuleGraphConnection保存引用信息,当前模块,父模块,依赖等。weak表示为弱依赖,用于处理一些SSR场景

  • ModuleGraphModule

    • incomingConnections
    • outgoingConnections
    • issuer
    • exports

incomingConnections保存模块的被引用集合,outgoingConnections保存模块的引用集合,集合对象都是ModuleGraphConnection。

ModuleGraphModule还保存着其他的信息,比如ExportsInfo,之后会用于保存模块的导出信息,并在优化的时候分析是否被使用来实现tree shaking

回到addModule

this.addModule(newModule, (err, module) => {

    if (...) {
        //...
    } else {
        //...
        for (let i = 0; i < dependencies.length; i++) {
            const dependency = dependencies[i];
            moduleGraph.setResolvedModule(
                connectOrigin ? originModule : null,
                dependency,
                module
            );
        }
    }

  //...
});

它会循环每个依赖并调用setResolvedModule

//ModuleGraph.js
setResolvedModule(originModule, dependency, module) {
  //实例化ModuleGraphConnection
    const connection = new ModuleGraphConnection(
        originModule,
        dependency,
        module,
        undefined,
        dependency.weak,
        dependency.getCondition(this)
    );
  //获取模块对应ModuleGraphModule的incomingConnections
    const connections = this._getModuleGraphModule(module).incomingConnections;
    connections.add(connection);

  //保存在outgoingConnections或_dependencyMap
    if (originModule) {
        const mgm = this._getModuleGraphModule(originModule);
        if (mgm._unassignedConnections === undefined) {
            mgm._unassignedConnections = [];
        }
        mgm._unassignedConnections.push(connection);
        if (mgm.outgoingConnections === undefined) {
            mgm.outgoingConnections = new SortableSet();
        }
        mgm.outgoingConnections.add(connection);
    } else {
        this._dependencyMap.set(dependency, connection);
    }
}

setResolvedModule会通过当前module新建ModuleGraphConnection实例,并保存在module对应的ModuleGraphModule的incomingConnections里。

之后如果有父模块就保存在outgoingConnections里,否则在_dependencyMap里保存connection。

最后会执行this.addmodule里的_handleModuleBuildAndDependencies,开始处理模块内容。

this.addModule(newModule, (err, module) => {
    //...
    this._handleModuleBuildAndDependencies(
        originModule,
        module,
        recursive,
        callback
    );

});


_handleModuleBuildAndDependencies(originModule, module, recursive, callback) {
    //...
    this.buildModule(module, err => {
        //...
    callback()
    });
}


//buildModule的processor
_buildModule(module, callback) {
    //...

    module.needBuild(
        {
            compilation: this,
            fileSystemInfo: this.fileSystemInfo,
            valueCacheVersions: this.valueCacheVersions
        },
        (err, needBuild) => {
            //...
            
      //调用module.build构建模块
            module.build(
                this.options,
                this,
                this.resolverFactory.get("normal", module.resolveOptions),
                this.inputFileSystem,
                err => {
                    //...
                }
            );
        }
    );
}

buildModule AsyncQueue

this.buildModule同样也是一个AsyncQueue,它最终会调用Module.build开始构建模块

//normalModule.js
build(options, compilation, resolver, fs, callback) {

  //初始化属性
    this._forceBuild = false;
    this._source = null;
    if (this._sourceSizes !== undefined) this._sourceSizes.clear();
    this._sourceTypes = undefined;
    this._ast = null;
    this.error = null;
    this.clearWarningsAndErrors();
    this.clearDependenciesAndBlocks();
    this.buildMeta = {};
    this.buildInfo = {
        cacheable: false,
        parsed: true,
        buildDependencies: undefined,
        valueDependencies: undefined,
        hash: undefined,
        assets: undefined,
        assetsInfo: undefined
    };

    const startTime = compilation.compiler.fsStartTime || Date.now();

    const hooks = NormalModule.getCompilationHooks(compilation);

    return this._doBuild(options, compilation, resolver, fs, hooks, err => {
        //...
    });
}

module.build会初始化构建信息,源码,AST,buildInfo 等,然后调用_doBuild

_doBuild(options, compilation, resolver, fs, hooks, callback) {
  //获取loader上下文
    const loaderContext = this._createLoaderContext(
        resolver,
        options,
        compilation,
        fs,
        hooks
    );
    
    //...

  //调用loader-runner执行loaders
    runLoaders(
        {
            //模块路径
            resource: this.resource,
            //loaders
            loaders: this.loaders,
            //loader上下文
            context: loaderContext,
            //处理资源的函数
            processResource: (loaderContext, resourcePath, callback) => {
                const resource = loaderContext.resource;
                const scheme = getScheme(resource);

        //根据不同的方案读文件
                hooks.readResource
                    .for(scheme)
                    .callAsync(loaderContext, (err, result) => {
                        if (err) return callback(err);
                        if (typeof result !== "string" && !result) {
                            return callback(new UnhandledSchemeError(scheme, resource));
                        }
                        return callback(null, result);
                    });
            }
        },
        (err, result) => {
            //...

      //将loader添加进构建信息
            for (const loader of this.loaders) {
                this.buildInfo.buildDependencies.add(loader.loader);
            }
            this.buildInfo.cacheable = this.buildInfo.cacheable && result.cacheable;
            processResult(err, result.result);
        }
    );
}

_doBuild首先会创建loaderContext,用于runLoaders的参数,runLoaders来自loader-runner,这是webpack用于运行Loaders的库。

processResource用于该怎么读资源给Loader,因为我们的入口文件是"/indx.js",所以readResource hook会拿出FileUriPlugin,并进行fs.readFile,将文件Buffer取出。

runLoaders会将result返回,它包含resourceBuffer和result,如果有相应的loader,result将会转换成js可操作的字符串。

下图是使用babel-loader生成的结果
img

最后会将结果传给processResult进行处理

const processResult = (err, result) => {
    //...

    const source = result[0];
    const sourceMap = result.length >= 1 ? result[1] : null;
    const extraInfo = result.length >= 2 ? result[2] : null;
    
    //如果不是Buffer或者字符串就报错
    if (!Buffer.isBuffer(source) && typeof source !== "string") {
        const currentLoader = this.getCurrentLoader(loaderContext, 0);
        const err = new Error(
            `Final loader (${
                currentLoader
                    ? compilation.runtimeTemplate.requestShortener.shorten(
                            currentLoader.loader
                        )
                    : "unknown"
            }) didn't return a Buffer or String`
        );
        const error = new ModuleBuildError(err);
        return callback(error);
    }
    
    //生成sourceMap
    this._source = this.createSource(
        options.context,
        //如果是二进制就转换成Buffer,否则字符串化
        this.binary ? asBuffer(source) : asString(source),
        sourceMap,
        compilation.compiler.root
    );
    if (this._sourceSizes !== undefined) this._sourceSizes.clear();
    this._ast =
        typeof extraInfo === "object" &&
        extraInfo !== null &&
        extraInfo.webpackAST !== undefined
            ? extraInfo.webpackAST
            : null;
    return callback();
};

processResult会要求loader必须返回Buffer或者String,然后会调用createSrouce对result处理,createSource会调用webpack-sources库,能够生成有或者无sourceMap的源码。

最后初始化AST,调用callback,callback会回到_doBuild。

return this._doBuild(options, compilation, resolver, fs, hooks, err => {
    // if we have an error mark module as failed and exit
    if (err) {
        //...
    }
    //...

    let result;
    try {
        const source = this._source.source();
        //调用parser转换成AST
        result = this.parser.parse(this._ast || source, {
            source,
            current: this,
            module: this,
            compilation: compilation,
            options: options
        });
    }
  
  //...
});

_doBuild会把结果进行解析,还记得吗,这里的parser是在resolve阶段生成的,因为当前模块是js资源所以生成的是JavascriptParser。

JavascriptParser

parse(source, state) {
  //ast
    let ast;
  //注释
    let comments;
    const semicolons = new Set();
    if (source === null) {
        throw new Error("source must not be null");
    }
    if (Buffer.isBuffer(source)) {
        source = source.toString("utf-8");
    }
    if (typeof source === "object") {
        ast = /** @type {ProgramNode} */ (source);
        comments = source.comments;
    } else {
        comments = [];
    //调用acorn生成ast
        ast = JavascriptParser._parse(source, {
            sourceType: this.sourceType,
            onComment: comments,
            onInsertedSemicolon: pos => semicolons.add(pos)
        });
    }
    const oldScope = this.scope;
    const oldState = this.state;
    const oldComments = this.comments;
    const oldSemicolons = this.semicolons;
    const oldStatementPath = this.statementPath;
    const oldPrevStatement = this.prevStatement;
    //初始化作用域
    this.scope = {
        topLevelScope: true,
        inTry: false,
        inShorthand: false,
        isStrict: false,
        isAsmJs: false,
        definitions: new StackedMap()
    };
    /** @type {ParserState} */
    this.state = state;
    this.comments = comments;
    this.semicolons = semicolons;
    this.statementPath = [];
    this.prevStatement = undefined;
    if (this.hooks.program.call(ast, comments) === undefined) {
        //判断js的模式
        this.detectMode(ast.body);
    //遍历topLevelScope的变量并声明
        this.preWalkStatements(ast.body);
        this.prevStatement = undefined;
        this.blockPreWalkStatements(ast.body);
        this.walkStatements(ast.body);
    }
    this.hooks.finish.call(ast, comments);
    this.scope = oldScope;
    /** @type {ParserState} */
    this.state = oldState;
    this.comments = oldComments;
    this.semicolons = oldSemicolons;
    this.statementPath = oldStatementPath;
    this.prevStatement = oldPrevStatement;
    return state;
}

JavascriptParser._parse函数用于解析js,在函数里会用acorn作为parser,然后转换成AST(抽象语法树),对于AST(抽象语法书)的树结构可以去astexplorer探索。

this.detectMode用于去检测是否含有 "use strict""use asm""use strict"代表当前为严格模式,"use asm"表示是使用asm.js编译出来的,帮助浏览器优化性能。

preWalkStatements

preWalkStatements用于迭代声明变量的范围

preWalkStatements(statements) {
    for (let index = 0, len = statements.length; index < len; index++) {
        const statement = statements[index];
        this.preWalkStatement(statement);
    }
}

preWalkStatement(statement) {
        this.statementPath.push(statement);
        if (this.hooks.preStatement.call(statement)) {
            this.prevStatement = this.statementPath.pop();
            return;
        }
        switch (statement.type) {
            case "BlockStatement":
                this.preWalkBlockStatement(statement);
                break;
            case "DoWhileStatement":
                this.preWalkDoWhileStatement(statement);
                break;
            case "ForInStatement":
                this.preWalkForInStatement(statement);
                break;
            case "ForOfStatement":
                this.preWalkForOfStatement(statement);
                break;
            case "IfStatement":
                this.preWalkIfStatement(statement);
                break;
            case "TryStatement":
                this.preWalkTryStatement(statement);
                break;
            case "VariableDeclaration":
                this.preWalkVariableDeclaration(statement);
                break;
      //...
        }
        this.prevStatement = this.statementPath.pop();
    }

preWalkStatement会遍历AST body,如果遇到最外层的变量声明,就定义变量到this.scope.definitions。definitions是个map结构,name为key,this.scope为值。

比如遇到var a = 2, 就符合VariableDeclaration的case,然后将"a"保存到definitions map里,对应的this.scope的topLevelScope为true。当然如果在if,for等block是用let声明的,那么会跳过,后续再定义scope,因为它们不属于topLevelScope。

blockPreWalkStatements

接着就是调用blockPreWalkStatements

blockPreWalkStatements(statements) {
    for (let index = 0, len = statements.length; index < len; index++) {
        const statement = statements[index];
        this.blockPreWalkStatement(statement);
    }
}

blockPreWalkStatement(statement) {
    this.statementPath.push(statement);
    if (this.hooks.blockPreStatement.call(statement)) {
        this.prevStatement = this.statementPath.pop();
        return;
    }
    switch (statement.type) {
        case "ImportDeclaration":
            this.blockPreWalkImportDeclaration(statement);
            break;
        case "ExportAllDeclaration":
            this.blockPreWalkExportAllDeclaration(statement);
            break;
        case "ExportDefaultDeclaration":
            this.blockPreWalkExportDefaultDeclaration(statement);
            break;
        case "ExportNamedDeclaration":
            this.blockPreWalkExportNamedDeclaration(statement);
            break;
        case "VariableDeclaration":
            this.blockPreWalkVariableDeclaration(statement);
            break;
        case "ClassDeclaration":
            this.blockPreWalkClassDeclaration(statement);
            break;
    }
    this.prevStatement = this.statementPath.pop();
}

在blockPreWalkStatements里,如果遇到的声明是ImportDeclaration的时候会调用blockPreWalkImportDeclaration。比如import some form "some"

blockPreWalkImportDeclaration(statement) {
    //获取module name
    const source = statement.source.value;
    //添加HarmonyImportSideEffectDependency依赖
    this.hooks.import.call(statement, source);

    //根据不同的import type 定义 VariableInfo
    switch (specifier.type) {
        case "ImportDefaultSpecifier":
            if (
                !this.hooks.importSpecifier.call(statement, source, "default", name)
            ) {
                this.defineVariable(name);
            }
            break;
        case "ImportSpecifier":
            if (
                !this.hooks.importSpecifier.call(statement,source,specifier.imported.name,name)
            ) {
                this.defineVariable(name);
            }
            break;
        case "ImportNamespaceSpecifier":
            if (!this.hooks.importSpecifier.call(statement, source, null, name)) {
                this.defineVariable(name);
            }
            break;
        default:
            this.defineVariable(name);
    }
    //...
}

//impor钩子注入的回调
parser.hooks.import.tap(
    "HarmonyImportDependencyParserPlugin",
    (statement, source) => {
        parser.state.lastHarmonyImportOrder =
            (parser.state.lastHarmonyImportOrder || 0) + 1;

        //创建ConstDependency
        const clearDep = new ConstDependency(
            parser.isAsiPosition(statement.range[0]) ? ";" : "",
            statement.range
        );
        clearDep.loc = statement.loc;
        parser.state.module.addPresentationalDependency(clearDep);
        parser.unsetAsiPosition(statement.range[1]);
        const assertions = getAssertions(statement);
        const sideEffectDep = new HarmonyImportSideEffectDependency(
            source,
            parser.state.lastHarmonyImportOrder,
            assertions
        );
        //设置代码位置
        sideEffectDep.loc = statement.loc;
        parser.state.module.addDependency(sideEffectDep);
        return true;
    }
);

blockPreWalkImportDeclaration先会获取module名字,如import { some } form "loadsh" , source.value就为loadsh。然后会调用parser的import hook。

HarmonyImportDependencyParserPlugin做的事情就是创建HarmonyImportSideEffectDependency并收集到模块的Dependencies数组里。

HarmonyImportSideEffectDependency是用于生成最后的import代码。比如

import { c_var } from "./module_c"

//生成如下代码

/* harmony import */ var _module_c__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./module_c */ "./module_c.js");

之后会根据不同的Import specifier给importSpecifier hooks传不同的参数,该钩子的插件会设置import变量的scope,跟平常变量不同的是,它会创建VariableInfo对象set到this.scope.definitions。VariableInfo除了包含作用域,还包含模块名字等信息。

如果遇到的是export,也会执行上述操作,不同的地方在于会创建不同的Dependency。

walkStatements

walkStatement(statement) {
    this.statementPath.push(statement);
    if (this.hooks.statement.call(statement) !== undefined) {
        this.prevStatement = this.statementPath.pop();
        return;
    }
    switch (statement.type) {
        case "BlockStatement":
            this.walkBlockStatement(statement);
            break;
        case "ClassDeclaration":
            this.walkClassDeclaration(statement);
            break;
        case "ExportDefaultDeclaration":
            this.walkExportDefaultDeclaration(statement);
            break;
        case "ExportNamedDeclaration":
            this.walkExportNamedDeclaration(statement);
            break;
        case "ExpressionStatement":
            this.walkExpressionStatement(statement);
            break;
        case "ForStatement":
            this.walkForStatement(statement);
            break;
        case "FunctionDeclaration":
            this.walkFunctionDeclaration(statement);
            break;
        case "SwitchStatement":
            this.walkSwitchStatement(statement);
            break;
    case "VariableDeclaration":
                this.walkVariableDeclaration(statement);
                break;
        //...
    }
    this.prevStatement = this.statementPath.pop();
}

walkStatements除了会遍历块内变量并定义对应scope,还会做其他事情。

webpack很聪明,虽然有import,但如果没有使用的话,也不会去请求依赖,除非有变量引用了它。

当遇到VariableDeclaration,会从scope拿出这个变量引用信息,如果取出来的是VariableInfo,说明是import的依赖,就会调用相关插件创建HarmonyImportSpecifierDependency并添加到module.dependencies

如果遇到FunctionDeclaration,并且为ImportExpression。说明是个dynamic-imports,然后会调用ImportParserPlugin插件进行处理。
为了支持magic commentsImportParserPlugin会解析出comments,并对其进行相关设置。之后会创建AsyncDependenciesBlock,平常的import会添加到module.dependencies,但是动态导入是添加到module.block,而且处理优先级也在最后。

到这里,buildModule的事情就基本完成了,将会回到buildModule回调。

this.buildModule(module, err => {
  //...

  this.processModuleDependencies(module, err => {
    if (err) {
      return callback(err);
    }
    callback(null, module);
  });
});

processModuleDependencies AsyncQueue

在buildModule里会调用processModuleDependencies,它是个AsyncQueue,processor为_processModuleDependencies

_processModuleDependencies(module, callback) {
  /** @type {Array<{factory: ModuleFactory, dependencies: Dependency[], originModule: Module|null}>} */
  const sortedDependencies = [];

  /** @type {DependenciesBlock} */
  let currentBlock;

  /** @type {Map<ModuleFactory, Map<string, Dependency[]>>} */
  let dependencies;
  //...

  try {
    /** @type {DependenciesBlock[]} */
    const queue = [module];
    do {
      const block = queue.pop();
      if (block.dependencies) {
        currentBlock = block;
        let i = 0;
        for (const dep of block.dependencies) processDependency(dep, i++);
      }
      if (block.blocks) {
        for (const b of block.blocks) queue.push(b);
      }
    } while (queue.length !== 0);
  } catch (e) {
    return callback(e);
  }

  if (--inProgressSorting === 0) onDependenciesSorted();
}

_processModuleDependencies会循环模块的dependencies和block。前面说过dependencies里是正常的import,block里是Dynamic import,它们都会经过processDependency进行处理。

因为在parser的时候添加HarmonyImportSpecifierDependency依赖是根据代码引用位置,在dependencies里是乱序的。所以processDependency的作用就是根据import前后位置进行排序,然后将相同模块的几个依赖放在一起。

排序完后调用onDependenciesSorted处理所有外部依赖

const onDependenciesSorted = err => {
  if (err) return callback(err);

  // early exit without changing parallelism back and forth
  if (sortedDependencies.length === 0 && inProgressTransitive === 1) {
    return callback();
  }

  // This is nested so we need to allow one additional task
  this.processDependenciesQueue.increaseParallelism();

  for (const item of sortedDependencies) {
    inProgressTransitive++;
    this.handleModuleCreation(item, err => {
      // In V8, the Error objects keep a reference to the functions on the stack. These warnings &
      // errors are created inside closures that keep a reference to the Compilation, so errors are
      // leaking the Compilation object.
      if (err && this.bail) {
        if (inProgressTransitive <= 0) return;
        inProgressTransitive = -1;
        // eslint-disable-next-line no-self-assign
        err.stack = err.stack;
        onTransitiveTasksFinished(err);
        return;
      }
      if (--inProgressTransitive === 0) onTransitiveTasksFinished();
    });
  }
  if (--inProgressTransitive === 0) onTransitiveTasksFinished();
};

onDependenciesSorted会循环依赖,并执行handleModuleCreation,这个函数不就是最开始处理入口模块的吗?是的,从开头的handleModuleCreation到这就是webpack递归处理所有引入模块的过程。

当所有依赖都处理完后就会从processDependenciesQueue往后回收栈,回到buildQueue,factorizeModule,回到最初调用handleModuleCreation的地方

addModuleTree({ context, dependency, contextInfo }, callback) {
  //...

  this.handleModuleCreation(
    (//...)
    ,
    (err, result) => {
      if (err && this.bail) {
        callback(err);
        this.buildQueue.stop();
        this.rebuildQueue.stop();
        this.processDependenciesQueue.stop();
        this.factorizeQueue.stop();
      } else if (!err && result) {
        callback(null, result);
      } else {
        callback();
      }
    }
  );
}

handleModuleCreation会停止所有任务队列并回调到make hook

finishMake

//compiler.js

compile(callback) {
 //...
 err => {
    if (err) return callback(err);

    //...
    logger.time("make hook");
    this.hooks.make.callAsync(compilation, err => {
      logger.timeEnd("make hook");
      if (err) return callback(err);

      logger.time("finish make hook");

      this.hooks.finishMake.callAsync(compilation, err => {
        logger.timeEnd("finish make hook");
        if (err) return callback(err);

        process.nextTick(() => {
          logger.time("finish compilation");
          compilation.finish(err => {
            logger.timeEnd("finish compilation");
            if (err) return callback(err);

            logger.time("seal compilation");
            compilation.seal(err => {
                //...
            });
          });
        });
      });
    });
  });

make hooks之后是finishMake hooks,在这里面会调用compilation.finish。

compilation.finish里大部分代码都在输出日志,但里面有个finishModules hooks里有个重要的插件flagDependencyExportsPlugin。它会将所有模块的export信息添加到NormalModule对应的ModuleGraphModule里,之后在优化阶段的时候用于分析是否被使用,是否需要tree shaking。

到这里make的所有环节就结束了

总结

总的来说make环节主要就是在处理模块,webpack将处理模块分为几个步骤。

factorize会调用resolver去解析模块和loader的路径相关信息,并生成对应的paser和generator。

addModule会设置模块对应的ModuleGraph,ModuleGraph包含模块,外部依赖的引用信息,导出信息等。

buildModule会通过Loader-runner执行loaders,将资源转换成js能操作的目标,然后将源码paser成AST,并遍历AST Body,对不同的声明进行处理,在此期间会收集外部依赖,导出信息等。

processModuleDependencies会处理所有收集的依赖,并将依赖回到factorize,从而实现递归处理所有的模块

  1. 原文: https://github.com/Hazlank/blog/issues/17 ,欢迎star
阅读 393
9 声望
0 粉丝
0 条评论
9 声望
0 粉丝
文章目录
宣传栏