Webpack4源码解析之doResolve完成后的最终回调链路

363 阅读10分钟

Webpack源码解析

// 使用webpack版本

"html-webpack-plugin": "^4.5.0",
"webpack": "^4.44.2",
"webpack-cli": "^3.3.12"

打包主流程分析

上一篇文章 文件的resolve流程 分析了一个普通文件需要经过大量的 plugins 进行解析处理,处理完毕就一层一层执行回调,将最终的模块信息一层一层的传递到顶层调用者,创建一个 module,然后进行最终的 buildModule, 这一篇就来看看最终的整个回调链路是怎样的。

doResolve最终回调链路

// node_modules/enhanced-resolve/lib/Resolver.js

doResolve () {
  ...

  return hook.callAsync(request, innerContext, (err, result) => {
    if (err) return callback(err);
    if (result) return callback(null, result);
    callback();
  });

  ...
}

to

// node_modules/enhanced-resolve/lib/Resolver.js

resolve () {
  ...

  this.doResolve(
    ...,
    ...,
    ...,
    ...,
    (err, result) => {
      if (!err && result) {
        return callback(
          null,
          result.path === false ? false : result.path + (result.query || ""),
          result
        );
      }

      ...
    };
  )

  ...
}

to

// node_modules/webpack/lib/NormalModuleFactory.js

this.hooks.resolver.tap("NormalModuleFactory", () => (data, callback) => {
  ...

  asyncLib.parallel(
    [
      ...,
      (callback) => {
        if (resource === "" || resource[0] === "?") {
          return callback(null, {
            resource,
          });
        }

        normalResolver.resolve(
          contextInfo,
          context,
          resource,
          {},
          (err, resource, resourceResolveData) => {
            if (err) return callback(err);
            callback(null, {
              resourceResolveData,
              resource,
            });
          }
        );
      },
    ],
    ...
  );

  ...

})

to

// node_modules/webpack/lib/NormalModuleFactory.js

