Webpack 系列之模块对象

时间:2021-2-20 作者:admin

Webpack 就像一条生产线,要经过一系列处理流程后才能将源文件转换成输出结果。 这条生产线上的每个处理流程的职责都是单一的,多个流程之间存在依赖关系,只有完成当前处理后才能交给下一个流程去处理。 插件就像是一个插入到生产线中的一个功能,在特定的时机对生产线上的资源做处理。 Webpack 通过 Tapable 来组织这条复杂的生产线。 Webpack 在运行过程中会广播事件,插件只需要监听它所关心的事件,就能加入到这条生产线中,去改变生产线的运作。 Webpack 的事件流机制保证了插件的有序性,使得整个系统扩展性很好。 –吴浩麟《深入浅出webpack》

Tapable

Webpack 的事件流机制是靠 Tapable 实现的,Tapable 是 Webpack 自带的模块,不需要单独安装,在 Webpack 被安装的同时它也会一并被安装。Tapable 主要提供了以下钩子,分为同步 / 异步,异步又分为并行和串行。

同步:SyncHook / SyncWaterfallHook / SyncBailHook / SyncLoopHook

异步

  • 异步并行:AsyncParallelHook, AsyncParallelBailHook
  • 异步串行:AsyncSeriesHook, AsyncSeriesBailHook, AsyncSeriesWaterfallHook
  • 其中 AsyncParallelBailHook 和 AsyncSeriesBailHook 在 Webpack 中没有使用,在此就不讲了

注册 & 触发

  • 同步:tap & call
  • 异步:tap & call, tapAsync & callAsync, tapPromise & promise
用法
  • 创建钩子

    const hook = new SyncHook(['arg1', 'arg2'])
    

    所有类型的钩子都有一个可选的参数,参数是一个字符串数组,每个字符串代表注册函数的参数名。

  • 注册

    hook.tap('plugin1', (arg1, arg2) => {
      // TODO:
    })
    hook.tap('plugin2', (arg1, arg2) => {
      // TODO:
    })
    

    传入一个名字,注册函数可以接收创建钩子时定义的参数。一个钩子可以注册多个函数。

  • 触发

    hook.call(arg1, arg2) // 同步
    hook.callAsync(arg1, arg2, callback) // 异步
    

    异步钩子在触发时可以在最后传递一个 callback

    实际使用的时候,创建钩子和触发的代码通常在一个类中,注册的代码在另一个类(插件)中。

表述统一

为了表述上的统一,我定义了几个概念

  • 注册函数、注册函数的回调、最终回调

    const hook = new AsyncParallelHook()
    hook.tapAsync('plugin1', function listener(callback) { // listener 是注册函数,callback 是注册函数的回调
      console.log('注册函数')
      callback()
    })
    hook.callAsync(function finalCb(err) { console.log('最终回调') })  // finalCb 是最终回调
    
  • 注册函数中报错:调用 callback 时传了 truthy 类型的参数;

    注册函数中没报错:调用 callback 时传了 falsy 类型的参数(undefined, false, null);

    调用 callback 时,AsyncParallelHook & AsyncSeriesHook 接收一个参数 err,表示报错信息;AsyncParallelBailHook & AsyncSeriesBailHook 接收两个参数 err & result,分别表示报错信息和注册函数的返回值。

SyncHook
  • 注册函数按注册顺序执行

  • demo

    // Car.js
    import { SyncHook } from 'tapable'
    export default class Car {
      constructor() {
        this.hooks = {
          start: new SyncHook(),
        }
      }
      start() {
        this.hooks.start.call()
      }
    }
    
    // index.js
    import Car from './Car'
    const car = new Car()
    car.hooks.start.tap('startPlugin1', () => console.log('系安全带1'))
    car.hooks.start.tap('startPlugin2', () => console.log('系安全带2'))
    car.start()
    
    // 打印
    // 系安全带1
    // 系安全带2
    
  • demo 触发时执行的函数

    function anonymous(arg1, arg2, arg3) {
      "use strict";
      var _context;
      var _x = this._x;
      var _fn0 = _x[0];
      _fn0(arg1, arg2, arg3);
    }
    
