current position:Home>Webpack5 packaging process source code analysis (1)

Webpack5 packaging process source code analysis (1)

2022-08-06 19:02:33Mingli people

开篇

webpack 相信大家都不陌生,In recent years is widely used in packaging resources of front-end engineering.

通常,我们只要掌握了 webpack 一些常用配置,Enough to meet the project build most scenarios.

然,When do you want to go to stand in a higher perspective to look at and use webpack 时,Like to do optimization、自定义 plugin 和 loader,理解 webpack Packaging of the compilation process is particularly important.Master packaging process online at various stages of the work done by,More accurate can help us to achieve high standards of customized function.

本篇,我们将以 Webpack 5 作为材料,Debugging the source code to get familiar with webpack Packaging process line,A source on the process are familiar with,In the future to further the principle of that part of the configuration will be easier.

一、前置知识

「1. Webpack」
webpack 是 JavaScript 应用程序静态模块打包器.Its source design adopted plug-in architecture,由 Tapable The ability to provide registration and call the plugin.因此,webpack The application of a lot of plug-ins,To complete for each config The realization of the function of configuration items.

「2. Compiler」
compiler 理解为 编译器,仅在 webpack Initialization time to create an instance,是 webpack Packaging process on the backbone of the engine.

在 compiler Instance provides a number of hooks To user-oriented realization of custom plug-ins,在 run After start the packaging will be created compilation Instance processing module compilation.