this.hooks.resolver.tap("NormalModuleFactory", () => (data, callback) => {
  ...

  // asyncLib.parallel 并行执行两个函数,一个是处理Loader的,另一个是resolve普通文件的,最终将结果合并成一个数组返回
  // 注意这里处理的是同一个request
  // results[0] 就是处理Loader的结果
  // results[1] 就是加载普通文件的结果

  asyncLib.parallel(
    [
      ...,
      ...
    ],
    (err, results) => {
      // 如果我们配置了Loader的话results大致上是这样的(经过第一个函数resolveRequestArray处理过,内联Loader会处理成以下格式)
      // {
      //   "results": [
      //     [
      //       {
      //         "loader": "loader的绝对路径1",
      //         "options": "loader参数1"
      //       },
      //       {
      //         "loader": "loader的绝对路径2",
      //         "options": "loader参数2"
      //       }
      //     ],
      //     {
      //       "resource": "模块绝对路径",
      //       "resourceResolveData": "模块基本信息(即doResolve执行结果)"
      //     }
      //   ]
      // }
      // results 是一个数组,第一个是Loader的解析结果
      // 第二个数组元素是一个对象,对象包含两个属性,一个resource是资源路径,另一个resourceResolveData是资源加载内容
      // resource:'/Users/---/lagou-edu/webpack-entry/src/index.js'
      // resourceResolveData:
      //   context:{issuer: '', compiler: undefined}
      //   file:false
      //   module:false
      //   path:'/Users/---/lagou-edu/webpack-entry/src/index.js'
      //   query:''
      //   relativePath:undefined
      //   request:undefined
      if (err) return callback(err);
      // 取出所有的loaders
      let loaders = results[0];
      const resourceResolveData = results[1].resourceResolveData;
      resource = results[1].resource;

      ...

      // 下面是处理 Loader 的,Loader有两种设置方式,一种是配置化,一种是内联

      // 配置化:
      //   {
      //     test: /\.css$/,
      //     use: [
      //       // [style-loader](/loaders/style-loader)
      //       { loader: 'style-loader' },
      //       // [css-loader](/loaders/css-loader)
      //       {
      //         loader: 'css-loader',
      //         options: {
      //           modules: true
      //         }
      //       },
      //       // [sass-loader](/loaders/sass-loader)
      //       { loader: 'sass-loader' }
      //     ]
      //   }
      // 内联:
      //   可以在 import 语句或任何 与 "import" 方法同等的引用方式 中指定 loader。使用 ! 将资源中的 loader 分开。每个部分都会相对于当前目录解析。
      //   import Styles from 'style-loader!css-loader?modules!./styles.css';
      //   详细请查看:https://webpack.docschina.org/concepts/loaders/#root
      // 我们以上 resolve 的 ./src/index.js 它也会去查找匹配的 Loader 进行解析

      // 下面代码就是处理这两种配置方式的load,最终asyncLib.parallel执行处理所有Loaders
      // 最终返回data

      // 将rules中的loader和options拼接
      // data格式如下
      // {
      //   "loader": "loader的绝对路径1",
      //   "options": "loader参数1"
      // },
      // const loaderToIdent = data => {
      //   if (!data.options) {
      //     return data.loader;
      //   }
      //   if (typeof data.options === "string") {
      //     return data.loader + "?" + data.options;
      //   }
      //   if (typeof data.options !== "object") {
      //     throw new Error("loader options must be string or object");
      //   }
      //   if (data.ident) {
      //     return data.loader + "??" + data.ident;
      //   }
      //   return data.loader + "?" + JSON.stringify(data.options);
      // };
      // Loader识别,配置loader和options,最终加上请求的路径,组合成一个Loader request
      // /Users/***/lagou-edu/webpack-entry/node_modules/babel-loader/lib/loader.js?{"module":false}'!/Users/***/lagou-edu/webpack-entry/src/index.js'
      const userRequest =
        (matchResource !== undefined ? `${matchResource}!=!` : "") +
        loaders.map(loaderToIdent).concat([resource]).join("!");

      // 分解请求路劲和参数
      let resourcePath = matchResource !== undefined ? matchResource : resource;
      let resourceQuery = "";
      const queryIndex = resourcePath.indexOf("?");
      if (queryIndex >= 0) {
        resourceQuery = resourcePath.substr(queryIndex);
        resourcePath = resourcePath.substr(0, queryIndex);
      }
      // 进行rule匹配,找到符合条件的Loader模块
      // 具体可以参考文章[https://github.com/CommanderXL/Biu-blog/issues/30](https://github.com/CommanderXL/Biu-blog/issues/30)
      const result = this.ruleSet.exec({
        resource: resourcePath, // 路径
        realResource:
          matchResource !== undefined ? resource.replace(/\?.*/, "") : resourcePath,
        resourceQuery, // 参数
        issuer: contextInfo.issuer,
        compiler: contextInfo.compiler,
      });
      // result 为最终根据 module 的 resourcePath 及相关匹配规则过滤后得到的 loaders,为 webpack.config 进行配置的
      // 输出的数据格式为:

      /**
       * [{
       *    type: 'use',
       *    value: {
       *      loader: 'babel-loader',
       *      options: {}
       *    },
       *    enforce: undefined // 可选值还有 pre/post  分别为 pre-loader 和 post-loader
       * }]
      */
      // 当获取到 webpack.config 当中配置的 loader 后,再根据 loader 的类型进行分组(enforce 配置类型)
      // postLoader 存储到 useLoadersPost 内部 后置Loader
      // preLoader 存储到 useLoadersPre 内部 前置Loader
      // normalLoader 存储到 useLoaders 内部 普通Loader
      // `pre > normal > inline > post`
      // 这些分组最终会决定加载一个 module 时不同 loader 之间的调用顺序

      const settings = {};
      const useLoadersPost = [];
      const useLoaders = [];
      const useLoadersPre = [];
      for (const r of result) {
        if (r.type === "use") {
          if (r.enforce === "post" && !noPrePostAutoLoaders) {
            useLoadersPost.push(r.value);
          } else if (
            r.enforce === "pre" &&
            !noPreAutoLoaders &&
            !noPrePostAutoLoaders
          ) {
            useLoadersPre.push(r.value);
          } else if (!r.enforce && !noAutoLoaders && !noPrePostAutoLoaders) {
            useLoaders.push(r.value);
          }
        } else if (
          typeof r.value === "object" &&
          r.value !== null &&
          typeof settings[r.type] === "object" &&
          settings[r.type] !== null
        ) {
          settings[r.type] = cachedCleverMerge(settings[r.type], r.value);
        } else {
          settings[r.type] = r.value;
        }
      }
      // 当分组过程进行完之后,即开始 loader 模块的 resolve 过程
      asyncLib.parallel(
        [
          this.resolveRequestArray.bind(
            this,
            contextInfo,
            this.context,
            useLoadersPost,
            loaderResolver
          ),
          this.resolveRequestArray.bind(
            this,
            contextInfo,
            this.context,
            useLoaders,
            loaderResolver
          ),
          this.resolveRequestArray.bind(
            this,
            contextInfo,
            this.context,
            useLoadersPre,
            loaderResolver
          ),
        ],
        (err, results) => {
          if (err) return callback(err);
          if (matchResource === undefined) {
            loaders = results[0].concat(loaders, results[1], results[2]);
          } else {
            loaders = results[0].concat(results[1], loaders, results[2]);
          }
          ...
          // results[0]  ->  postLoader
          // results[1]  ->  normalLoader
          // results[2]  ->  preLoader
          // 这里将构建 module 需要的所有类型的 loaders 按照一定顺序组合起来,对应于:
          // [postLoader, inlineLoader, normalLoader, preLoader]
          // 最终 loader 所执行的顺序对应为: preLoader -> normalLoader -> inlineLoader -> postLoader
          // 不同类型 loader 上的 pitch 方法执行的顺序为: postLoader.pitch -> inlineLoader.pitch -> normalLoader.pitch -> preLoader.pitch

          process.nextTick(() => {
            const type = settings.type;
            const resolveOptions = settings.resolve;
            // 执行回调(factory钩子提供的factory函数中调用resolver的回调)
            callback(null, {
              context: context,
              request: loaders.map(loaderToIdent).concat([resource]).join("!"),
              dependencies: data.dependencies,
              userRequest,
              rawRequest: request,
              loaders,
              resource,
              matchResource,
              resourceResolveData,
              settings,
              type,
              parser: this.getParser(type, settings.parser),
              generator: this.getGenerator(type, settings.generator),
              resolveOptions,
            });
          });
        }
      );
    };
  );

  ...

})