SyncWaterfallHook
  • 上一个注册函数的返回值作为下一个注册函数的输入值

  • demo

    // Car.js
    import { SyncWaterfallHook } from 'tapable'
    export default class Car {
      constructor() {
        this.hooks = {
          speed: new SyncWaterfallHook(['speed']),
        }
      }
      speed(spd) {
        this.hooks.speed.call(spd)
      }
    }
    
    // index.js
    import Car from './Car'
    const car = new Car()
    car.hooks.speed.tap('speedPlugin1', (speed) => { console.log(`加速到${speed}`); return speed + 50; })
    car.hooks.speed.tap('speedPlugin2', (speed) => { console.log(`加速到${speed}`); return speed+ 50; }) // 上一个回调返回了 100,它会作为第二个回调的参数
    car.hooks.speed.tap('speedPlugin3', (speed) => console.log(`加速到${speed}`))
    car.speed(100)
    
    // 打印
    // 加速到100
    // 加速到150
    // 加速到200
    
  • demo 触发时执行的函数

    function anonymous(arg1) { 
      "use strict";
      var _context;
      var _x = this._x;
      var _fn0 = _x[0];
      var _result0 = _fn0(arg1);
      if (_result0 !== undefined) {
        arg1 = _result0; // 这里保存结果,给下一个函数使用
      }
      var _fn1 = _x[1];
      var _result1 = _fn1(arg1);
      if (_result1 !== undefined) {
        arg1 = _result1;
      }
      return arg1;
    }
    
SyncBailHook
  • 如果上一个注册函数有返回值,剩余的注册函数都不执行

  • demo

    // Car.js
    import { SyncBailHook } from 'tapable'
    export default class Car {
      constructor() {
        this.hooks = {
          brake: new SyncBailHook(),
        }
      }
      brake() {
        this.hooks.brake.call()
      }
    }
    
    // index.js
    import Car from './Car'
    const car = new Car()
    car.hooks.brake.tap('brakePlugin1', () => { console.log('刹车1')})
    car.hooks.brake.tap('brakePlugin2', () => { console.log('刹车2'); return 1;})
    car.hooks.brake.tap('brakePlugin3', () => { console.log('刹车3')}) // 上一个回调返回了 1,所以这个回调不会执行
    car.brake()
    
    // 打印
    // 刹车1
    // 刹车2
    
  • demo 触发时执行的函数

    function anonymous(/*``*/) {
      "use strict";
      var _context;
      var _x = this._x;
      var _fn0 = _x[0];
      var _result0 = _fn0();
      if (_result0 !== undefined) { // 如果undefined直接返回,如果不是则需要进入下一个函数
        return _result0;
      } else {
        var _fn1 = _x[1];
        var _result1 = _fn1();
        if (_result1 !== undefined) {
          return _result1;
        } else {
        }
      }
    }
    
SyncLoopHook
  • 只要注册函数有返回值,就一直循环执行它

  • demo

    // Car.js
    import { SyncLoopHook } from 'tapable'
    export default class Car {
      constructor() {
        this.hooks = {
          startEngine: new SyncLoopHook(),
        }
      }
      startEngine() {
        this.hooks.startEngine.call()
      }
    }
    
    // index.js
    import Car from './Car'
    const car = new Car()
    let index = 1;
    car.hooks.startEngine.tap('startEnginePlugin1', () => {
      console.log(`启动${index}次`);
      if (index < 3) {
        index++
        return 1
      }
    })
    car.hooks.startEngine.tap('startEnginePlugin2', () => { console.log('启动成功') })
    car.startEngine()
    
    // 打印
    // 启动1次
    // 启动2次
    // 启动3次
    // 启动成功
    
