3
头图

引言

webpack@5.69.0
之前使用taro@3.5.5创建一个taro-react项目之后build了一个weapp项目

编译.png

最后一步就是启用webpackreact代码编译成weapp

webpack入口

package.json中可以找到入口文件
这个文件的作用就是引入webpack核心函数(lib/webpack.js)以及工具文件,抛出整理好之后的webpack核心函数

"main": "lib/index.js",
  1. 最终输出:

    // lib/index.js
    // mergeExports是处理fn的,最后输出的就是结果处理的fn
    module.exports = mergeExports(fn, {
     // 很多webpack内置插件
     get webpack() {
         return require("./webpack"); // 核心文件
     },
     ......
    })
  2. mergeExports

    // lib/index.js
    // 第一个参数是对象(函数继承于Fuction,Fuction继承于Object)
    const mergeExports = (obj, exports) => {
     // 克隆exports对象,传入这个对象里有很多的文件的引入
     const descriptors = Object.getOwnPropertyDescriptors(exports);
     // 遍历这个对象
     for (const name of Object.keys(descriptors)) {
         const descriptor = descriptors[name];
         if (descriptor.get) {
             const fn = descriptor.get;
             // 遍历出的属性一个个添加getter到传入的webpack函数
             Object.defineProperty(obj, name, {
                 configurable: false,
                 enumerable: true,
                 /**
                   * memoize就是执行了传入的fn返回一个返回执行之后的结果的一个函数
                   * memoize(fn)等于
                   * function(){
                   *   return fn();
                   * } 
                   */
                 get: memoize(fn)
             });
         } else if (typeof descriptor.value === "object") {
             Object.defineProperty(obj, name, {
                 configurable: false,
                 enumerable: true,
                 writable: false,
                 value: mergeExports({}, descriptor.value) // 递归
             });
         } else {
             throw new Error(
                 "Exposed values must be either a getter or an nested object"
             );
         }
     }
     // 返回了一个冻结之后的对象,这个对象是个函数,函数里面有很多添加进去的属性方法
     return /** @type {A & B} */ (Object.freeze(obj)); 
    };

    打印descriptor可以得到如下

    {
      get: [Function: get webpack],
      set: undefined,
      enumerable: true,
      configurable: true
    }
    ...
    {
      value: {
     ModuleDependency: [Getter],
     ConstDependency: [Getter],
     NullDependency: [Getter]
      },
      writable: true,
      enumerable: true,
      configurable: true
    }
    ...
    
  3. fn:

    // lib/index.js
    // fn懒加载出来的webpack核心函数
    const fn = lazyFunction(() => require("./webpack"));
  4. lazyFunction:这里我们可以知道webpack核心函数(lib/webpack)是怎么接受参数的

    // lib/index.js
    const lazyFunction = factory => {
     // 和mergeExports一样返回一个返回执行了factory后的函数
     const fac = memoize(factory); // fac函数一个函数
     const f = (
         // 这里args就是我们传入的webpack配置参数
         (...args) => {
            /** 
              * fac() 等于 factory()
              * fac()(...args) 等于 factory()(...args)
              * factory 等于 () => require("./webpack")
              * require("./webpack")(...args)
              */
             return fac()(...args); 
         }
     );
     return (f);// 最后返回f这个函数,在mergeExports里getter添加之后作为入口抛出,在前端工程化项目中webpack启动器里传入webpack配置参数执行
    };
    

webpack.js