to

// node_modules/webpack/lib/NormalModuleFactory.js

this.hooks.factory.tap("NormalModuleFactory", () => (result, callback) => {
  ...

  resolver(result, (err, data) => {
    if (err) return callback(err);

    // Ignored
    if (!data) return callback();

    // 如果是一个直接的module就直接执行回调
    if (typeof data.source === "function") return callback(null, data);

    // Resolver拿到的是模块的依赖关系和文件的Loader关系,这里执行resolve后的钩子
    this.hooks.afterResolve.callAsync(data, (err, result) => {
      // result 如下
      /**
        *   context:'/Users/---/lagou-edu/webpack-entry'
        * ▼ dependencies:(1) [SingleEntryDependency]
        *    ▼ 0:SingleEntryDependency {…}
        *       loc:{name: 'main'}
        *       module:null
        *       optional:false
        *       request:'./src/index.js'
        *       type (get):ƒ type() {\n\t\treturn "single entry";\n\t}
        *       userRequest:'./src/index.js'
        *       weak:false
        *     ▶__proto__:ModuleDependency
        *     length:1
        *    ▶ __proto__:Array(0)
        * ▶ generator:JavascriptGenerator
        * ▼ loaders:(1) [{…}]
        *    ▼ 0: {...}
        *        ident:'ref--4'
        *        loader:'/Users/---/lagou-edu/webpack-entry/node_modules/babel-loader/lib/index.js'
        *      ▼ options:{presets: Array(1)}
        *         presets:(1) ['@babel/preset-env']
        *           0:'@babel/preset-env'
        *           length:1
        *         ▶ __proto__:Array(0)
        *        ▶ __proto__:Object
        *       ▶ __proto__:Object
        *      length:1
        *    ▶ __proto__:Array(0)
        *   matchResource:undefined
        * ▶ parser:Parser {_pluginCompat: SyncBailHook, hooks: {…}, options: {…}, sourceType: 'auto', scope: undefined, …}
        *   rawRequest:'./src/index.js'
        *   request:'/Users/---/lagou-edu/webpack-entry/node_modules/babel-loader/lib/index.js??ref--4!/Users/---/lagou-edu/webpack-entry/src/index.js'
        * ▶ resolveOptions:{}
        *   resource:'/Users/---/lagou-edu/webpack-entry/src/index.js'
        *   resourceResolveData:{…}
        *    ▶ context:{issuer: '', compiler: undefined}
        *    ▶ descriptionFileData:{name: 'webpack-code', version: '1.0.0', main: './src/index.js', license: 'MIT', devDependencies: {…}, …}
        *      descriptionFilePath:'/Users/---/lagou-edu/webpack-entry/package.json'
        *      descriptionFileRoot:'/Users/---/lagou-edu/webpack-entry'
        *      file:false
        *      module:false
        *      path:'/Users/---/lagou-edu/webpack-entry/src/index.js'
        *      query:''
        *      relativePath:'./src/index.js'
        *      request:undefined
        *      __innerRequest:'./src/index.js'
        *      __innerRequest_relativePath:'./src/index.js'
        *      __innerRequest_request:undefined
        *    ▶ __proto__:Object
        * ▶ settings:{type: 'javascript/auto', resolve: {…}}
        *   type:'javascript/auto'
        *   userRequest:'/Users/---/lagou-edu/webpack-entry/src/index.js'
        * ▶ __proto__:Object
      */
      if (err) return callback(err);

      // Ignored
      if (!result) return callback();

      // 钩子执行完毕就根据结果创建module
      // createModule钩子上找有没有创建好的module
      let createdModule = this.hooks.createModule.call(result);
      if (!createdModule) {
        if (!result.request) {
          return callback(new Error("Empty dependency (no request)"));
        }
        // 没有的话实例化NormalModule,创建一个module
        createdModule = new NormalModule(result);
      }

      // 将module和result交由module钩子处理
      createdModule = this.hooks.module.call(createdModule, result);

      // createdModule如下
      // ...

      // _buildHash:''
      // _cachedSources:Map(0)
      // _chunks:Set(0) {_sortFn: ƒ, _lastActiveSortFn: null, _cache: undefined, _cacheOrderIndependent: undefined}
      // _lastSuccessfulBuildMeta:{}
      // _rewriteChunkInReasons:undefined
      // _source:null
      // _sourceSize:null
      // arguments (get):ƒ arguments() {\n\t\tthrow new Error("Module.arguments was removed, there is no replacement.");\n\t}
      // arguments (set):ƒ arguments(value) {\n\t\tthrow new Error("Module.arguments was removed, there is no replacement.");\n\t}
      // binary:false
      // blocks:(0) []
      // buildInfo:undefined
      // buildMeta:undefined
      // buildTimestamp:undefined
      // built:false
      // chunksIterable (get):ƒ chunksIterable() {\n\t\treturn this._chunks;\n\t}
      // context:'/Users/---/lagou-edu/webpack-entry/src'
      // debugId:1001
      // dependencies:(0) []
      // depth:null
      // entry (get):ƒ get() {\n\t\tthrow new Error("Module.entry was removed. Use Chunk.entryModule");\n\t}
      // entry (set):ƒ set() {\n\t\tthrow new Error("Module.entry was removed. Use Chunk.entryModule");\n\t}
      // error:null

      // ...

      // 执行回调,返回创建好的module
      return callback(null, createdModule);
    });
  });
});

