Webpack has separate dependency module type files to handle template processing for different dependency modules. For example, if your source code uses ES Module, it will be handled by the HarmonyModulesPlugin’s dependencies, or if your source code uses CommonJS Module, You end up with dependencies that are used in CommonJsPlugin. In addition, WebPack also deals with other types of module-dependent syntax:

  • AMD -> AMDPlugin
  • System -> SystemPlugin
  • Require.ensure -> RequireEnsurePlugin
  • Import (subcontract asynchronous loading module) -> ImportPlugin
  • .
// WebpackOptionsApply.js

const LoaderPlugin = require("./dependencies/LoaderPlugin");
const CommonJsPlugin = require("./dependencies/CommonJsPlugin");
const HarmonyModulesPlugin = require("./dependencies/HarmonyModulesPlugin");
const SystemPlugin = require("./dependencies/SystemPlugin");
const ImportPlugin = require("./dependencies/ImportPlugin");
const AMDPlugin = require("./dependencies/AMDPlugin");
const RequireContextPlugin = require("./dependencies/RequireContextPlugin");
const RequireEnsurePlugin = require("./dependencies/RequireEnsurePlugin");
const RequireIncludePlugin = require("./dependencies/RequireIncludePlugin");

class WebpackOptionsApply extends OptionsApply {
  constructor() {
    super()
  }

  process(options, compiler) {
    ...
    new HarmonyModulesPlugin(options.module).apply(compiler);
		new AMDPlugin(options.module, options.amd || {}).apply(compiler);
		new CommonJsPlugin(options.module).apply(compiler);
		new LoaderPlugin().apply(compiler);

    new RequireIncludePlugin().apply(compiler);
		new RequireEnsurePlugin().apply(compiler);
		new RequireContextPlugin(
			options.resolve.modules,
			options.resolve.extensions,
			options.resolve.mainFiles
		).apply(compiler);
		new ImportPlugin(options.module).apply(compiler);
		newSystemPlugin(options.module).apply(compiler); . }}Copy the code

The handling of the module-dependent syntax is very important for WebPack to generate the final file content. These processing plug-ins for different dependency loading syntax are loaded and initialized when WebPack initializes the compiler. Here we take a look at how modules follow the dependency templates used by ES Module, which is what the HarmonyModulesPlugin does internally.

// Part 1: Introduces dependency types for the different syntax used in ES Module
const HarmonyCompatibilityDependency = require("./HarmonyCompatibilityDependency");
const HarmonyInitDependency = require("./HarmonyInitDependency");
const HarmonyImportSpecifierDependency = require("./HarmonyImportSpecifierDependency");
const HarmonyImportSideEffectDependency = require("./HarmonyImportSideEffectDependency");
const HarmonyExportHeaderDependency = require("./HarmonyExportHeaderDependency");
const HarmonyExportExpressionDependency = require("./HarmonyExportExpressionDependency");
const HarmonyExportSpecifierDependency = require("./HarmonyExportSpecifierDependency");
const HarmonyExportImportedSpecifierDependency = require("./HarmonyExportImportedSpecifierDependency");
const HarmonyAcceptDependency = require("./HarmonyAcceptDependency");
const HarmonyAcceptImportDependency = require("./HarmonyAcceptImportDependency");

const NullFactory = require(".. /NullFactory");

// Part 2: Introduces a different syntax for the ES Module, which requires hooks to be attached during compilation
const HarmonyDetectionParserPlugin = require("./HarmonyDetectionParserPlugin");
const HarmonyImportDependencyParserPlugin = require("./HarmonyImportDependencyParserPlugin");
const HarmonyExportDependencyParserPlugin = require("./HarmonyExportDependencyParserPlugin");
const HarmonyTopLevelThisParserPlugin = require("./HarmonyTopLevelThisParserPlugin");

class HarmonyModulesPlugin {
	constructor(options) {
		this.options = options;
	}