AsyncParallelHook
  • 如果在某个注册函数中报错,立刻执行最终回调,并且只执行一次,不影响其他注册函数执行;如果所有的注册函数都没报错,那么先执行完所有注册函数,最后执行最终回调。

  • demo

    // Car.js
    import { AsyncParallelHook } from 'tapable'
    export default class Car {
      constructor() {
        this.hooks = {
          calculateRoutes: new AsyncParallelHook(),
        }
      }
      calculateRoutes(callback) {
        this.hooks.calculateRoutes.callAsync(callback)
      }
    }
    
    // index.js
    import Car from './Car'
    const car = new Car()
    car.hooks.calculateRoutes.tapAsync('calRoutesPlugin1', callback => {
      setTimeout(() => {
        console.log('计算路线1')
        callback()
      }, 3000)
    })
    car.hooks.calculateRoutes.tapAsync('calRoutesPlugin2', callback => {
      setTimeout(() => {
        console.log('计算路线2')
        callback()
      }, 1000)
    })
    car.calculateRoutes(err => { console.log('最终的回调, err: ', err) }) // 最终的回调
    
    // 打印
    // 如果 calRoutesPlugin1 & calRoutesPlugin2 调用 callback 的时候没有传参数,会在1s的时候打印'计算路线2',3s的时候打印'计算路线1',紧接着打印'最终的回调, err: undefined'
    // 如果 calRoutesPlugin1 & calRoutesPlugin2 分别调用 callback(1) & callback(2) ,会在1s的时候打印'计算路线2',紧接着打印'最终的回调, err: 2',3s的时候打印'计算路线1'
    
  • demo 触发时执行的函数

    function anonymous(_callback) {
      "use strict";
      var _context;
      var _x = this._x;
      do {
        var _counter = 2; // 注册函数的数量
        var _done = () => {
          _callback();
        };
    
        if (_counter <= 0) break; // 执行注册函数之前,检查 _counter,如果小于等于 0,说明某个注册函数的回调中出错,直接终止。
        var _fn0 = _x[0];
        _fn0(_err0 => { // _fn0 函数的回调调用时间不确定
          if (_err0) { // 如果有注册函数报错
            if (_counter > 0) {
              _callback(_err0);
              _counter = 0;
            }
          } else {
            if (--_counter === 0) _done() // 所有注册函数执行完都没有报错,则执行最终回调
          }
        });
    
        if (_counter <= 0) break;
        var _fn1 = _x[1];
        _fn1(_err1 => {
          if (_err1) {
            if (_counter > 0) {
              _callback(_err1);
              _counter = 0;
            }
          } else {
            if (--_counter === 0) _done();
          }
        });
      } while (false);
    }
    
AsyncSeriesHook
  • 如果在某个注册函数中报错,立刻执行最终回调,并且只执行一次,其后的注册函数不再执行;如果所有的注册函数都没有报错,那么先执行完所有注册函数,最后执行最终回调。

  • demo

    // Car.js
    import { AsyncSeriesHook } from 'tapable'
    export default class Car {
      constructor() {
        this.hooks = {
          calculateRoutes2: new AsyncSeriesHook(),
        }
      }
      calculateRoutes2(callback) {
        this.hooks.calculateRoutes2.callAsync(callback)
      }
    }
    
    // index.js
    import Car from './Car'
    const car = new Car()
    car.hooks.calculateRoutes2.tapAsync('calRoutesPlugin1', callback => {
      setTimeout(() => {
        console.log('计算路线1')
        callback()
      }, 3000)
    })
    car.hooks.calculateRoutes2.tapAsync('calRoutesPlugin2', callback => {
      setTimeout(() => {
        console.log('计算路线2')
        callback()
      }, 1000)
    })
    car.calculateRoutes2(err => { console.log('最终的回调, err: ', err) })
    
    // 打印
    // 如果 calRoutesPlugin1 & calRoutesPlugin2 调用 callback 的时候没有传参数,会在3s的时候打印'计算路线1',4s的时候打印'计算路线2',紧接着打印'最终的回调, err: undefined'
    // 如果 calRoutesPlugin1 & calRoutesPlugin2 分别调用 callback(1) & callback(2) ,会在3s的时候打印'计算路线1',紧接着打印'最终的回调, err: 1',不会执行 calRoutesPlugin2 的回调,所以不会打印'计算路线2'
    
  • demo 触发时执行的函数:

    function anonymous(_callback) {
      "use strict";
      var _context;
      var _x = this._x;
      var _fn0 = _x[0];
      _fn0(_err0 => {
        if (_err0) {
          _callback(_err0);
        } else {
          var _fn1 = _x[1];
          _fn1(_err1 => {
            if (_err1) {
              _callback(_err1);
            } else {
              _callback(); // 串行执行,最后返回 _callback
            }
          });
        }
      });
    }
    