「3. Compilation」
compilation 理解为 编译,是由 compiler The compiler to create a,A compiler to compile may create one or more,则 compilation 可能会被创建多次.(watch

compilation The entry module will start,Compile the module and its dependence on child module.All modules compiled will pass:加载(loaded)、封存(sealed)、优化(optimized)、分块(chunked)、哈希(hashed) 和 重新创建(restored).

「4. Dependence」
webpack For it to record module dependencies between.在模块中引用其它模块,Will reference relationship expressed as Dependency 子类并关联 module 对象,等到当前 module 内容都解析完毕之后,启动下次循环开始将 Dependency Object is transformed to a new Module 子类.

「5. Module」
webpack When handling each resource file,都会以 module 对象形式存在,包含了资源的路径、上下文、依赖、内容等信息.All the resources to build、转译、Merger is also module 为基本单位进行.

「6. Chunk」
编译完成准备输出时,webpack 会将 module 按特定的规则组织成一个一个的 chunk,这些 chunk To some extent with the final output file one-to-one correspondence.

「7. Assets」
asset Represents the final output to disk file content,它与 chunk 一一对应.

「8. Tapable」
Tapable The ability to provide registration and call the plugin,来串联 webapck The whole packaging process of plugins work.

It can trigger hooks at the particular time,With enough context information on,Go to the notification plug-in to register hook callback,去产生 side effect Compiling state and subsequent process.

流程概览

webpack Packaging process as a whole can be divided into four large,A subsequent source debugging analysis will also be in accordance with the following division.

  1. 初始化阶段
  2. 构建阶段(make)
  3. 生成阶段(seal)
  4. 写入阶段(emit)

Because the content and length is too long,Will be divided into two articles to introduce process,本篇主要介绍「初始化阶段」和 「构建阶段」;「生成阶段」和「写入阶段」请移步到 webpack5 Packaging process source code analysis(2).

二、调试环境

Let's initialize a debugging environment:

mkdir webpack-debugger && cd webpack-debugger && npm init -y && npm install webpack webpack-cli -D
复制代码

webpack-debugger 目录下创建 src/index.js 作为入口模块,Add a line of print code file:

// src/index.js
conspole.log('webpack-debugger');
复制代码

新建 webpack.config.js,We use the most simple packaging configuration:

// webpack.config.js
const path = require('path');

module.exports = () => ({
  mode: 'development',
  entry: './src/index.js',
  output: {
    path: path.resolve(__dirname, 'build'),
    filename: 'static/js/[name].[contenthash:8].js',
  }
})
复制代码

最后,创建 build.js 作为调用 webpack 打包的执行文件:

// build.js
const path = require('path');
const webpack = require('webpack');
const configFactory = require('./webpack.config.js');
const config = configFactory();

debugger;
const compiler = webpack(config);

debugger;
compiler.run();
复制代码

通常我们都会在 package.json scripts 字段中使用 webpack bin Command to webpack-cli 进行打包,而进入 webpack-cli Inside the package with the above the same.

上述代码可知:webpack The default export a function method,接收 config A configuration object as a parameter,返回 compiler 对象;通过 compiler.run() Open the packaging process.

如果你使用的 VSCode,新建一个 JavaScript Debug Terminal 并执行 node build.js 开启调试.

Next, we analysis the initialization phase webpack 做了哪些事情.

三、初始化阶段

Before we put in a real building entrance module of this phase is divided into初始化阶段,主要步骤如下:

  1. 初始化参数:The user into the configured with the default configuration of final configuration parameter;
  2. 创建编译器对象:According to the configuration parameter to create Compiler 实例对象;
  3. 初始化编译环境:Registered users to configure the plug-in, and plug-ins;
  4. 运行编译:执行 compiler.run 方法;
  5. 确定入口:根据配置 entry Looking for all entry documents,并转换为 dependence 对象,等待执行 compilition.addEntry 编译工作.

3.1、webpack()

webpack Depend on the package default derived a method,这个方法的定义在 webpack/lib/webpack.js 之中:

// webpack/lib/webpack.js
const webpack = (options, callback) => {
  const create = () => {
    const webpackOptions = options;
    const compiler = createCompiler(webpackOptions);
  }

  if (callback) {
    const { compiler } = create();
    compiler.run((err, stats) => {
      compiler.close(err2 => {
        callback(err || err2, stats);
      });
    });
    return compiler;
  } else {
    const { compiler } = create();
    return compiler;
  }
}
复制代码

This method allows two parameters,若传递了 callback,创建 compiler Instance automatically after the call run 方法启动打包,Otherwise to external manual calls to start packing.

3.2、createCompiler

参数合并、compiler The compiler instance creation、Registration of external and internal plug-ins are all here to do:

// webpack/lib/webpack.js
const createCompiler = rawOptions => {
  // 规范 webpack config 配置项(创建 config 配置项)
  const options = getNormalizedWebpackOptions(rawOptions);
  // 设置默认的 config.context
  applyWebpackOptionsBaseDefaults(options);
  // 创建 compiler 编译器实例
  const compiler = new Compiler(options.context, options);
  // 应用 Node 环境插件,如为 compiler 提供 fs 文件操作 API(fs Module secondary packaging).
  new NodeEnvironmentPlugin({
    infrastructureLogging: options.infrastructureLogging
  }).apply(compiler);
  // Registered users of the incoming plug-in configuration
  if (Array.isArray(options.plugins)) {
    for (const plugin of options.plugins) {
      if (typeof plugin === "function") {
        plugin.call(compiler, compiler);
      } else {
        plugin.apply(compiler);
      }
    }
  }
  // Application configuration items default value
  applyWebpackOptionsDefaults(options);
  compiler.hooks.environment.call();
  compiler.hooks.afterEnvironment.call();
  // 关键,注册 Webpack Packaging process of plug-ins
  new WebpackOptionsApply().process(options, compiler);
  compiler.hooks.initialize.call();
  return compiler;
};
复制代码

Here we focus on to mention two:

  1. new Compiler(options.context, options) Compiler 是一个 ES6 class 构造函数,Here we just learned that one run 方法,Its basic structure is as follows:
// webpack/lib/Compiler.js
class Compiler {
  constructor(context, options = {}) {
    this.hooks = Object.freeze({
      initialize: new SyncHook([]),
      run: new AsyncSeriesHook(["compiler"]),
      done: new AsyncSeriesHook(["stats"]),
      emit: new AsyncSeriesHook(["compilation"]),
      make: new AsyncParallelHook(["compilation"]),
      ... 很多很多
    });
    this.options = options;
		this.context = context;
  }
  run(callback) {}
}
复制代码
  1. new WebpackOptionsApply().process(options, compiler) 上面「前置知识」中我们了解到:webpack Is a plugin structured design architecture,Namely the realization of each function is done by a plug-in to the,Such as modules compiled entrance entry 是由 EntryPlugin 来管理和执行.

WebpackOptionsApply().process Registered plug-ins a lot,Here we only care about this will involve the plug-in configuration.

// webpack/lib/WebpackOptionsApply.js
class WebpackOptionsApply extends OptionsApply {
  constructor() {
    super();
  }
  process(options, compiler) {
    new JavascriptModulesPlugin().apply(compiler);
    // entry 插件
    new EntryOptionPlugin().apply(compiler);
    compiler.hooks.entryOption.call(options.context, options.entry);
    ...
  }
}
复制代码
  • EntryOptionPlugin 会为 config.entry Each of the configuration application EntryPlugin To register the compilation of entry,等后续 hooks.make Time to start the entry module compiler.
  • JavascriptModulesPlugin 提供了 parse AST 的核心实现,Subsequent collection module are introduced deps Rely on the module will use.

到这里,compiler 实例创建完成,And the related plug-in registration.

接下来会执行 compiler.run() Open the package.Due to haven't go to the real compiler,Put this part「初始化阶段」一并介绍.

3.3、compiler.run

// webpack/lib/Compiler.js
class Compiler {
  ...
  run(callback) {
    const finalCallback = (err, stats) => {} // All work is completed the final executive function
    const onCompiled = (err, compilation) => {} // Compiled after the completion of the execution of the function
    this.hooks.beforeRun.callAsync(this, err => {
      if (err) return finalCallback(err);
      this.hooks.run.callAsync(this, err => {
        if (err) return finalCallback(err);
        this.compile(onCompiled);
      });
    });
  }
}
复制代码

首先执行了 beforeRunrun 两个 hook 钩子,If there is a plug-in registered these two types of hooks,Register the callback function will be executed immediately.Here we go this.compile 之中.

// webpack/lib/Compiler.js
class Compiler {
  ...
  compile(callback) {
    const params = this.newCompilationParams();
    this.hooks.beforeCompile.callAsync(params, err => {
      this.hooks.compile.call(params);
      const compilation = this.newCompilation(params);
      this.hooks.make.callAsync(compilation, err => {
        compilation.finish(err => {
          compilation.seal(err => {
            return callback(null, compilation);
          });
        });
      });
    });
  }
}
复制代码

compile() Is the key to start the compilation,编译实例 compilation 的参数定义、Instance creation and compiled after the completion of the finishing touches are all here to realize.

  1. 首先是 this.newCompilationParams 创建 compilation Compile the module parameters needed:
// webpack/lib/Compiler.js
newCompilationParams() {
  const params = {
    normalModuleFactory: this.createNormalModuleFactory(),
    contextModuleFactory: this.createContextModuleFactory()
  };
  return params;
}
复制代码

这里,我们需要留意一下 createNormalModuleFactory,在 webpack 中,Each dependent modules can be seen as a Module 对象,Usually there will be a lot of modules to deal with,Create a module factory here Factory.

// webpack/lib/Compiler.js
createNormalModuleFactory() {
  const normalModuleFactory = new NormalModuleFactory({
    context: this.options.context,
    fs: this.inputFileSystem,
    resolverFactory: this.resolverFactory, // resolve 模块时使用
    options: this.options.module,
    associatedObjectForCache: this.root,
    layers: this.options.experiments.layers
  });
  this._lastNormalModuleFactory = normalModuleFactory;
  this.hooks.normalModuleFactory.call(normalModuleFactory);
  return normalModuleFactory;
}
复制代码
  1. 创建 compilation 实例对象:
// webpack/lib/Compiler.js
newCompilation(params) {
  this._cleanupLastCompilation(); // 清除上次 compilation
  const compilation = this._lastCompilation = new Compilation(this, params);
  this.hooks.thisCompilation.call(compilation, params);
  this.hooks.compilation.call(compilation, params);
  return compilation;
}
复制代码

CompilationCompiler 都是一个 class 构造函数,Instance also contains a lot of properties and methods.

// webpack/lib/Compilation.js
class Compilation {
  constructor(compiler, params) {
    this.hooks = Object.freeze({ ... });
    this.compiler = compiler;
    this.params = params;
    this.options = compiler.options;
    this.entries = new Map(); // 存储 entry module
    this.modules = new Set();
    this._modules = new Map(); // 存储所有 module
    ...
  }
}
复制代码
  1. 调用 hooks.make 这一步很关键,In the above create get compilation 之后,To get into compilation phase,Will compile from the entrance to the module to begin.

While the module of preparation is registered in EntryOptionPlugin 之中:

// webpack/lib/EntryOptionsPlugin.js
class EntryOptionPlugin {
  apply(compiler) {
    compiler.hooks.entryOption.tap("EntryOptionPlugin", (context, entry) => {
      EntryOptionPlugin.applyEntryOption(compiler, context, entry);
      return true;
    });
  }
  static applyEntryOption(compiler, context, entry) {
    const EntryPlugin = require("./EntryPlugin");
    for (const name of Object.keys(entry)) {
      const desc = entry[name];
      const options = EntryOptionPlugin.entryDescriptionToOptions(compiler, name, desc);
      for (const entry of desc.import) {
        new EntryPlugin(context, entry, options).apply(compiler); // 注册 entry 插件
      }
    }
  }
}
复制代码

Which are a key part for each entry 注册 EntryPlugin,在 EntryPlugin You will see and hook.make 相关的逻辑:

// webpack/lib/EntryPlugin.js
class EntryPlugin {
  apply(compiler) {
    // 1、记录 entry Parsing module is used when normalModuleFactory
    compiler.hooks.compilation.tap(
      "EntryPlugin",
      (compilation, { normalModuleFactory }) => {
        compilation.dependencyFactories.set(
          EntryDependency, // key
          normalModuleFactory // value
        );
      }
    );
    const { entry, options, context } = this;
    // 2、为 entry 创建 Dependency 对象
    const dep = EntryPlugin.createDependency(entry, options);
    // 3、监听 hook.make,执行 compilation.addEntry
    compiler.hooks.make.tapAsync("EntryPlugin", (compilation, callback) => {
      compilation.addEntry(context, dep, options, err => {
        callback(err);
      });
    });
  }
}
复制代码

现在我们清楚了:当执行 hooks.make.callAsync 时,其实就是执行 compilation.addEntry Start the entry module compiler construction phase.

这也是很关键的一步:Find the entrance to the building.

四、构建阶段

hooks.make Is the beginning of the trigger module compiler entrance,在 webpack 的构建阶段,流程如下:

  1. entry Entry module to create entryData 存储在 compilation.entries,For the follow-up for each entrance output chunk;
  2. 拿到处理 entry 模块的工厂方法 moduleFactory,开始 ModuleTree 的创建,Mr. Behind every file module into a Module;
  3. 执行 handleModuleCreation Start dealing with the entry module build,当然,Introduced by entry module relies on module,Build will begin from here;
  4. The build process will experience feactorize(创建 module)addModulebuildModule 三个阶段,build Phase involves loader Code conversion and rely on collection;
  5. 模块构建完成后,If any child depend on(module.dependencies),Back to the third son began to rely on to build.

下面,Let's take a look at the process line in the code.

4.1、构建 EntryModuleTree

compilation.addEntry 开始进入 entry 模块的编译,调用 _addEntryItem 创建 entryData 加入到 this.entries 集合中.

// webpack/lib/Compilation.js
class Compilation {
  constructor(compiler, params) {
    this.entries = new Map();
    ...
  }
  
  addEntry(context, entry, options, callback) {
    this._addEntryItem(context, entry, "dependencies", options, callback);
  }
  
  _addEntryItem(context, entry, target, options, callback) {
    const { name } = options;
    let entryData = name !== undefined ? this.entries.get(name) : this.globalEntry;
    if (entryData === undefined) {
      entryData = {
        dependencies: [],
        includeDependencies: [],
        options: {
          name: undefined,
          ...options
        }
      };
      entryData[target].push(entry);
      this.entries.set(name, entryData);
    }
    this.hooks.addEntry.call(entry, options);

    this.addModuleTree({ context, dependency: entry, contextInfo: undefined }, (err, module) => {
      this.hooks.succeedEntry.call(entry, options, module);
      return callback(null, module);
    });
  }
}
复制代码

接着,执行 addModuleTree 获取 moduleFactory The above storage normalModuleFactory

// webpack/lib/Compilation.js
addModuleTree({ context, dependency, contextInfo }, callback) {
  const Dep = dependency.constructor; // EntryDependency
  // dependencyFactories.get(EntryDependency) = normalModuleFactory
  const moduleFactory = this.dependencyFactories.get(Dep); // 用于后续执行 moduleFactory.create()
  this.handleModuleCreation({ 
    factory: moduleFactory, 
    dependencies: [dependency], 
    originModule: null, contextInfo, context 
  }, (err, result) => {
    callback(null, result);
  });
}
复制代码

然后,执行 handleModuleCreation

// webpack/lib/Compilation.js
handleModuleCreation( { factory, // moduleFactory dependencies, // [dep] ... }, callback ) {
  const moduleGraph = this.moduleGraph;
  this.factorizeModule(
    {
      currentProfile: false,
      factory,
      dependencies,
      factoryResult: true,
      originModule,
      contextInfo,
      context
    },
    (err, factoryResult) => {
      const newModule = factoryResult.module;
      this.addModule(newModule, (err, module) => {
        ...
      });
    }
  );
}
复制代码

factorizeModule There is the meaning of decomposition module,可以理解为:为 entry 创建一个 Module.Its logic function body is simple:

// webpack/lib/Compilation.js
Compilation.prototype.factorizeModule = function (options, callback) {
  this.factorizeQueue.add(options, callback);
}
复制代码

4.2、Module compilation of phase

See here will be confused.从代码来看,将 options 加入到 factorizeQueue In the process is over.

其实不然,这是一个 AsyncQueue 异步队列,You can understand for each module factorize Decomposition is a task to join in the queue,In a row to it would perform.

factorizeQueue Similar to the function of the queue and addModuleQueuebuildQueue,They are on the initialization compilation Instance is defined as follows:

// webpack/lib/Compilation.js
class Compilation {
  constructor(compiler, params) {
    this.processDependenciesQueue = new AsyncQueue({
      name: "processDependencies",
      parallelism: options.parallelism || 100,
      processor: this._processModuleDependencies.bind(this)
    });
    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)
    });
  }
  _processModuleDependencies(module, callback) { }
  _addModule(module, callback) { }
  _factorizeModule(params, callback) { }
  _buildModule(module, callback) { }
}
复制代码