	apply(compiler) {
		compiler.hooks.compilation.tap(
			"HarmonyModulesPlugin",
			(compilation, { normalModuleFactory }) => {
				compilation.dependencyFactories.set(
					HarmonyCompatibilityDependency,
					new NullFactory()
				);
				// Set the template required for dependency rendering
				compilation.dependencyTemplates.set(
					HarmonyCompatibilityDependency,
					new HarmonyCompatibilityDependency.Template()
				);

				compilation.dependencyFactories.set(
					HarmonyInitDependency,
					new NullFactory()
				);
				compilation.dependencyTemplates.set(
					HarmonyInitDependency,
					new HarmonyInitDependency.Template()
				);

				compilation.dependencyFactories.set(
					HarmonyImportSideEffectDependency,
					normalModuleFactory
				);
				compilation.dependencyTemplates.set(
					HarmonyImportSideEffectDependency,
					new HarmonyImportSideEffectDependency.Template()
				);

				compilation.dependencyFactories.set(
					HarmonyImportSpecifierDependency,
					normalModuleFactory
				);
				compilation.dependencyTemplates.set(
					HarmonyImportSpecifierDependency,
					new HarmonyImportSpecifierDependency.Template()
				);

				compilation.dependencyFactories.set(
					HarmonyExportHeaderDependency,
					new NullFactory()
				);
				compilation.dependencyTemplates.set(
					HarmonyExportHeaderDependency,
					new HarmonyExportHeaderDependency.Template()
				);

				compilation.dependencyFactories.set(
					HarmonyExportExpressionDependency,
					new NullFactory()
				);
				compilation.dependencyTemplates.set(
					HarmonyExportExpressionDependency,
					new HarmonyExportExpressionDependency.Template()
				);

				compilation.dependencyFactories.set(
					HarmonyExportSpecifierDependency,
					new NullFactory()
				);
				compilation.dependencyTemplates.set(
					HarmonyExportSpecifierDependency,
					new HarmonyExportSpecifierDependency.Template()
				);

				compilation.dependencyFactories.set(
					HarmonyExportImportedSpecifierDependency,
					normalModuleFactory
				);
				compilation.dependencyTemplates.set(
					HarmonyExportImportedSpecifierDependency,
					new HarmonyExportImportedSpecifierDependency.Template()
				);

				compilation.dependencyFactories.set(
					HarmonyAcceptDependency,
					new NullFactory()
				);
				compilation.dependencyTemplates.set(
					HarmonyAcceptDependency,
					new HarmonyAcceptDependency.Template()
				);

				compilation.dependencyFactories.set(
					HarmonyAcceptImportDependency,
					normalModuleFactory
				);
				compilation.dependencyTemplates.set(
					HarmonyAcceptImportDependency,
					new HarmonyAcceptImportDependency.Template()
				);

				const handler = (parser, parserOptions) = > {
					if(parserOptions.harmony ! = =undefined && !parserOptions.harmony)
						return;

					new HarmonyDetectionParserPlugin().apply(parser);
					new HarmonyImportDependencyParserPlugin(this.options).apply(parser);
					new HarmonyExportDependencyParserPlugin(this.options).apply(parser);
					new HarmonyTopLevelThisParserPlugin().apply(parser);
				};

				normalModuleFactory.hooks.parser
					.for("javascript/auto")
					.tap("HarmonyModulesPlugin", handler);
				normalModuleFactory.hooks.parser
					.for("javascript/esm")
					.tap("HarmonyModulesPlugin", handler); }); }}module.exports = HarmonyModulesPlugin;
Copy the code

The files introduced by HarmonyModulesPlugin are divided into two main parts:

  • The dependency types of the different syntax used in ES Module
  • The ES Module uses a different dependency syntax, and hooks that need to be mounted during parser compilation (which are registered with the relevant plugin) for easy dependency collection

After the webpack create new compilation object, it performs compiler.hooks.com pilation registration method inside the hook. The main tasks are as follows:

1. Set up different depend on the type of moduleFactory, such as setting the HarmonyImportSpecifierDependency depend on the type of moduleFactory normalModuleFactory;

2. Set dependencyTemplate for different dependency types. Such as setting the HarmonyImportSpecifierDependency depend on the type of Template for new HarmonyImportSpecifierDependency. The Template instance ();

3. Registered normalModuleFactory. Hooks. The parser hook function. This hook function is executed each time a new normalModule is created, triggering the execution of the handler function. The handler function initializes the various plugins internally, registering the related hooks.

We’ll start by looking at some plugins registered with the Parser compiler that are initialized by the handler function.

HarmonyDetectionParserPlugin

// HarmonyDetectionParserPlugin.js
const HarmonyCompatibilityDependency = require("./HarmonyCompatibilityDependency");
const HarmonyInitDependency = require("./HarmonyInitDependency");

module.exports = class HarmonyDetectionParserPlugin {
	apply(parser) {
		parser.hooks.program.tap("HarmonyDetectionParserPlugin", ast => {
			const isStrictHarmony = parser.state.module.type === "javascript/esm";
			const isHarmony =
				isStrictHarmony ||
				ast.body.some(statement= > {
					return /^(Import|Export).*Declaration$/.test(statement.type);
				});
			if (isHarmony) {
				// Get the module currently being compiled
				const module = parser.state.module;
				const compatDep = new HarmonyCompatibilityDependency(module);
				compatDep.loc = {
					start: {
						line: - 1.column: 0
					},
					end: {
						line: - 1.column: 0
					},
					index: - 3
				};
				Add a compatDep dependency to this module
				module.addDependency(compatDep);
				const initDep = new HarmonyInitDependency(module);
				initDep.loc = {
					start: {
						line: - 1.column: 0
					},
					end: {
						line: - 1.column: 0
					},
					index: 2 -
				};
				// Add an initDep dependency to this module
				module.addDependency(initDep);
				parser.state.harmonyParserScope = parser.state.harmonyParserScope || {};
				parser.scope.isStrict = true;
				// Initializes the meta information generated by the final compilation of this module,
				module.buildMeta.exportsType = "namespace";
				module.buildInfo.strict = true;
				module.buildInfo.exportsArgument = "__webpack_exports__";
				if (isStrictHarmony) {
					module.buildMeta.strictHarmonyModule = true;
					module.buildInfo.moduleArgument = "__webpack_module__"; }}}); . }};Copy the code