Webpack 中的 tapable

Compiler

Compiler 模块是 Webpack 主要引擎,Webpack 启动后实例化了一个 Compiler 对象,然后调用它的 run 方法开始编译。

// /webpack/lib/Compiler.js
class Compiler extends Tapable {
  constructor(context) {
    super();
    this.hooks = { // 声明事件
      ...
      /** @type {AsyncSeriesHook<Compiler>} */
      run: new AsyncSeriesHook(["compiler"]),
      /** @type {SyncHook<Compilation, CompilationParams>} */
      compilation: new SyncHook(["compilation", "params"]),
      /** @type {AsyncParallelHook<Compilation>} */
      make: new AsyncParallelHook(["compilation"]),
    };
  }
  run(callback) {}
}
Compilation

一个 Compilation 实例代表了一次版本构建和生成资源。当运行 Webpack 开发环境中间件时,每当检测到一个文件变化,就会开启一次新的编译,从而生成一组新的资源。一个 Compilation 实例表现了当前的模块资源、生成资源、变化的文件、以及被跟踪依赖的状态信息,也提供了很多关键点钩子供插件做自定义处理时选择使用。

// /webpack/lib/Compilation.js
class Compilation extends Tapable {
  constructor(compiler) {
    super();
    this.hooks = {
      ...
      /** @type {SyncHook} */
      seal: new SyncHook([]),
    };
    modules: [],
    _modules: [],
    entries: [],
  }
}

模块对象

前面讲了 Tapable 各种类型的钩子,因为和构建息息相关的 Compiler & Compilation 都是继承 Tapable,学习它能更好理解 Webpack 的编译过程,下面正式开始。

注意:下面的 Webpack 源码是部分截取或源码的简单写法,重点在主要构建流程,不纠结细枝末节。

我们先定义下入口文件和配置文件

// src/make/index.js
import add from './b.js'
add(1, 2)
import('./c.js').then(del => del(1, 2))

// src/make/b.js
export default function add(n1, n2) {
  return n1 + n2
}

// src/make/c.js
export default function add(n1, n2) {
  return n1 + n2
}

// webpack.config.js
module.exports = {
  entry: {
    app: './src/make/index.js' // 单入口文件
  }
}

启动 Webpack

// 这里模仿 webpack-cli 中的代码,相当于在命令行里输入 webpack
const options = require("./webpack.config.js");
const compiler = webpack(options);
compiler.run();
初始化

这个阶段主要做了以下几件事:

  1. 校验参数 options
  2. 整合 options
  3. 创建 compiler 对象
  4. 加载外部插件
  5. 加载内部插件
  6. 返回 compiler 对象

我们来看看主要代码:

// /webpack/lib/webpack.js
const webpack = (options, callback) => {
   // 1: 校验参数 options
  const webpackOptionsValidationErrors = validateSchema(
    webpackOptionsSchema,
    options
  );

  // 2: 整合 options:有命令行中的配置,webpack.config.js 中的配置,Webpack 的默认配置
  options = new WebpackOptionsDefaulter().process(options);

  // 3: 创建 compiler 对象
  compiler = new Compiler(options.context);

  // 4: 加载外部插件
  if (options.plugins && Array.isArray(options.plugins)) {
    for (const plugin of options.plugins) {
      plugin.apply(compiler);
    }
  }

  // 5: 加载内部插件
  compiler.options = new WebpackOptionsApply().process(options, compiler);

  // 6: 返回 compiler 对象
  return compiler;
}