lib/webpack.js
抛出编译器(有callback则执行编译器编译)

  1. webpack 方法 主方法,抛出编译器compiler(有callback则执行编译器编译)
    options是一个webpack配置对象或者配置对象数组,回调函数callback,有callback就可以在这里直接编译,没有则需要外部调用compiler.run()
    《build一个weapp》的packages/taro-webpack5-runner/src/index.mini.ts中 const compiler = webpack(webpackConfig) 没有传callback只传了webpack配置,正好在webpack启动器里调用了run、watch、close方法

    // lib/webpack.js
    const webpack = (options, callback) => {
     // create返回一个对象对象里有编译器、是否监听、监听参数
     const create = () => {
         // options作为数组然后用webpackOptionsSchemaCheck校验参数
         if (!asArray(options).every(webpackOptionsSchemaCheck)) { // false
            ...
         }
         let compiler; // MultiCompiler多个编译器或Compiler单个编译器
         let watch = false; // 是否热更新监听,只要有一个webpack配置是热更新就监听
         let watchOptions; // 热更新监听的参数可以是对象也可以是个对象数组
         if (Array.isArray(options)) {
             compiler = createMultiCompiler(options,options);
             watch = options.some(options => options.watch);
             watchOptions = options.map(options => options.watchOptions || {});
         } else {
             const webpackOptions = options;
             compiler = createCompiler(webpackOptions);
             watch = webpackOptions.watch;
             watchOptions = webpackOptions.watchOptions || {};
         }
         return { compiler, watch, watchOptions };
     };
     if (callback) {
         try {
             const { compiler, watch, watchOptions } = create();
             if (watch) {
                 // 开始监听
                 compiler.watch(watchOptions, callback);
             } else {
                 // 开始编译
                 compiler.run((err, stats) => {
                     compiler.close(err2 => {
                         callback(err || err2, stats);
                     });
                 });
             }
             // 抛出编译器
             return compiler;
         } catch (err) {
             process.nextTick(() => callback(err));
             return null;
         }
     } else {
         const { compiler, watch } = create();
         if (watch) {
             util.deprecate(
                 () => {},
                 "A 'callback' argument needs to be provided to the 'webpack(options, callback)' function when the 'watch' option is set. There is no way to handle the 'watch' option without a callback.",
                 "DEP_WEBPACK_WATCH_WITHOUT_CALLBACK"
             )();
         }
         // 抛出编译器
         return compiler;
     }
    };
  2. createCompiler创建一个编译器

    // 参数就是webpack配置参数对象
    const createCompiler = rawOptions => {
     // 把webpack配置参数标准化
     const options = getNormalizedWebpackOptions(rawOptions);
     // 基础参数的初始化
     applyWebpackOptionsBaseDefaults(options);
     // 实例化Compiler编译器
     const compiler = new Compiler(options.context, options);
     // 拓展compiler,添加对文件的输入缓存、监听、输出、注册beforeRun钩子
     new NodeEnvironmentPlugin({
         infrastructureLogging: options.infrastructureLogging
     }).apply(compiler);
     // 注册plugin插件this指向编译器compiler
     if (Array.isArray(options.plugins)) {
         for (const plugin of options.plugins) {
             if (typeof plugin === "function") {
                 plugin.call(compiler, compiler);
             } else {
                 // packages/taro-webpack5-runner/src/plugins/MiniPlugin.ts
                 // 插件入口 TaroMiniPlugin.apply(compiler),这里会调用编译器里的compilation.addEntry、compilation.dependencyFactories
                 plugin.apply(compiler); 
             }
         }
     }
     // 基础参数的初始化
     applyWebpackOptionsDefaults(options);
     // 执行environment钩子和afterEnvironment钩子
     compiler.hooks.environment.call(); // 获取环境信息
     compiler.hooks.afterEnvironment.call(); // 获取环境信息之后
     // 根据不同的环境不同的平台等等注册内置插件,多个hook的tag挂载
     new WebpackOptionsApply().process(options, compiler);
     // 执行initialize钩子
     compiler.hooks.initialize.call(); // 初始化
     return compiler;
    };

