关于前端:webpack5-源码详解-编译模块

5次阅读

共计 37605 个字符,预计需要花费 95 分钟才能阅读完成。

构建模块

上一篇讲了对于 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 里的。

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

  • e.g. Absolute specifiers like ‘file:///user/webpack/index.js’
  • https://tools.ietf.org/html/r…

持续 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 会蕴含一些解析相干信息。

接着回到 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();});

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 也容许对 Parser 和 Generator 进行配置以取得某些性能或者更改输入配置

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 生成的后果

最初会将后果传给 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 comments,ImportParserPlugin会解析出 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
正文完
 0