// /webpack/lib/WebpackOptionsApply.js
class WebpackOptionsApply extends OptionsApply {
  process(options, compiler) {
    ...                                      // 其他内部插件
    new EntryOptionPlugin().apply(compiler); // EntryOptionPlugin 插件,监听了 entryOption 钩子
    compiler.hooks.entryOption.call(options.context, options.entry); // 触发 entryOption

    new HarmonyModulesPlugin(options.module).apply(compiler); // EntryOptionPlugin 插件,监听了 compilation 钩子
  }
}

Webpack 定义了很多内部插件,其中,跟构建流程关联比较大的是 EntryOptionPlugin,它通过解析 options.entry 属性创建不同的插件,例如: SingleEntryPlugin, MultiEntryPlugin, DynamicEntryPlugin。由于我们是单入口,所以会创建 SingleEntryPlugin,它内部监听了 compiler 对象的 compilation & make 钩子。

// /webpack/lib/EntryOptionPlugin.js
const itemToPlugin = (context, item, name) => {
  if (Array.isArray(item)) {
    return new MultiEntryPlugin(context, item, name);
  }
  return new SingleEntryPlugin(context, item, name);
};
class EntryOptionPlugin {
  apply(compiler) {
    compiler.hooks.entryOption.tap("EntryOptionPlugin", (context, entry) => {
      if (typeof entry === "string" || Array.isArray(entry)) {
        itemToPlugin(context, entry, "main").apply(compiler);
      } else if (typeof entry === "object") {
        for (const name of Object.keys(entry)) {
          itemToPlugin(context, entry[name], name).apply(compiler);
        }
      } else if (typeof entry === "function") {
        new DynamicEntryPlugin(context, entry).apply(compiler);
      }
      return true;
    });
  }
};
创建 & 构建模块对象

Webpack 的核心原理是:一切皆模块,js / css / 图片都是模块。实际构建过程中,Webpack 并没有直接生成一个模块,而是先生成了一个依赖,依赖会对应一个模块工厂函数,再通过这个工厂函数去创建模块对象。以单文件入口为例,先生成一个 SingleEntryDependency,它对应的模块工厂函数是 NormalModuleFactory,再通过它生成一个 NormalModule 实例。Webpack 中还有很多其他类型的 Module。同时依赖还会对应一个模块模版函数

Webpack 默认只能解析 js, json, wasm 类型的文件,解析 js 的是 Parser,解析 json 的是 jsonParser,其他类型的文件(css / jpg 等)需要通过 loader 转换成 js 来兼容。创建 Module 实例之后,开始构建这个 Module,先加载 loaders 生成内容,再根据文件类型使用对应的解析器生成 AST。

注意:Webpack 中大量异步的写法使用的是 callback,而不是我们现在习惯的 promise & await,习惯这种写法能更好的看懂源码,例如:

// 使用 await 写异步
const { err, module } = await moduleFactory.create(params);
// Webpack 源码
moduleFactory.create(params, (err, module) => { ... })

接下来我们来看是如何构建 Module 的:

compiler.run() 中,调用了 this.compile(),在 compile() 中,

  • 调用 newCompilation() ,创建了一个 compilation 对象,并且触发了 compilation 钩子;
  • 接着触发 make 钩子。
// /webpack/lib/Compiler.js
class Compiler extends Tapable {
  constructor(context) {
    this.hooks = {
      ...
      beforeRun,
      run,
      make,
    }
  }
  run(callback) {
    ...
    this.hooks.beforeRun.callAsync(this, err => {
      this.hooks.run.callAsync(this, err => {
        ...
        this.compile(onCompiled);
      });
    });
  },
  compile(callback) {
    const params = this.newCompilationParams();
    const compilation = this.newCompilation(params);
    this.hooks.make.callAsync(compilation, err => {})
  }
}