The hooks registered on this plugin are triggered when each module starts compiling. Through the AST node type to judge whether the module is ES module, if so, will first instantiate an instance of HarmonyCompatibilityDependency rely on, and record the position, depend on the need to be replaced Then add this instance to the Module dependency, instantiate an instance of the HarmonyInitDependency dependency, note where the dependency needs to be replaced, and add the instance to the Module dependency. It then sets some build information for the module currently being handled by Parser when it is finally rendered, for example, the exportsArgument might use __webpack_exports__, which means the module output mount variable uses __webpack_exports__.

The Template of HarmonyCompatibilityDependency depends on mainly are:

HarmonyCompatibilityDependency.Template = class HarmonyExportDependencyTemplate {
	apply(dep, source, runtime) {
		const usedExports = dep.originModule.usedExports;
		if(usedExports ! = =false&&!Array.isArray(usedExports)) {
			// Define the module export type
			const content = runtime.defineEsModuleFlagStatement({
				exportsArgument: dep.originModule.exportsArgument
			});
			source.insert(- 10, content); }}}Copy the code

Call RuntimeTemplate instance provide defineEsModuleFlagStatement method in the current module in the end, inserting the code in the generated code:

__webpack_require__.r(__webpack_exports__) // Define an __esModule attribute on __webpack_exports__ to indicate that the current module is an ES module
Copy the code

The main things that HarmonyInitDependency does in its Template are:

HarmonyInitDependency.Template = class HarmonyInitDependencyTemplate {
	apply(dep, source, runtime, dependencyTemplates) {
		const module = dep.originModule;
		const list = [];
    // Walk through all dependencies in the module to which this dependency belongs
		for (const dependency of module.dependencies) {
      // Get the template used by the different dependencies
			const template = dependencyTemplates.get(dependency.constructor);
      // Parts of the template do not perform the related template-dependent replacement immediately after generator calls generate
      // Instead, the related operations are placed in the harmonyInit function, which is added to an array
			if (
				template &&
				typeof template.harmonyInit === "function" &&
				typeof template.getHarmonyInitOrder === "function"
			) {
				const order = template.getHarmonyInitOrder(dependency);
				if (!isNaN(order)) {
					list.push({
						order,
						listOrder: list.length, dependency, template }); }}}// Sort the template-dependent arrays
		list.sort((a, b) = > {
			const x = a.order - b.order;
			if (x) return x;
			return a.listOrder - b.listOrder;
		});

    // Execute the harmonyInit method on the template dependencies in turn, at which point the related template replacement begins
		for (const item oflist) { item.template.harmonyInit( item.dependency, source, runtime, dependencyTemplates ); }}}Copy the code

HarmonyImportDependencyParserPlugin

Next we look at HarmonyModulesPlugin plug-in initialization second plug-in HarmonyImportDependencyParserPlugin inside, this plugin is the main work and ES Module using the import of grammar related:

module.exports = class HarmonyImportDependencyParserPlugin {
  constructor() {... } apply(parser) { ... parser.hooks.import.tap('HarmonyImportDependencyParserPlugin', (statement, source) => {
      ...
      const sideEffectDep = newHarmonyImportSideEffectDependency({ ... }) parser.state.module.addDependency(sideEffectDep); . }) parser.hooks.importSpecifier.tap('HarmonyImportDependencyParserPlugin', (statement, source, id, name) => {
      ...
      // Set the mapping of imported module names
      parser.state.harmonySpecifier.set(name, {
        source,
        id,
        sourceOrder: parser.state.lastHarmonyImportOrder }); . }) parser.hooks.expression .for('imported var')
      .tap('HarmonyImportDependencyParserPlugin', expr => {
        ...
				const dep = newHarmonyImportSpecifierDependency({ ... }); parser.state.module.addDependency(dep); . }) parser.hooks.call .for('imported var')
      .tap('HarmonyImportDependencyParserPlugin', expr => {
        ...
        const dep = newHarmonyImportSpecifierDependency({ ... }) parser.state.module.addDependency(dep); . }}})Copy the code