注:以下是createCompiler里的相关的函数、类的简单介绍:

  1. applyWebpackOptionsBaseDefaults基础参数的初始化
    对标准webpack配置参数context、infrastructureLogging的初始化

    const applyWebpackOptionsBaseDefaults = options => {
     // context设置,如果options.context是undefined,options.context就等于项目路径
     F(options, "context", () => process.cwd());
     // 日志输出格式设置
     applyInfrastructureLoggingDefaults(options.infrastructureLogging);
    };
  2. applyWebpackOptionsDefaults基础参数的初始化
    对标准webpack配置参数context、target、devtool...的初始化

    const applyWebpackOptionsDefaults = options => {
      ...
    };
  3. NodeEnvironmentPlugin拓展compiler,添加对文件的输入缓存、监听、输出、注册beforeRun钩子

    class NodeEnvironmentPlugin {
     constructor(options) {
         this.options = options; // webpack配置参数标准化
     }
    
     apply(compiler) {
         // infrastructureLogging:日志输出格式设置
         const { infrastructureLogging } = this.options;
         // 日志打印器ConsoleLogger打印器
         compiler.infrastructureLogger = createConsoleLogger({
           ...
         });
         // 输入缓存文件操作
         compiler.inputFileSystem = new CachedInputFileSystem(fs, 60000);
         // 输入文件系统
         const inputFileSystem = compiler.inputFileSystem;
         // 输出文件系统,挂载到compiler对象(graceful-fs就是对node原生fs做了一层封装本质还是node的fs)
         compiler.outputFileSystem = fs;
         compiler.intermediateFileSystem = fs;
         // 监听文件系统,挂载到compiler对象
         compiler.watchFileSystem = new NodeWatchFileSystem(
             compiler.inputFileSystem
         );
         // 注册beforeRun钩子 在执行compiler.run前执行
         compiler.hooks.beforeRun.tap("NodeEnvironmentPlugin", compiler => {
             if (compiler.inputFileSystem === inputFileSystem) {
                 compiler.fsStartTime = Date.now();
                 inputFileSystem.purge(); // 清除缓存
             }
         });
     }
    }
  4. WebpackOptionsApply注册内置插件

    class WebpackOptionsApply extends OptionsApply {
     constructor() {
         super();
     }
     process(options, compiler) {
        // 注册内置插件
        ...
        new ExternalsPlugin(...).apply(compiler); // web 配置外部文件的模块加载
        new ChunkPrefetchPreloadPlugin().apply(compiler);
        new ArrayPushCallbackChunkFormatPlugin().apply(compiler);
        new EnableChunkLoadingPlugin(type).apply(compiler);
        new EnableWasmLoadingPlugin(type).apply(compiler);
        ...
        // 执行完成注册内置插件钩子
        compiler.hooks.afterPlugins.call(compiler);
        ...
        compiler.hooks.afterResolvers.call(compiler);
     }
    }

[Plugins].apply

MiniPlugin为例
node_modules/@tarojs/webpack5-runner/dist/plugins/MiniPlugin.js
上面我们说了在lib/webpack.js调用了MiniPlugin.apply,这里就注册多个hook

// 插件入口
apply (compiler: Compiler) {
   ...
    // 配置获取入口文件路径
   this.appEntry = this.getAppEntry(compiler)
   ...
   // 注册钩子run, lib/Compiler.js的run中使用也就是在开始编译的时候只用
   compiler.hooks.run.tapAsync(
     ...
   )
   // 注册钩子watchRun lib/Watching.js里使用用于监听文件改变重新注册run钩子
   compiler.hooks.watchRun.tapAsync(
     ...
   )
   // 注册钩子make,lib/Compiler.js的compile里使用
   compiler.hooks.make.tapAsync(
     ...
   )
   // 注册钩子compilation,lib/Compiler.js的new Compilation后执行
   compiler.hooks.compilation.tap(
     ...
   )
   // 注册钩子afterEmit
   compiler.hooks.afterEmit.tapAsync(
     ...
   )
   // 实例化TaroNormalModulesPlugin并调用apply
   new TaroNormalModulesPlugin(this.options.onParseCreateElement).apply(compiler)
}

Hook的注册和使用