A module compiler will pass factorize 创建模块addModule 添加模块buildQueue 构建模块processDependencies Recursive handle child dependent modules(如果有) 几个阶段.

Each phase and the real execution of the function in Queue.processor 处理器上.

4.3、factorize 创建模块

_factorizeModule The link is longer,先后经过:factory.create --> hooks.factorize --> hooks.resolve --> new NormalModule 得到 module 对象.

// webpack/lib/Compilation.js
_factorizeModule( { currentProfile, factory, dependencies, originModule, factoryResult, contextInfo, context }, callback ) {
  factory.create({ context, dependencies, ... }, (err, result) => {
    callback(null, factoryResult ? result : result.module);
  });
}
复制代码

factory.create Is to create the module module 的开始,这里的 factory 就是创建 compilation.params 时传入的 normalModuleFactory.

create() 中执行 hooks.factorize.callSync,而注册 hooks.factorize.tapAsync 发生在初始化 NormalModuleFactory 实例时.

此外,Return when the initialization register hooks.resolve.tapAsync,Its execution time better in hooks.factorize.tapAsync 之中.代码如下:

// webpack/lib/NormalModuleFactory.js
class NormalModuleFactory extends ModuleFactory {
  constructor() {
    this.hooks.factorize.tapAsync({}, (resolveData, callback) => {
      this.hooks.resolve.callAsync(resolveData, (err, result) => {
        ...
      }
    })
    this.hooks.resolve.tapAsync({}, (data, callback) => {
      ...
    })
  }
  create(data, callback) {
    const resolveData = {
      contextInfo,
      resolveOptions,
      context,
      request,
      dependencies,
      dependencyType,
      createData: {},
      cacheable: true
      ...
    };
    this.hooks.factorize.callAsync(resolveData, (err, module) => {
      const factoryResult = {
        module,
        fileDependencies,
        missingDependencies,
        contextDependencies,
        cacheable: resolveData.cacheable
      };
      callback(null, factoryResult);
    }
  }
}
复制代码

首先第一步是在 hooks.resolve.tapAsync 中:

  1. 调用 enhanced-resolve Third party libraries get resources based on context 的绝对路径;
  2. According to the resource suffix(文件类型)收集 webpack.config.js 中配置的 loader,得到最终的 loaders 集合,这里涉及到 loader Order and the processing of inline and configuration way;
  3. According to the above information is a creation module 时所需的数据 --> createData.
// webpack/lib/NormalModuleFactory.js
this.hooks.resolve.tapAsync({}, (data, callback) => {
  // 创建一个 normal Resolve 实例
  const normalResolver = this.getResolver("normal", resolveOptions);
  let resourceData, loaders;

  const continueCallback = () => {
    ... 一系列 loader 规则处理
    // 生成 create module 相关数据集合
    Object.assign(data.createData, {
      layer:
        layer === undefined ? contextInfo.issuerLayer || null : layer,
      request: stringifyLoadersAndResource(
        allLoaders,
        resourceData.resource
      ),
      userRequest,
      rawRequest: request,
      loaders: allLoaders,
      resource: resourceData.resource, // Resources to the full absolute path
      context:
        resourceData.context || getContext(resourceData.resource),
      matchResource: matchResourceData
        ? matchResourceData.resource
        : undefined,
      resourceResolveData: resourceData.data,
      settings,
      type,
      parser: this.getParser(type, settings.parser), // module 的 parse Rely on collecting the parser
      parserOptions: settings.parser,
      generator: this.getGenerator(type, settings.generator), // module 的代码生成器
      generatorOptions: settings.generator,
      resolveOptions
    });
    callback();
  }

  // 执行 enhanced-resolve Third party libraries parse module path,得到 resolvedResource
  this.resolveResource(
    contextInfo,
    context,
    unresolvedResource,
    normalResolver,
    resolveContext,
    (err, resolvedResource, resolvedResourceResolveData) => {
      if (resolvedResource !== false) {
        resourceData = {
          resource: resolvedResource,
          data: resolvedResourceResolveData,
          ...cacheParseResource(resolvedResource)
        };
      }
      continueCallback();
    }
  );
})
复制代码

有了 module createData,接下来就是创建 NormalModule 实例得到 module

this.hooks.factorize.tapAsync({}, (resolveData, callback) => {
  this.hooks.resolve.callAsync(resolveData, (err, result) => {
    const createData = resolveData.createData;
    this.hooks.createModule.callAsync(createData, resolveData, (err, createdModule) => {
      // 创建 module 实例
      if (!createdModule) createdModule = new NormalModule(createData);
      createdModule = this.hooks.module.call(createdModule, createData, resolveData);
      return callback(null, createdModule);
    })
  }
})
复制代码

一个 module 实例上,记录了 parse 以及 loader 相关信息,Contains the common properties and methods:

// webpack/lib/NormalModule.js
class NormalModule extends Module {
  constructor({ ...createData }) {
    this.request = request;
    this.parser = parser;
    this.generator = generator;
    this.resource = resource;
    this.loaders = loaders;
    this._source = null; // module 文件内容
  }
  createSource() {},
  _doBuild(options, compilation, resolver, fs, hooks, callback) {}
  build(options, compilation, resolver, fs, callback) {}
  codeGeneration() {}
  ...
}
复制代码

create module 完成后,将 result 回传给 callback 即回到了 this.factorizeModule 的回调中执行 addModule

// webpack/lib/Compilation.js
handleModuleCreation({ ... }, callback) {
  this.factorizeModule({ ... }, (err, factoryResult) => {
    const newModule = factoryResult.module;
    this.addModule(newModule, (err, module) => {
      ...
    });
  });
}
复制代码

4.4、addModule 存储模块

addModule AsyncQueue 的处理器是 _addModule,将 module 添加到 modules 集合中:

// webpack/lib/Compilation.js
_addModule(module, callback) {
  const identifier = module.identifier();
  const alreadyAddedModule = this._modules.get(identifier);
  if (alreadyAddedModule) {
    return callback(null, alreadyAddedModule);
  }

  this._modulesCache.get(identifier, null, (err, cacheModule) => {
    if (cacheModule) {
      cacheModule.updateCacheModule(module);
      module = cacheModule;
    }
    this._modules.set(identifier, module);
    this.modules.add(module);
    callback(null, module);
  });
}
复制代码

module 添加完成后,执行 callback 回到 addModule 的回调中:

// webpack/lib/Compilation.js
handleModuleCreation({ ... }, callback) {
  this.factorizeModule({ ... }, (err, factoryResult) => {
    const newModule = factoryResult.module;
    this.addModule(newModule, (err, module) => {
      for (let i = 0; i < dependencies.length; i++) {
        const dependency = dependencies[i]; // entry dep
        moduleGraph.setResolvedModule(
          connectOrigin ? originModule : null,
          dependency,
          module
        );
      }
      this._handleModuleBuildAndDependencies(
        originModule,
        module,
        recursive,
        callback
      );
    });
  });
}
复制代码

4.5、buildModule 构建模块

接下来执行 _handleModuleBuildAndDependencies 进入 build 阶段.

build Stage to do the following a few things:

  1. 创建 loader context 上下文;
  2. 调用 loader-runner 第三方库提供的 runLoaders() 执行 loader 进行代码转换,这里会传入 resourceloadersloaderContext,After a conversion source code result; 3、执行 createSource 创建 RawSource 实例到 module._source 上,通过 _source.source() To get the file converted the source code;
  3. 执行 parse(JavascriptParser) 对 source 进行 ast 解析,Collection module depends on the collection to module.dependencies 中.

Let's take a look at the code on the implementation of the.

// webpack/lib/Compilation.js
_handleModuleBuildAndDependencies(originModule, module, recursive, callback) {
  this.buildModule(module, err => {
    ...
  })
}
复制代码

首先判断是否需要 build,Primary packaging will need to,接着执行 module.buildNormalModule.build,并执行 _doBuild 进行打包.

// webpack/lib/Compilation.js
_buildModule(module, callback) {
  module.needBuild({ ... }, (err, needBuild) => {
    this.hooks.buildModule.call(module);
    this.builtModules.add(module);
    module.build(
      this.options,
      this,
      this.resolverFactory.get("normal", module.resolveOptions),
      this.inputFileSystem,
      err => { 
        ... 
      }
    )
  })
}

// webpack/lib/NormalModule.js
build(options, compilation, resolver, fs, callback) {
  return this._doBuild(options, compilation, resolver, fs, hooks, err => {
    ...
  })
}
复制代码

_doBuild 中创建 loader 执行上下文,通过 runLoaders 执行 loader 转换代码,最后得到 this._source 对象.

// webpack/lib/NormalModule.js
_doBuild(options, compilation, resolver, fs, hooks, callback) {
  // 创建 loader 上下文
  const loaderContext = this._createLoaderContext(resolver, options, compilation, fs, hooks);
  // 生成 _source 对象
  const processResult = (err, result) => {
    const source = result[0];
    const sourceMap = result.length >= 1 ? result[1] : null;
    this._source = this.createSource(
      options.context,
      this.binary ? asBuffer(source) : asString(source),
      sourceMap,
      compilation.compiler.root
    );
    return callback();
  }
  // 调用 loader 进行代码转换
  runLoaders({
    resource: this.resource,
    loaders: this.loaders, // 配置的 loader
    context: loaderContext,
  }, (err, result) => {
    processResult(err, result.result);
  })
}
复制代码

_doBuild 执行完毕后回到 build 作用域下,对经过 loader After the conversion source code parse ast 解析,Collect dependent modules,并生成 build module hash.

// webpack/lib/NormalModule.js
build(options, compilation, resolver, fs, callback) {
  return this._doBuild(options, compilation, resolver, fs, hooks, err => {
    const handleParseResult = result => {
      this._initBuildHash(compilation);
      return handleBuildDone();
    }

    const handleBuildDone = () => {
      // 创建快照
      compilation.fileSystemInfo.createSnapshot(..., (err, snapshot) => {
        return callback();
      })
    }

    const source = this._source.source();
    // 这里的 parse 是由 JavascriptParser.js 提供
    result = this.parser.parse(this._ast || source, {
      source,
      current: this,
      module: this,
      compilation: compilation,
      options: options
    });
    handleParseResult(result);
  })
}
复制代码

最后回到 build 时传递的 callback,将 module 存储在 _modulesCache 中:

// webpack/lib/Compilation.js
module.build(
  this.options,
  this,
  this.resolverFactory.get("normal", module.resolveOptions),
  this.inputFileSystem,
  err => { 
    this._modulesCache.store(module.identifier(), null, module, err => {
      this.hooks.succeedModule.call(module);
      return callback();
    });
  }
)
复制代码

至此,module Phase is complete,回到 this.buildModule 中执行 processModuleDependencies 处理依赖模块.

// webpack/lib/Compilation.js
this.buildModule(module, err => {
  this.processModuleDependencies(module, err => {
    callback(null, module);
  });
})
复制代码

4.6、processModuleDependencies

如果模块存在 dependencies 依赖,Pair will module call handleModuleCreation() For the build steps,否则执行 callback Module to compile the end.

// webpack/lib/Compilation.js
_processModuleDependencies(module, callback) {
  // Didn't have to deal with dependent on
  if (sortedDependencies.length === 0 && inProgressTransitive === 1) {
    return callback();
  }
  // Handle child depend on
  for (const item of sortedDependencies) {
    this.handleModuleCreation(item, err => {
      ...
    });
  }
}
复制代码

现在,All depend on the processing is complete,依次完成 this.handleModuleCreation ---> this.addModuleTree ---> compilation.addEntry ---> compiler.hooks.make.

4.7、compilation.finish

进入了 compilation.finish 意味着模块的 make Packaging production phase to complete.在这里,调用 hooks.finishModules The process of building a and collecting module of errorswarnings.

// webpack/lib/Compilation.js
class Compilation {
  constructor(compiler, params) {
    this.errors = [];
    this.warnings = [];
  }
  finish(callback) {
    this.factorizeQueue.clear();
    const { modules } = this;
    this.hooks.finishModules.callAsync(modules, err => {
      for (const module of modules) {
        // 收集 error
        const errors = module.getErrors();
        if (errors !== undefined) {
          for (const error of errors) {
            this.errors.push(error);
          }
        }
        // 收集 warning
        const warnings = module.getWarnings();
        if (warnings !== undefined) {
          for (const warning of warnings) {
            this.warnings.push(warning);
          }
        }
      }
      this.moduleGraph.unfreeze();
      callback();
    });
  }
}
复制代码

最后

感谢阅读.

第二篇介绍了「生成阶段」和「写入阶段」,Can walk to here to see webpack5 Packaging process source code analysis(2).

copyright notice
author[Mingli people],Please bring the original link to reprint, thank you.
https://en.cdmana.com/2022/218/202208061858275438.html

Random recommended