This plugin registers hooks triggered by tokens while compiling the module through the Parser. ImportSpecifier, for example, is used mainly for variable names that you declare when loading other modules with import syntax, which is recorded in a map structure. When you use this variable name in your source code, for example, as a function (which triggers the hooks. Call hook) or as an expression (which triggers the hooks. Express hook), Then they will create a new HarmonyImportSpecifierDependency rely on instance, and enter into the current module is compiled.

The HarmonyImportSpecifierDependency templates rely on the main work is:

HarmonyImportSpecifierDependency.Template = class HarmonyImportSpecifierDependencyTemplate extends HarmonyImportDependency.Template {
	apply(dep, source, runtime) {
		super.apply(dep, source, runtime);
		const content = this.getContent(dep, runtime);
		source.replace(dep.range[0], dep.range[1] - 1, content);
	}

	getContent(dep, runtime) {
		const exportExpr = runtime.exportFromImport({
			module: dep._module,
			request: dep.request,
			exportName: dep._id,
			originModule: dep.originModule,
			asiSafe: dep.shorthand,
			isCall: dep.call,
			callContext: !dep.directImport,
			importVar: dep.getImportVar()
		});
		return dep.shorthand ? `${dep.name}: ${exportExpr}`: exportExpr; }};Copy the code

Introduce the source of other modules depend on the variable name for the replacement of the string, specific can consult RuntimeTemplate. ExportFromImport method.

Let’s look at an example:

// During parse compilation, hooks. ImportSpecifier are triggered to record variable names via map
import { add } from './add.js'

/ / trigger hooks. Call hooks, give the module to join HarmonyImportSpecifierDependency dependency
add(1.2-// The final generated code is:
/* harmony import */ var _add__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(1);

Object(_b__WEBPACK_IMPORTED_MODULE_0__["add"]) (1.2);
Copy the code

HarmonyExportDependencyParserPlugin

This plugin does some work related to the use of export syntax in ES Module:

module.exports = class HarmonyExportDependencyParserPlugin {
	constructor(moduleOptions) {
		this.strictExportPresence = moduleOptions.strictExportPresence;
	}

	apply(parser) {
		parser.hooks.export.tap(
			"HarmonyExportDependencyParserPlugin",
			statement => {
        ...
				const dep = newHarmonyExportHeaderDependency(...) ; . parser.state.current.addDependency(dep);return true; }); parser.hooks.exportImport.tap("HarmonyExportDependencyParserPlugin",
			(statement, source) => {
        ...
				const sideEffectDep = newHarmonyImportSideEffectDependency(...) ; . parser.state.current.addDependency(sideEffectDep);return true; }); parser.hooks.exportExpression.tap("HarmonyExportDependencyParserPlugin",
			(statement, expr) => {
        ...
				const dep = newHarmonyExportExpressionDependency(...) ; . parser.state.current.addDependency(dep);return true; }); parser.hooks.exportDeclaration.tap("HarmonyExportDependencyParserPlugin",
			statement => {}
		);
		parser.hooks.exportSpecifier.tap(
			"HarmonyExportDependencyParserPlugin",
			(statement, id, name, idx) => {
        ...
				if (rename === "imported var") {
					const settings = parser.state.harmonySpecifier.get(id);
					dep = newHarmonyExportImportedSpecifierDependency(...) ; }else {
					dep = newHarmonyExportSpecifierDependency(...) ; } parser.state.current.addDependency(dep);return true; }); parser.hooks.exportImportSpecifier.tap("HarmonyExportDependencyParserPlugin",
			(statement, source, id, name, idx) => {
				...
				const dep = newHarmonyExportImportedSpecifierDependency(...) ; . parser.state.current.addDependency(dep);return true; }); }};Copy the code

When compiling the source code, parse triggers hooks that failed to pass depending on the ES Module export syntax you use, and then adds corresponding dependent modules to the currently compiled Module. Here are two examples:

/ / export an add identifier, can trigger hooks in the parse link exportSpecifier hooks, can add a HarmonyExportSpecifierDependency dependence in the current module
export function add() {} -- -- --// The final output in the output file is
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "add".function() { return add; });

function add() {}
Copy the code
. / / the export from the add js module to load the add identifier, can trigger hooks in the parse link exportImportSpecifier hooks, Can add a HarmonyExportImportedSpecifierDependency dependence in the current module
export { add } from './add'

---

// The final output in the output file is
/* harmony import */ var _add__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(1);
/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "add".function() { return _add__WEBPACK_IMPORTED_MODULE_0__["add"]; });
Copy the code

Specific replacement work can consult HarmonyExportSpecifierDependency. Template and HarmonyExportImportedSpecifierDependency. Depend on the Template function provided by the Template.