this.hooks.make.callAsync为例

  1. AsyncParallelHook异步钩子

    AsyncParallelHook
  2. 调用钩子

    // lib/Compiler.js的compile里
    this.hooks.make.callAsync(compilation, () => {...})
  3. 实例化钩子

    // lib/Compiler.js的constructor里初始化
    this.hooks = Object.freeze({
       ...
       // 传入了要执行的钩子compilation,实例化异步钩子AsyncParallelHook
       make: new AsyncParallelHook(["compilation"]), 
       ...
    })
  4. AsyncParallelHook异步钩子

    // node_modules/tapable/lib/AsyncParallelHook.js
    function AsyncParallelHook(args = [], name = undefined) {
     // args = ["compilation"]
     const hook = new Hook(args, name); // 实例化一个钩子
     hook.constructor = AsyncParallelHook;
     // 重写hook.compile
     hook.compile = COMPILE;
     hook._call = undefined;
     hook.call = undefined;
     // 返回hook可以在第一步hook.callAsync(compilation, () => {...})执行
     return hook;
    }
  5. hook.callAsync

    // node_modules/tapable/lib/Hook.js
    constructor(args = [], name = undefined) {
      ...
      this.callAsync = CALL_ASYNC_DELEGATE;
      ...
    }
  6. CALL_ASYNC_DELEGATE

    // node_modules/tapable/lib/Hook.js
    const CALL_ASYNC_DELEGATE = function(...args) {
     this.callAsync = this._createCall("async"); // this执行Hooks,创建一个异步
     return this.callAsync(...args); // 执行异步并返回结果
    };
  7. _createCall

     // node_modules/tapable/lib/Hook.js
     _createCall(type) {
         // 执行this.compile,this.compile在AsyncParallelHook重写为COMPILE
         return this.compile({
             taps: this.taps,
             interceptors: this.interceptors,
             args: this._args,
             type: type // "async"
         });
     }
  8. COMPILE

    // node_modules/tapable/lib/AsyncParallelHook.js
    const factory = new AsyncParallelHookCodeFactory(); // 用于创建函数体
    const COMPILE = function(options) {
     factory.setup(this, options); // 收集函数this指向Hook
     return factory.create(options); // 创建函数体
    };
     setup(instance, options) {
         instance._x = options.taps.map(t => t.fn);
     }
  9. AsyncParallelHookCodeFactory基础HookCodeFactory钩子函数异步创建工厂

    // node_modules/tapable/lib/HookCodeFactory.js
    create(options) {
      ...
      let fn;
      // 在5中this._createCall("async"),_createCall里又把"async"作为type传给this.compile
      // 这里的this.options.type = "async"
      switch (this.options.type) {
         ...
         case "async":
            fn = new Function(
                 this.args({after: "_callback"}), // 结尾加一个_callback参数
                 '"use strict";\n' + this.header() +
                  this.contentWithInterceptors({
                     onError: err => `_callback(${err});\n`,
                     onResult: result => `_callback(null, ${result});\n`,
                     onDone: () => "_callback();\n"
                  }) // 创建一个异步函数体
               );   
          break;
          ...    
      }
    return fn
    }
  10. 所创建的函数体fn

     // CALL_ASYNC_DELEGATE执行this.callAsync(...args)也就睡fn(compilation, () => {})
     function fn(compilation, _callback) {
         "use strict";
         var _context;
         var _x = this._x; // 5. COMPILE收集到的fn(this指向Hook)
         var _taps = this.taps;
         var _interceptors = this.interceptors; 
         _interceptors[0].call(compilation);
         var _fn0 = _x[0]; // 收集的第一个fn执行
         _fn0(compilation, (function (_err0) {
             if (_err0) {
                 _callback(_err0);
             } else {
                 _interceptors[0].done(); // ProgressPlugin进度条完成完成
                 _callback();
             }
         }));
     }

    options打印这个就是传入的compilation,也是执行taps[0].fn(compilation, () => {})

    {
      taps: [
         {
           type: 'async',
           fn: [Function (anonymous)],
           name: 'TaroMiniPlugin' // 从taro项目转入的插件
         }
      ],
      interceptors: [
         {
           name: 'ProgressPlugin', // 进度条
           call: [Function: call],
           done: [Function: done]
         }
      ],
      args: [ 'compilation' ],
      type: 'async'
    }
  11. apply
    taps怎么来,MiniPlugin的apply里调用compiler.hooks.make.tapAsync钩子生成tagsapplycreateCompiler(lib/webpack.js)里调用的

    //node_modules/@tarojs/webpack5-runner/dist/plugins/MiniPlugin.js
    apply (compiler: Compiler) {
      ...
      compiler.hooks.make.tapAsync( // tapAsync
       PLUGIN_NAME,
       this.tryAsync<Compilation>(async compilation => {
         const dependencies = this.dependencies
         const promises: Promise<null>[] = []
         this.compileIndependentPages(compiler, compilation, dependencies, promises)
         dependencies.forEach(dep => {
           promises.push(new Promise<null>((resolve, reject) => {
             compilation.addEntry(this.options.sourceDir, dep, {
               name: dep.name,
               ...dep.options
             }, err => err ? reject(err) : resolve(null))
           }))
         })
         await Promise.all(promises)
         await this.options.onCompilerMake?.(compilation)
       })
     )
    ...
    }
  12. Hooks.tapAsync注册钩子
    调用tapAsync又回到node_modules/tapable/lib/Hook.js

    node_modules/tapable/lib/Hook.js
    // 传入的参数options, fn,options = 'TaroMiniPlugin',fn就是上面那个this.tryAsync(...)的结果,结果也是tags里的fn
    tapAsync(options, fn) {
     this._tap("async", options, fn);
    }
    // node_modules/tapable/lib/Hook.js
    _tap(type, options, fn) {
     if (typeof options === "string") {
         options = {
             name: options.trim() //添加name
         };
     } ... 
     if (typeof options.context !== "undefined") {
         deprecateContext();
     }
     options = Object.assign({ type, fn }, options);// 添加fn,type以及options里的其他值,fn这里就是我们最终的tags
     options = this._runRegisterInterceptors(options);
     this._insert(options);// 把钩子注册到taps
    }
    // 注册成这样
    [
      {
         type: 'async',
         fn: [Function (anonymous)],
         name: 'TaroMiniPlugin'
      }
    ]

    总结:
    (1)就是通过tapAsync、tap、tapPromise注册钩子、
    (2)通过callAsync、call、callPromise执行钩子
    (3)执行注册的fn就是this.tryAsync(...)(compilation, () => {})