to

// node_modules/webpack/lib/NormalModuleFactory.js

create(data, callback) {
  ...

  this.hooks.beforeResolve.callAsync(
    ...,
    (err, result) => {
      ...

      factory(result, (err, module) => {
        if (err) return callback(err);
        // 依赖缓存
        if (module && this.cachePredicate(module)) {
          for (const d of dependencies) {
            dependencyCache.set(d, module);
          }
        }
        // 执行回调
        callback(null, module);
      });
    }
  );
}

to

// node_modules/webpack/lib/Compilation.js

_addModuleChain(context, dependency, onModule, callback) {
  ...

  moduleFactory.create(
    ...,
    (err, module) => {
      // 如果出错就从编译队列中取出下一个执行
      if (err) {
        this.semaphore.release();
        return errorAndCallback(new EntryModuleNotFoundError(err));
      }

      let afterFactory;

      if (currentProfile) {
        afterFactory = Date.now();
        currentProfile.factory = afterFactory - start;
      }

      // 添加到 _modules 和 cache 中,最终返回module

      // build:true
      // dependencies:true
      // issuer:true
      // module: NormalModule{...}
      const addModuleResult = this.addModule(module);
      // 取出module,此时的module有可能是一个cache
      module = addModuleResult.module;
      // onModule 对应调用者的第三个参数:
      // module => {
      //  this.entries.push(module);
      // },
      // 将module添加到entries中
      onModule(module);

      // 替换entry的module

      // loc:{name: 'main'}
      // module:NormalModule {…}
      // optional:false
      // request:'./src/index.js'
      // type (get):ƒ type() {\n\t\treturn "single entry";\n\t}
      // userRequest:'./src/index.js'
      // weak:false
      dependency.module = module;
      // 调用Module模块的addReason方法(node_modules/webpack/lib/Module.js 第216行)
      // addReason(module, dependency, explanation) {
      //   this.reasons.push(new ModuleReason(module, dependency, explanation));
      // }
      // Module模块的reasons中添加一个ModuleReason实例
      // ModuleReason {module: null, dependency: SingleEntryDependency, explanation: undefined, _chunks: null}
      //   _chunks:null
      //   dependency:SingleEntryDependency {module: NormalModule, weak: false, optional: false, loc: {…}, request: './src/index.js', …}
      //   explanation:undefined
      //   module:null
      module.addReason(null, dependency);

      // build之后做的事情,到这里一个文件的Loader和Loader的pitch、normal都已经执行完毕,build hash生成
      // 此时会判断当前module是否存在依赖,如果存在依赖就会去加载依赖
      const afterBuild = () => {
        if (addModuleResult.dependencies) {
          // processModuleDependencies 方法就是去加载依赖
          // 它会调用Compilation的addModuleDependencies方法,遍历依赖,递归加载所有依赖
          // 每一个依赖是一个对象:
          //  dependencies:(2) [HarmonyImportSideEffectDependency, HarmonyImportSpecifierDependency]
          //  factory:NormalModuleFactory {_pluginCompat: SyncBailHook, hooks: {…}, resolver ...}
          // dependencies里面是依赖的基本信息
          // factory是NormalModule工厂类,它下面存在create方法,addModuleDependencies中通过调用factory.create就开始执行模块的处理
          // 流程和加载file类似,最终回调中afterBuild会继续判断当前依赖是否存在其它依赖,然后递归加载,直到查找到所有依赖链,最终将解析结果返回
          this.processModuleDependencies(module, (err) => {
            if (err) return callback(err);
            // 依赖加载完毕后执行最终回调至addEntry
            callback(null, module);
          });
        } else {
          return callback(null, module);
        }
      };

      // 关于issuer请看:[https://juejin.cn/post/6955878306981871623](https://juejin.cn/post/6955878306981871623)
      // 很少使用,这里忽略不计
      if (addModuleResult.issuer) {
        if (currentProfile) {
          module.profile = currentProfile;
        }
      }

      // 进行打包(build为true说明resolver处理、创建module的工作都已经完成,可以进入打包)
      // 这里build具体流程下一篇在分析
      if (addModuleResult.build) {
        this.buildModule(module, false, null, null, (err) => {
          if (err) {
            this.semaphore.release();
            return errorAndCallback(err);
          }

          if (currentProfile) {
            const afterBuilding = Date.now();
            currentProfile.building = afterBuilding - afterFactory;
          }

          this.semaphore.release();
          // buildModule执行完毕执行afterBuild
          afterBuild();
        });
      } else {
        // 否则就取出编译队列的下一个执行,并等待build结束
        this.semaphore.release();
        this.waitForBuildingFinished(module, afterBuild);
      }
    }
  );
}