// /webpack/lib/SingleEntryPlugin.js
apply(compiler) {
  compiler.hooks.compilation.tap("SingleEntryPlugin", (compilation, { normalModuleFactory }) => {
    compilation.dependencyFactories.set(
      SingleEntryDependency,
      normalModuleFactory
    );
  });
  compiler.hooks.make.tapAsync("SingleEntryPlugin", (compilation, callback) => {
    const { entry, name, context } = this;
    const dep = SingleEntryPlugin.createDependency(entry, name);
    compilation.addEntry(context, dep, name, callback);
  });
}

由于 SingleEntryPlugin 监听了 compiler 对象的 compilation & make 钩子,它在 compilation 阶段定义了SingleEntryDependency 对应的模块工厂函数 NormalModuleFactory,在 make 阶段调用 addEntry 创建并构建了一个 NormalModule 对象。我们来看看主要代码:

addEntry -> _addModuleChain -> buildModule -> processModuleDependencies

// /webpack/lib/Compilation.js
addEntry(context, entry, name, callback) {
  this._addModuleChain(context, entry)
}
_addModuleChain(context, dependency) {
  const Dep = dependency.constructor;
  const moduleFactory = this.dependencyFactories.get(Dep);
  const dependentModule = moduleFactory.create() // 创建 NormalModule 对象
  const dependentModuleWithDeps = this.buildModule(dependentModule) // 构建 NormalModule 对象
  this.processModuleDependencies(dependentModuleWithDeps, callback); // 处理 NormalModule 对象的依赖
}
buildModule(module) {
  module.build()
}
processModuleDependencies(module) {
  const dependencies = new Map();
  module.dependencies.map(dep => {
    // 有 resourceIdent 的才是我们通常理解的依赖,比如:require & import 引入的模块,需要生成新的模块
    const resourceIdent = dep.getResourceIdentifier();
    if (resourceIdent) {
      const factory = this.dependencyFactories.get(dep.constructor);
      const innerMap = new Map([[resourceIdent, []]]);
      innerMap.get(resourceIdent).push(dep);

      dependencies.set(factory, innerMap);
    }
  })
  this.addModuleDependencies(module, dependencies);
}
// 递归解析模块依赖
addModuleDependencies(module, dependencies) {
  dependencies.map(dep => {
    // 创建模块 -> 构建模块 ...
    const factory = dep.factory
    const dependentModule = factory.create()
    this.buildModule(dependentModule)
  })
}
// NormalModule.js
build() {
  const result = runLoaders(this.resource, this.loaders) // 加载 loaders,this.resource 是文件路径
  this.source = result
  this.parser.parse(this.source) // 将 loaders 处理后的内容转换成 AST;遍历 AST,收集 NormalModule 的依赖
}