Compiler

lib/Compiler.js将编译好的文件输出
webpack的主要引擎,在compiler对象记录了完整的webpack环境信息,在webpack从启动到结束,compiler只会生成一次。你可以在compiler对象上读取到webpack config信息,outputPath

class Compiler {
    constructor(context, options = ({})) {
       super();
       this.hooks = Object.freeze({
          initialize: new SyncHook([]),
          shouldEmit: new SyncBailHook(["compilation"]),
          ...
       });
       this.webpack = webpack;
       ...
    }
    watch(watchOptions, handler) { ... }
    run(callback) { ... }
    purgeInputFileSystem() { ... }
    compile(callback) { ... }
    close(callback) { ... }
    ...    
}
  1. run

     run(callback) {
         // 重复执行报错
         if (this.running) {
             return callback(new ConcurrentCompilationError());
         }
    
         let logger; // 日志打印
         // 结束回调(无论执行成功还是失败)
         const finalCallback = (err, stats) => {
             ...
         };
         // 此刻时间
         const startTime = Date.now();
         // 执行过了将running变为true,用于判断是否重复执行
         this.running = true;
         const onCompiled = (err, compilation) => {
             ...
         };
         // 执行
         const run = () => {
             this.hooks.beforeRun.callAsync(this, err => {
                 if (err) return finalCallback(err);
    
                 this.hooks.run.callAsync(this, err => {
                     if (err) return finalCallback(err);
    
                     this.readRecords(err => {
                         if (err) return finalCallback(err);
                         // 传入onCompiled,this.compile继续执行
                         this.compile(onCompiled);
                     });
                 });
             });
         };
    
         if (this.idle) {
             this.cache.endIdle(err => {
                 if (err) return finalCallback(err);
                 this.idle = false;
                 run();
             });
         } else {
             run();
         }
     }
  2. compile完成编译

     compile(callback) {
         //  参数是:普通模块工厂normalModuleFactory、上下文模块工厂contextModuleFactory
         const params = this.newCompilationParams();
         // 异步调用beforeCompile钩子
         this.hooks.beforeCompile.callAsync(params, err => {
             if (err) return callback(err);
             // 调用compile钩子
             this.hooks.compile.call(params);
             // 获取一个编译器Compilation!!!!!!
             const compilation = this.newCompilation(params);
    
             const logger = compilation.getLogger("webpack.Compiler");
    
             logger.time("make hook");
             // 入口文件开始,构建模块,直到所有模块创建结束
             // 执行make钩子会调用compilation上的addEntry
             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 => {
                                 logger.timeEnd("seal compilation");
                                 if (err) return callback(err);
    
                                 logger.time("afterCompile hook");
                                 // 异步调用afterCompile,返回回调函数
                                 this.hooks.afterCompile.callAsync(compilation, err => {
                                     logger.timeEnd("afterCompile hook");
                                     if (err) return callback(err);
                                     // 执行一些列钩子后,执行run里的onCompiled,onCompiled(null, compilation)
                                     return callback(null, compilation);
                                 });
                             });
                         });
                     });
                 });
             });
         });
     }
  3. run里的onCompiled

         const onCompiled = (err, compilation) => {
             if (err) return finalCallback(err);
             //如果没有编译完成
             if (this.hooks.shouldEmit.call(compilation) === false) {
                 compilation.startTime = startTime;
                 compilation.endTime = Date.now();
                 const stats = new Stats(compilation);
                 this.hooks.done.callAsync(stats, err => {
                     if (err) return finalCallback(err);
                     return finalCallback(null, stats);
                 });
                 return;
             }
    
             process.nextTick(() => {
                 logger = compilation.getLogger("webpack.Compiler");
                 logger.time("emitAssets");
                 // 输出编译后的文件,调用emitAsset方法,emitAsset主要负责写入文件输出文件,不影响我们先看编译(通过this.outputFileSystem、mkdirp、emitFiles输出文件)
                 this.emitAssets(compilation, err => {
                     logger.timeEnd("emitAssets");
                     if (err) return finalCallback(err);
                     // 如果上传的编译没有完成则重来一遍编译
                     if (compilation.hooks.needAdditionalPass.call()) {
                         compilation.needAdditionalPass = true;
    
                         compilation.startTime = startTime;
                         compilation.endTime = Date.now();
                         logger.time("done hook");
                         const stats = new Stats(compilation);
                         this.hooks.done.callAsync(stats, err => {
                             logger.timeEnd("done hook");
                             if (err) return finalCallback(err);
    
                             this.hooks.additionalPass.callAsync(err => {
                                 if (err) return finalCallback(err);
                                 this.compile(onCompiled);
                             });
                         });
                         return;
                     }
                     // 完成编译
                     logger.time("emitRecords");
                     this.emitRecords(err => {
                         logger.timeEnd("emitRecords");
                         if (err) return finalCallback(err);
                         // 编译开始和编译结束时间
                         compilation.startTime = startTime;
                         compilation.endTime = Date.now();
                         logger.time("done hook");
                         const stats = new Stats(compilation);
                         this.hooks.done.callAsync(stats, err => {
                             logger.timeEnd("done hook");
                             if (err) return finalCallback(err);
                             this.cache.storeBuildDependencies(
                                 compilation.buildDependencies,
                                 err => {
                                     if (err) return finalCallback(err);
                                     // 编译完成返回new Stats(compilation)表示完成编译
                                     return finalCallback(null, stats);
                                 }
                             );
                         });
                     });
                 });
             });
         };
  4. emitAssets收集编译好的文件之后创建文件

    emitAssets(compilation, callback) {
    let outputPath;
    // 收集编译好的文件
    const emitFiles = err => {
        const assets = compilation.getAssets();
        compilation.assets = { ...compilation.assets };
        const caseInsensitiveMap = new Map();
        const allTargetPaths = new Set();
        // 对每个file资源文件进行路径拼接,将每个source源码转换为buffer(为了性能提升),最后将文件写入真实目标路径
        asyncLib.forEachLimit(...)
        ...
    }
    // 创建文件
    this.hooks.emit.callAsync(compilation, err => {
        if (err) return callback(err);
        outputPath = compilation.getPath(this.outputPath, {});
        mkdirp(this.outputFileSystem, outputPath, emitFiles);
      });
    }
  5. Compiler的流程线
    Compiler的流程线.png