to

// node_modules/webpack/lib/Compilation.js

addEntry(context, entry, name, callback) {
  ...

  this._addModuleChain(
    context,
    entry,
    (module) => {
      this.entries.push(module);
    },
    (err, module) => {
      // 如果出现错误,则调用failedEntry钩子,处理错误信息
      // 然后执行回调
      if (err) {
        this.hooks.failedEntry.call(entry, name, err);
        return callback(err);
      }

      // 将slot中的module信息替换为最终解析的module
      if (module) {
        slot.module = module;
      } else {
        // 如果没有返回module信息,就将其中入口文件中删除掉,最终在seal封存编译结果时会从_preparedEntrypoints中取出所有入口文件进行合并处理
        const idx = this._preparedEntrypoints.indexOf(slot);
        if (idx >= 0) {
          this._preparedEntrypoints.splice(idx, 1);
        }
      }
      // 调用succeedEntry钩子处理解析完毕的module
      this.hooks.succeedEntry.call(entry, name, module);
      // 执行回调,回到compiler的make钩子
      return callback(null, module);
    }
  );
}

to

// node_modules/webpack/lib/Compiler.js

compile(callback) {
  ...

  this.hooks.beforeCompile.callAsync(params, (err) => {
    ...

    this.hooks.make.callAsync(compilation, (err) => {
      if (err) return callback(err);
      
      // 调用compilation的finish和seal方法进行代码的封存,直至最终的输出

      compilation.finish((err) => {
        if (err) return callback(err);

        compilation.seal((err) => {
          if (err) return callback(err);

          this.hooks.afterCompile.callAsync(compilation, (err) => {
            if (err) return callback(err);

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

to

// node_modules/webpack/lib/Compiler.js

const onCompiled = (err, compilation) => {
  if (err) return finalCallback(err);

  if (this.hooks.shouldEmit.call(compilation) === false) {
    ...
    return;
  }

  this.emitAssets(compilation, (err) => {
    if (err) return finalCallback(err);

    if (compilation.hooks.needAdditionalPass.call()) {
      ...
    }

    this.emitRecords((err) => {
      ...
    });
  });
};

到此一个 compilation module 处理的最终回调链路就结束了,后面做的事就是 seal 对 module 的封存处理,直到最终的结果输出。

整个回调最终回到了 compiler 的 make hook 中,我们总结一下整个编译流程:

  1. webpack 创建 compiler 实例,调用 compiler.run 开始编译

  2. 读取编译 records,最终回到 run 的回调中执行 compiler

  3. 创建 compilation 实例,执行 make 钩子开始正式编译

  4. 调用make的PersistentChildCompilerSingletonPlugig函数创建一个子编译,进行HTML模板的编译

  5. 调用make的SingleEntryPlugin钩子进行入口文件解析

  6. addEntry是入口文件的开始

  7. 执行一个并行任务,第一个进行配置Loader和行内Loader的收集,整合成一个Loader集合,集合中存放一个个对象,包含Loader的名称和路径;第二个就是入口文件的resolve

  8. 执行file的doResolve,期间会执行一个个hook来对文件内容进行处理,包括路径的映射、alias的处理、文件扩展的添加、symLink的替换等等,最终就是替换掉一切非真实的文件引用关系,便于后面的依赖收集

  9. Loader收集和doResolve完成后就是对Loader的分析,将Loader进行整合,分为preLoaders、postLoaders、loaders等

  10. Loader最终整合完毕就去以Entry和Loader信息创建一个module

  11. module创建完毕就进行build

  12. build核心就是Loader的pitch和normal方法的执行,通过这两个方法对资源进行处理

  13. 资源被Loader处理完毕就进行最终的parse,生成AST树,迭代AST Body,对每一个node进行处理,这里会通过node的type(AST上每一个node都有一个标识符,标识这个node属于哪种类型)进入不同的分支进行解析(判断是块级、条件语句、成员方法调用、循环迭代语句、try/catch、function、class等等),创建依赖,然后将依赖添加到module的dep中,如果存在import会将import替换成文件导入,方法调用的参数也会作为一个标识符进行替换处理。。。

  14. AST Body上所有的Node处理完毕就执行回调,到_addModuleChain的afterBuild中判断当前module上是否存在依赖,存在就会进行依赖加载

  15. 首先会在Compilation中的processModuleDependencies方法里对依赖进行整合排序(依赖的创建时都设置了index),整理出最终需要加载的依赖

  16. 然后就是在Compilation中addModuleDependencies方法里遍历dependencies,取出依赖的factory(NormalModuleFactory,依赖它也是一个NormalModuleFactory),然后调用NormalModuleFactory的create方法,执行factory\resolver钩子进行doResolve

  17. 这里的doResolve执行完毕后会回到addModuleDependencies的回调中执行afterBuild,判断依赖是否存在依赖,比如Vue就又有96个dependencies,此时就会回到Compilation中的processModuleDependencies再次就行依赖处理,执行addModuleDependencies,就这样进行依赖的递归收集解析

  18. 最终依赖的依赖也收集解析完毕执行回调回到_addModuleChain中,然后就到addEntry中完成module的解析