// NormalModuleFactory.js
class NormalModuleFactory {
  create(data) {
    // 资源路径,如果是入口依赖则这个就是入口文件路径
    const request = data.request
    // 资源类型
    const type = this.getType(request)
    return new NormalModule({
      // 创建解析该模块需要的loader
      loaders: this.createLoaders(request)
      // 获取模块解析器
      parser: this.getParser(type)
      // 获取模版生成器
      generator: this.getGenerator(type)
    });
  }
}
  • 创建 NormalModule 对象

    根据文件类型 type 定义模块的解析器以及模版生成器,以及需要使用哪些 loaders 处理。

  • 构建 NormalModule 对象

    模块创建完后,此时只是获取了模块的基本信息,如相对路径,文件类型,需要用哪些 loader 处理这个文件等。接下来需要调用 build 来构建 NormalModule。

    • 加载 loaders

      使用不同 loader 来转换资源文件,js 文件内容可以直接传给下一步,非 js 文件经过 loader 后一般会输出一段 js 字符串内容。本例中的 index.js 没有使用 loaders,所以经过 runLoaders 处理后得到的内容就是原文件内容:

      import add from './b.js'
      add(1, 2)
      import('./c.js').then(del => del(1, 2))
      
    • parse:https://github.com/acornjs/acorn / astexplorer.net/

      runLoaders 处理后的内容转换成 AST,并解析出文件所有的依赖,index.js 的依赖是:

      NormalModule['./src/make/index.js']: {
        "dependencies": [
          "HarmonyCompatibilityDependency", // 对应模板 `HarmonyExportDependencyTemplate` 会在 index.js 的最前面添加如:`__webpack_require__.r(__webpack_exports__);` 的代码,用于定义 exports:__esModule
          "HarmonyInitDependency", // 对应模板 `HarmonyInitDependencyTemplate`, 下文单独说明其作用
          "ConstDependency", // 对应模板 `ConstDependencyTemplate`,会将 index.js 中的同步 import 语句删掉
          "HarmonyImportSideEffectDependency", // 对应模板 `HarmonyImportSideEffectDependencyTemplate`,执行 apply 调用父类 HarmonyImportDependencyTemplate 的 apply,即为空。
          "HarmonyImportSpecifierDependency" // 对应模板 `HarmonyImportSpecifierDependencyTemplate`,会在 index.js 中将引入的变量替换为 webpack 对应的包装变量
        ],
        "blocks": ["ImportDependenciesBlock"] // 对应模板 `ImportDependencyTemplate`, 会将 index.js 中的 `import('./c.js')`替换为 `Promise.resolve(/*! import() */).then(__webpack_require__.bind(null, /*! ./c.js */ "./src/make/c.js"))`
      }
      
    • 处理依赖

      • 筛选:调用 processModuleDependencies 处理上一步产生的的依赖,它会筛选出因为 require or import 引入的依赖,比如上面的 HarmonyImportSideEffectDependency, HarmonyImportSpecifierDependency, ImportDependenciesBlock ,筛选出来的依赖如下。

        sortedDependencies: [
            {
            factory: NormalModuleFactory,
            dependencies: [HarmonyImportSideEffectDependency, HarmonyImportSpecifierDependency]
          }, {
            factory: NormalModuleFactory,
            dependencies: [ImportDependency]
          }
        ]
        
      • 处理筛选出来的依赖:调用 addModuleDependencies 为依赖文件生成模块对象。

    • 最终的 modules

      Compilation: {
        modules: [
          NM('./src/make/index.js'): {
            blocks: [ImportDependenciesBlock('./c.js')],
            dependencies: [
              HarmonyCompatibilityDependency,
              HarmonyInitDependency,
              ConstDependency,
              HarmonyImportSideEffectDependency: { module: NM('./b.js') }
              HarmonyImportSpecifierDependency: { module: NM('./b.js') }
            ]
          },
          NM('./b.js'): {
            blocks: [],
            dependencies: [
              HarmonyCompatibilityDependency,
              HarmonyInitDependency,
              HarmonyExportSpecifierDependency,
              HarmonyExportHeaderDependency
            ]
          },
          NM('./c.js'): {
            blocks: [],
            dependencies: [
              HarmonyCompatibilityDependency,
              HarmonyInitDependency,
              HarmonyExportSpecifierDependency,
              HarmonyExportHeaderDependency
            ]
          },
        ]
      }
      
总结
  1. 创建模块对象,将模块对象添加到 Compilation.modules 中。
  2. 构建模块对象:将模块经 loaders 处理之后的内容转成 AST,遍历 AST 找到所有的依赖,筛选出因 require or import 引入的依赖,继续第1步,直至所有文件处理完毕。

参考

Webpack源码分析 – 模块Module

深入源码理解webpack是如何保证plugins的执行顺序的

zhuanlan.zhihu.com/p/102606552

dependencyTemplates 依赖模板

声明:本文内容由互联网用户自发贡献自行上传,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任。如果您发现有涉嫌版权的内容,欢迎进行举报,并提供相关证据,工作人员会在5个工作日内联系你,一经查实,本站将立刻删除涉嫌侵权内容。