Compilation

lib/Compilation.js
使用对应 loader 对文件进行转换处理,解析文件的ast语法树,创建bundles,生成chunks,在Compiler.js实例化用于编译,代表了一次单一的版本构建和生成资源。Compiler的流程线里调用hooks.make钩子会执行Compilation.addEntry
(1)addEntry:启动构建入口模块,成功后将入口模块添加到程序之中,调用hooks.addEntry,执行Compilation.addModule
(2)finish:完成编译,收集了每个模块构建是产生的问题。
(3)seal:结束编译,生成chunks,对chunks进行一系列的优化。

  1. addEntry参数:context项目路径,entry入口依赖,optionsOrName项目配置或者项目名,callback回调。
    (1)addEntry整理好optionsOrName后执行Compilation._addEntryItem

    // Compilation._addEntryItem
    _addEntryItem(context, entry, target, options, callback) {
     // 第一步生成entryData大致如下
     let entryData = {
         dependencies: [],
         includeDependencies: [],
         options: {
            name: undefined,
            ...options
         },
         [target]: entry
     }
     ...
     // 把entryData存入entries
     this.entries.set(name, entryData);
     ...
     // 调用hooks.addEntry 让this.entries.get(name)的options变成{ name: 名对应字, runtime: 'runtime'}
     this.hooks.addEntry.call(entry, options);
     // 调用Compilation.addModuleTree
     this.addModuleTree({...参数}, 一个callback函数);
    }

    (2)hooks.addEntry挂载了两个fnProgressPlugin是进度条。

    taps: [
      {
        type: 'sync',
        fn: [Function (anonymous)],
        name: 'RuntimeChunkPlugin'
      },
      { 
        type: 'sync', 
        fn: [Function: entryAdd], 
        name: 'ProgressPlugin' 
      }
    ],
    

    (3)RuntimeChunkPlugin里的addEntry.tap

    // lib/optimize/RuntimeChunkPlugin.js
    compilation.hooks.addEntry.tap(
      "RuntimeChunkPlugin",
      (_, { name: entryName }) => {
       if (entryName === undefined) return;
       const data = compilation.entries.get(entryName);
       if (data.options.runtime === undefined && !data.options.dependOn) {
           // 确定运行时区块名称
           let name = this.options.name;
           if (typeof name === "function") {
               name = name({ name: entryName });
           }
           data.options.runtime = name; // 让entries里对应entryName的值的runtime = 'runtime'
       }
      }
    );

    (4)addModuleTree

    addModuleTree({ context, dependency, contextInfo }, callback) {
      ...
      // this.dependencyFactories是Map,在初始化注册插件apply的时候set
      const Dep = dependency.constructor; // class TaroSingleEntryDependency extends ModuleDependency
      // 根据对应的Dep获取对应的模块工厂,在Compiler.compile中可以知道分成普通模块工厂normalModuleFactory和上下文模块工厂contextModuleFactory
      const moduleFactory = this.dependencyFactories.get(Dep);
      // 执行Compilation.handleModuleCreation
      this.handleModuleCreation({...参数}, 一个callback函数)     
    }

    (5)handleModuleCreation

    handleModuleCreation({ factory, dependencies, originModule, contextInfo, context, recursive = true, connectOrigin = recursive },callback) {
      // ModuleGraph用于记录依赖关系
      const moduleGraph = this.moduleGraph;  
      // 配置文件 = undefined
      const currentProfile = this.profile ? new ModuleProfile() : undefined;
      
      this.factorizeModule({...参数}, 一个callback函数)
    }

    (6)factorizeModule

    Compilation.prototype.factorizeModule = function (options, callback) {
      this.factorizeQueue.add(options, callback); // 放入到异步队列里
    };
    constructor(compiler, params) {
      ...
      // factorizeQueue是AsyncQueue的实例,作用是异步队列控制,将callback存入callbacks
      this.factorizeQueue = new AsyncQueue({
         name: "factorize",
         parent: this.addModuleQueue,
         // 达到条件会后执行processor,会返回一个【modulefactory】,执行factory.create,执行完成之后会执行回调callbacks里的callback
         processor: this._factorizeModule.bind(this)
      });
      ...
    }
    _factorizeModule({currentProfile, factory, dependencies, originModule, factoryResult, contextInfo, context}, callback){
      ...
      factory.create({...参数}, 个callback函数);
    }

    (7)normalModuleFactorycontextModuleFactory

    【normalModuleFactory】常用来处理import A from './a'这种导入语句的文件
    
    【contextModuleFactory】常用来处理const b = require('./b/' + c)这种带变量的导入语句,在编译阶段webpack没办法识别真正运行时该用哪个文件,因此会将b目录下的所有文件都进行编译

    (8)factory.create我们在项目里都是用normal的方式引入,用于构建module

    // lib/NormalModuleFactory.js
    create(data, callback) {
     ...
     // 创建resolveData
     const resolveData = {....};
     // 执行hooks.beforeResolve
     this.hooks.beforeResolve.callAsync(resolveData, (err, result) => {
         ...
         // 执行hooks.factorize(NormalModuleFactory初始化的时候挂载),内部会执行hooks.resolve和hooks.createModule
         this.hooks.factorize.callAsync(resolveData, (err, module) => {
             ...
             const factoryResult = {...};
             callback(null, factoryResult); // 获得factoryResult执行回调
         })   
     })
    }

    (9)hooks.factorize

    // NormalModuleFactory初始化
    this.hooks.factorize.tapAsync({ name: "NormalModuleFactory", stage: 100},(resolveData, callback) => {
      // 生成对应的`loader`和依赖信息
      this.hooks.resolve.callAsync(resolveData, (err, result) => {
          ...
          this.hooks.afterResolve.callAsync(resolveData, (err, result) => {
              ...
              this.hooks.createModule.callAsync(createData, resolveData, () => {
                  ...
                  createdModule = this.hooks.module.call(
                     createdModule,
                     createData,
                     resolveData
                  );
                  return callback(null, createdModule); // module构建完成
              })
          })
      })
    })

    (10)hooks.resolve,解析NormalModuleFactory,生成对应的loader和依赖信息

    // NormalModuleFactory初始化 data = resolveData
    this.hooks.resolve.tapAsync({ name: "NormalModuleFactory", stage: 100},(data, callback) => {
      ...
      // 获取一个解析loader的工具
      const loaderResolver = this.getResolver("loader");
      // ...
    }
    // type = "loader" resolveOptions = undefined
    getResolver(type, resolveOptions) {
      return this.resolverFactory.get(type, resolveOptions);
    }
    // lib/ResolverFactory.js
    // ResolverFactory.get
    get(type, resolveOptions = EMPTY_RESOLVE_OPTIONS) {
      // 获取缓存
      let typedCaches = this.cache.get(type);
      // 没有则创建缓存并存入
      if (!typedCaches) {
          ...
          this.cache.set(type, typedCaches);
      }  
      // 如果存在resolver直接返回resolver
      const cachedResolver = typedCaches.direct.get(resolveOptions);  
      if (cachedResolver) {
          return cachedResolver;
      } 
      ...
      // 创建了一个resolver并返回
      // this._create通过require("enhanced-resolve").ResolverFactory.createResolver(resolveOptions)来创建resolver
      const newResolver = this._create(type, resolveOptions);
      ...
      return newResolver;     
    }

perkz
51 声望28 粉丝