关于webpack(v5.74.0)的模块联邦原理

news2025/1/13 14:08:44

在webpack中模块联邦的实现主要依赖于两个插件ContainerReferencePlugin和ContainerPlugin,ContainerPlugin是用来添加入口依赖并给当前依赖添加异步依赖,ContainerReferencePlugin用来添加解析用户的请求并分析是否是远程模块,然后加载远程模块。接下来我们看看源码是如何实现的。

基础配置

const path = require('path');
const { ModuleFederationPlugin } = require("webpack").container;

module.exports = {
	mode: 'development',
	plugins: [
		new ModuleFederationPlugin({
      // 指定导出的容器名称
      name: "app_exposes",
			library:{ type: "var", name: "app_exposes" },
      // 打包生成的文件名称
      filename: "remoteEntry.js",
      // 导出对应的模块
      exposes: {
        "./math": "./src/math.js",
      },
    })
	],
	entry: './src/index.js',
	output: {
		filename: 'bundle.js',
		path: path.resolve(__dirname, 'dist'),
		clean:true
	},
};

ContainerPlugin

const ContainerPlugin = require('./ContainerPlugin')
class TestPlugin {
  constructor(options) {
    this._options = options
  }
  apply(compiler) {
    const options = this._options
    // 添加当前允许的变量挂载类型window,var,self,this去接收__webpack_exports__
    compiler.options.output.enabledLibraryTypes.push(options.library.type)
    // 在初始化内部插件集合完成设置之后调用
    compiler.hooks.afterPlugins.tap("TestPlugin", () => {
      new ContainerPlugin({
        name: options.name,
        library:options.library,
        filename: options.filename,
        runtime: options.runtime,
        exposes: options.exposes
      }).apply(compiler);
    });
  }
}

module.exports = TestPlugin

主要执行了ContainerPlugin插件:

class ContainerPlugin {
	/**
	 * @param {ContainerPluginOptions} options options
	 */
	constructor(options) {
		this._options = {
			name: options.name,
			shareScope: options.shareScope || "default",
			// 全局变量接收__webpack_exports__
			library: options.library || {
				type: "var",
				name: options.name
			},
			runtime: options.runtime,
			filename: options.filename || undefined,
			exposes: parseOptions(
				options.exposes,
				item => ({
					import: Array.isArray(item) ? item : [item],
					name: undefined
				}),
				item => ({
					import: Array.isArray(item.import) ? item.import : [item.import],
					name: item.name || undefined
				})
			)
		};
	}

	/**
	 * Apply the plugin
	 * @param {Compiler} compiler the compiler instance
	 * @returns {void}
	 */
	apply(compiler) {
		const { name, exposes, shareScope, filename, library, runtime } =
			this._options;

		compiler.options.output.enabledLibraryTypes.push(library.type);

		compiler.hooks.make.tapAsync(PLUGIN_NAME, (compilation, callback) => {
			const dep = new ContainerEntryDependency(name, exposes, shareScope);
			dep.loc = { name };
			compilation.addEntry(
				compilation.options.context,
				dep,
				{
					name,
					filename,// 输出的文件名
					runtime,
					library
				},
				error => {
					if (error) return callback(error);
					callback();
				}
			);
		});

		compiler.hooks.thisCompilation.tap(
			PLUGIN_NAME,
			(compilation, { normalModuleFactory }) => {
				// 设置remoteEntry.js的依赖和创造remoteEntry的工厂
				compilation.dependencyFactories.set(
					ContainerEntryDependency,
					new ContainerEntryModuleFactory()// ContainerEntryModule 依赖ContainerExposedDependency
				);
				// 设置依赖的模块创造工厂
				compilation.dependencyFactories.set(
					ContainerExposedDependency,// math
					normalModuleFactory // 普通模块
				);
			}
		);
	}
}

可以看出通过addEntry方法添加了一个入口,并设置了依赖ContainerEntryDependency,然后在thisCompilation中设置了ContainerEntryDependency依赖的创造模块工厂ContainerEntryModuleFactory。也就意味着会先调用ContainerEntryModuleFactory函数的ContainerEntryModule去生成模块。

module.exports = class ContainerEntryModuleFactory extends ModuleFactory {
	/**
	 * @param {ModuleFactoryCreateData} data data object
	 * @param {function(Error=, ModuleFactoryResult=): void} callback callback
	 * @returns {void}
	 */
	create({ dependencies: [dependency] }, callback) {
		const dep = /** @type {ContainerEntryDependency} */ (dependency);
		callback(null, {
			module: new ContainerEntryModule(dep.name, dep.exposes, dep.shareScope)
		});
	}
};

我们重点看ContainerEntryModule方法的build方法和codeGeneration方法。

/** 给当前模块添加依赖,设置变量
 * @param {WebpackOptions} options webpack options
 * @param {Compilation} compilation the compilation
 * @param {ResolverWithOptions} resolver the resolver
 * @param {InputFileSystem} fs the file system
 * @param {function(WebpackError=): void} callback callback function
 * @returns {void}
 */
build(options, compilation, resolver, fs, callback) {
	this.buildMeta = {};
	// 设置三个变量
	this.buildInfo = {
		strict: true,
		topLevelDeclarations: new Set(["moduleMap", "get", "init"])
	};
	this.buildMeta.exportsType = "namespace";

	this.clearDependenciesAndBlocks();
	// remoteEntry.js依赖于main.js,此时AsyncDependenciesBlock表示公开模块来创建一个额外的容器入口。客户端会通过jsonp加载
	for (const [name, options] of this._exposes) {
		// 添加异步模块
		const block = new AsyncDependenciesBlock(
			{
				name: options.name
			},
			{ name },
			options.import[options.import.length - 1]
		);
		let idx = 0;
		for (const request of options.import) {
			// name=math.js
			const dep = new ContainerExposedDependency(name, request);
			dep.loc = {
				name,
				index: idx++
			};
			// build过程中设置异步模块的依赖(ContainerExposedDependency)
			block.addDependency(dep);
		}
		// 添加异步模块,会把math.js单独打包出来,同时会在remoteEntry.js文件中引入运行时的代码
		this.addBlock(block);
	}
	// 设置导出依赖
	this.addDependency(new StaticExportsDependency(["get", "init"], false));

	callback();
}

在执行build方法过程中会给remoteEntry模块添加一个异步依赖math.js。再来看看codeGeneration执行过程:

/**
 * @param {CodeGenerationContext} context context for code generation
 * @returns {CodeGenerationResult} result
 */
codeGeneration({ moduleGraph, chunkGraph, runtimeTemplate }) {
	const sources = new Map();
	const runtimeRequirements = new Set([
		// __webpack_require__.d 
		RuntimeGlobals.definePropertyGetters,
		// __webpack_require__.o
		RuntimeGlobals.hasOwnProperty,
		// __webpack_exports__
		RuntimeGlobals.exports
	]);
	const getters = [];
	// 给webpack/container/entry/app_exposes模块新增'"./math": () => {\n\treturn __webpack_require__.e("src_math_js").then(() => (() => ((__webpack_require__(/*! ./src/math.js */ "./src/math.js")))));\n}'
	for (const block of this.blocks) {
		const { dependencies } = block;
		// 取出异步模块
		const modules = dependencies.map(dependency => {
			const dep = /** @type {ContainerExposedDependency} */ (dependency);
			return {
				name: dep.exposedName,
				module: moduleGraph.getModule(dep),
				request: dep.userRequest
			};
		});

		let str;
		// 获取要请求的模块
		if (modules.some(m => !m.module)) {
			str = runtimeTemplate.throwMissingModuleErrorBlock({
				request: modules.map(m => m.request).join(", ")
			});
		} else {
			// runtimeTemplate.blockPromise=>__webpack_require__.e
			str = `return ${runtimeTemplate.blockPromise({
				block,
				message: "",
				chunkGraph,
				runtimeRequirements
				// runtimeTemplate.returningFunction  () => ()
			})}.then(${runtimeTemplate.returningFunction(
				runtimeTemplate.returningFunction(
					`(${modules
						.map(({ module, request }) =>
						// runtimeTemplate.moduleRaw =>__webpack_require__(/*! ./src/math.js */ "./src/math.js")
							runtimeTemplate.moduleRaw({
								module,
								chunkGraph,
								request,
								weak: false,
								runtimeRequirements
							})
						)
						.join(", ")})`
				)
			)});`;
		}

		getters.push(
			`${JSON.stringify(modules[0].name)}: ${runtimeTemplate.basicFunction(
				"",
				[str]
			)}`
		);
	}
	// remoteEntry.js文件的内容
	const source = Template.asString([
		`var moduleMap = {`,
		Template.indent(getters.join(",\n")),
		"};",
		// 定义remotEntry.js的get
		`var get = ${runtimeTemplate.basicFunction("module, getScope", [
			`${RuntimeGlobals.currentRemoteGetScope} = getScope;`,
			// reusing the getScope variable to avoid creating a new var (and module is also used later)
			"getScope = (",
			Template.indent([
				`${RuntimeGlobals.hasOwnProperty}(moduleMap, module)`,
				Template.indent([
					"? moduleMap[module]()",
					`: Promise.resolve().then(${runtimeTemplate.basicFunction(
						"",
						"throw new Error('Module \"' + module + '\" does not exist in container.');"
					)})`
				])
			]),
			");",
			`${RuntimeGlobals.currentRemoteGetScope} = undefined;`,
			"return getScope;"
		])};`,
		// 定义remotEntry.js的init
		`var init = ${runtimeTemplate.basicFunction("shareScope, initScope", [
			`if (!${RuntimeGlobals.shareScopeMap}) return;`,
			`var name = ${JSON.stringify(this._shareScope)}`,
			`var oldScope = ${RuntimeGlobals.shareScopeMap}[name];`,
			`if(oldScope && oldScope !== shareScope) throw new Error("Container initialization failed as it has already been initialized with a different share scope");`,
			`${RuntimeGlobals.shareScopeMap}[name] = shareScope;`,
			`return ${RuntimeGlobals.initializeSharing}(name, initScope);`
		])};`,
		"",
		"// This exports getters to disallow modifications",
		`${RuntimeGlobals.definePropertyGetters}(exports, {`,
		Template.indent([
			`get: ${runtimeTemplate.returningFunction("get")},`,
			`init: ${runtimeTemplate.returningFunction("init")}`
		]),
		"});"
	]);

	sources.set(
		"javascript",
		this.useSourceMap || this.useSimpleSourceMap
			? new OriginalSource(source, "webpack/container-entry")
			: new RawSource(source)
	);

	return {
		sources,
		// remotentry添加运行时函数
		runtimeRequirements
	};
}

这里主要是设置remoteEntry.js中的webpack/container/entry/app_exposes模块内容:

var moduleMap = {
  "./math": () => {
    return __webpack_require__
      .e("src_math_js")
      .then(
        () => () => __webpack_require__(/*! ./src/math.js */ "./src/math.js")
      );
  },
};
var get = (module, getScope) => {
  __webpack_require__.R = getScope;
  getScope = __webpack_require__.o(moduleMap, module)
    ? moduleMap[module]()
    : Promise.resolve().then(() => {
        throw new Error('Module "' + module + '" does not exist in container.');
      });
  __webpack_require__.R = undefined;
  return getScope;
};
var init = (shareScope, initScope) => {
  if (!__webpack_require__.S) return;
  var name = "default";
  var oldScope = __webpack_require__.S[name];
  if (oldScope && oldScope !== shareScope)
    throw new Error(
      "Container initialization failed as it has already been initialized with a different share scope"
    );
  __webpack_require__.S[name] = shareScope;
  return __webpack_require__.I(name, initScope);
};

// This exports getters to disallow modifications
__webpack_require__.d(exports, {
  get: () => get,
  init: () => init,
});

//# sourceURL=webpack://webpack/container_entry?

此时我们打包一下:
在这里插入图片描述
会生成三个文件,bundle.js文件就是我们配置的entry文件入口。在这里插入图片描述
remoteEntry.js文件是我们新增加的入口,webpack/container/entry/app_exposes依赖就是此处的dep.。在这里插入图片描述

在这里插入图片描述
math.js文件是remoteEntry.js文件中webpack/container/entry/app_exposes的依赖。该依赖的添加是在webpack/container/entry/app_exposes模块的build阶段:
在这里插入图片描述
此时ContainerPlugin插件做的事情我们就分析清楚了。接下来分析ContainerReferencePlugin插件。

ContainerReferencePlugin

基础配置

const path = require('path');

module.exports = {
  mode: 'development',
  plugins: [
    new ModuleFederationPlugin({
      // 引用的外部资源列表
      remotes: {
        app_exposes: 'app_exposes@http://localhost:8081/remoteEntry.js',
      }
    })
  ],
  entry: './src/index2.js',
  output: {
    filename: 'bundle.js',
    path: path.resolve(__dirname, 'dist'),
    clean: true
  },
};

// index2.js
import('app_exposes/math')

主要执行的是ModuleFederationPlugin插件:

/*
  MIT License http://www.opensource.org/licenses/mit-license.php
  Author Tobias Koppers @sokra and Zackary Jackson @ScriptedAlchemy
*/

"use strict";

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

class ModuleFederationPlugin {
  /**
   * @param {ModuleFederationPluginOptions} options options
   */
  constructor(options) {

    this._options = options;
  }

  /**
   * Apply the plugin
   * @param {Compiler} compiler the compiler instance
   * @returns {void}
   */
  apply(compiler) {
    const { _options: options } = this;
    const library = { type: "var", name: options.name };
    const remoteType = "script";
    compiler.options.output.enabledLibraryTypes.push(library.type);
    compiler.hooks.afterPlugins.tap("ModuleFederationPlugin", () => {
      new ContainerReferencePlugin({
        remoteType,
        remotes: options.remotes
      }).apply(compiler);
    });
  }
}

module.exports = ModuleFederationPlugin;

该插件主要执行ContainerReferencePlugin插件,我们继续看:

/*
  MIT License http://www.opensource.org/licenses/mit-license.php
  Author Tobias Koppers @sokra and Zackary Jackson @ScriptedAlchemy
*/

"use strict";

const ExternalsPlugin = require("webpack/lib/ExternalsPlugin");
const RuntimeGlobals = require("webpack/lib/RuntimeGlobals");
const FallbackDependency = require("webpack/lib/container/FallbackDependency");
const FallbackItemDependency = require("webpack/lib/container/FallbackItemDependency");
const FallbackModuleFactory = require("webpack/lib/container/FallbackModuleFactory");
const RemoteModule = require("webpack/lib/container/RemoteModule");
const RemoteRuntimeModule = require("webpack/lib/container/RemoteRuntimeModule");
const RemoteToExternalDependency = require("webpack/lib/container/RemoteToExternalDependency");
const { parseOptions } = require("webpack/lib/container/options");



const slashCode = "/".charCodeAt(0);

class ContainerReferencePlugin {
  /**
   * @param {ContainerReferencePluginOptions} options options
   */
  constructor(options) {
    this._remoteType = options.remoteType;
    this._remotes = parseOptions(
      options.remotes,
      item => ({
        external: Array.isArray(item) ? item : [item],
        shareScope: options.shareScope || "default"
      }),
      item => ({
        external: Array.isArray(item.external)
          ? item.external
          : [item.external],
        shareScope: item.shareScope || options.shareScope || "default"
      })
    );
  }

  /** 1. 定义了普通模块
   * Apply the plugin
   * @param {Compiler} compiler the compiler instance
   * @returns {void}
   */
  apply(compiler) {
    const { _remotes: remotes, _remoteType: remoteType } = this;

    /** @type {Record<string, string>} */
    const remoteExternals = {};
    for (const [key, config] of remotes) {
      let i = 0;
      for (const external of config.external) {
        if (external.startsWith("internal ")) continue;
        remoteExternals[
          `webpack/container/reference/${key}${i ? `/fallback-${i}` : ""}`
        ] = external;
        i++;
      }
    }
    // ExternalModuleFactoryPlugin=>ExternalModule最终返回webpack/container/reference/app_exposes的模块内容
    new ExternalsPlugin(remoteType, remoteExternals).apply(compiler);

    compiler.hooks.compilation.tap(
      "ContainerReferencePlugin",
      (compilation, { normalModuleFactory }) => {
        // 
        compilation.dependencyFactories.set(
          RemoteToExternalDependency,
          normalModuleFactory
        );
        // 定义了普通模块
        compilation.dependencyFactories.set(
          FallbackItemDependency,
          normalModuleFactory
        );
        // 当前模块依赖于FallbackDependency
        compilation.dependencyFactories.set(
          FallbackDependency,
          new FallbackModuleFactory() // FallbackModule依赖于FallbackItemDependency
        );
        // 4. 第一次解析(type=module)'./src/index2.js' 第二次'app_exposes/math'
        normalModuleFactory.hooks.factorize.tap(
          "ContainerReferencePlugin",
          data => {
            if (!data.request.includes("!")) {
              for (const [key, config] of remotes) {
                // 在解析依赖的时候如果以app_exposes开头并且有'\'
                if (
                  data.request.startsWith(`${key}`) &&
                  (data.request.length === key.length ||
                    data.request.charCodeAt(key.length) === slashCode)
                ) {
                  // app_exposes/math依赖于RemoteToExternalDependency 
                  return new RemoteModule(
                    data.request,
                    config.external.map((external, i) =>
                      external.startsWith("internal ")
                        ? external.slice(9)
                        : `webpack/container/reference/${key}${i ? `/fallback-${i}` : ""
                        }`
                    ),
                    `.${data.request.slice(key.length)}`,
                    config.shareScope
                  );
                }
              }
            }
          }
        );
        // 添加运行时代码
        compilation.hooks.runtimeRequirementInTree
          .for(RuntimeGlobals.ensureChunkHandlers)
          .tap("ContainerReferencePlugin", (chunk, set) => {
            set.add(RuntimeGlobals.module);
            set.add(RuntimeGlobals.moduleFactoriesAddOnly);
            set.add(RuntimeGlobals.hasOwnProperty);
            set.add(RuntimeGlobals.initializeSharing);
            set.add(RuntimeGlobals.shareScopeMap);
            compilation.addRuntimeModule(chunk, new RemoteRuntimeModule());
          });
      }
    );
  }
}

module.exports = ContainerReferencePlugin;

首先执行的是ExternalsPlugin插件,其次执行的compiler.hooks.compilation.tap回调方法。compiler.hooks.compilation.tap主要设置了FallbackDependency依赖的模块创造函数,其次监听了normalModuleFactory.hooks.factorize钩子,该钩子是在解析依赖路径的时候会触发。我们在来看看ExternalsPlugin插件干啥了。

/*
  MIT License http://www.opensource.org/licenses/mit-license.php
  Author Tobias Koppers @sokra
*/

"use strict";

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

/** @typedef {import("../declarations/WebpackOptions").Externals} Externals */
/** @typedef {import("./Compiler")} Compiler */

class ExternalsPlugin {
  /**
   * @param {string | undefined} type default external type
   * @param {Externals} externals externals config
   */
  constructor(type, externals) {
    this.type = type;
    this.externals = externals;
  }

  /** compile先于compilation执行
   * Apply the plugin
   * @param {Compiler} compiler the compiler instance
   * @returns {void}
   */
  apply(compiler) {// beforeCompile 之后立即调用,但在一个新的 compilation 创建之前
    compiler.hooks.compile.tap("ExternalsPlugin", ({ normalModuleFactory }) => {
      new ExternalModuleFactoryPlugin(this.type, this.externals).apply(
        normalModuleFactory
      );
    });
  }
}

module.exports = ExternalsPlugin;

主要执行ExternalModuleFactoryPlugin插件:

...

class ExternalModuleFactoryPlugin {
  /**
   * @param {string | undefined} type default external type
   * @param {Externals} externals externals config
   */
  constructor(type, externals) {
    this.type = type;
    this.externals = externals;
  }

  /**
   * @param {NormalModuleFactory} normalModuleFactory the normal module factory
   * @returns {void}
   */
  apply(normalModuleFactory) {
    const globalType = this.type;// 2.第一次解析(type=module)'./src/index2.js',3.第二次解析(type=module)'app_exposes/math',6.第三次解析(type=module)'webpack/container/reference/app_exposes' 第四次解析(type=script)'webpack/container/reference/app_exposes'
    normalModuleFactory.hooks.factorize.tapAsync(
      "ExternalModuleFactoryPlugin",
      (data, callback) => {
        const context = data.context;
        const contextInfo = data.contextInfo;
        const dependency = data.dependencies[0];
        const dependencyType = data.dependencyType;

        /** value 'app_exposes@http://localhost:8081/remoteEntry.js'
         * @param {string|string[]|boolean|Record<string, string|string[]>} value the external config
         * @param {string|undefined} type type of external
         * @param {function(Error=, ExternalModule=): void} callback callback
         * @returns {void}
         */
        const handleExternal = (value, type, callback) => {
          if (value === false) {
            // Not externals, fallback to original factory
            return callback();
          }
          /** @type {string | string[] | Record<string, string|string[]>} */
          let externalConfig;
          if (value === true) {
            externalConfig = dependency.request;
          } else {
            externalConfig = value;
          }
          // When no explicit type is specified, extract it from the externalConfig
          if (type === undefined) {
            if (
              typeof externalConfig === "string" &&
              UNSPECIFIED_EXTERNAL_TYPE_REGEXP.test(externalConfig)
            ) {
              const idx = externalConfig.indexOf(" ");
              type = externalConfig.slice(0, idx);
              externalConfig = externalConfig.slice(idx + 1);
            } else if (
              Array.isArray(externalConfig) &&
              externalConfig.length > 0 &&
              UNSPECIFIED_EXTERNAL_TYPE_REGEXP.test(externalConfig[0])
            ) {
              const firstItem = externalConfig[0];
              const idx = firstItem.indexOf(" ");
              type = firstItem.slice(0, idx);
              externalConfig = [
                firstItem.slice(idx + 1),
                ...externalConfig.slice(1)
              ];
            }
          }
          callback(
            null,
            new ExternalModule(// 8.最终执行
              externalConfig,
              type || globalType,
              dependency.request
            )
          );
        };

        /**
         * @param {Externals} externals externals config
         * @param {function((Error | null)=, ExternalModule=): void} callback callback
         * @returns {void}
         */
        const handleExternals = (externals, callback) => {
          if (typeof externals === "string") {
            if (externals === dependency.request) {
              return handleExternal(dependency.request, undefined, callback);
            }
          } else if (Array.isArray(externals)) {
            let i = 0;
            const next = () => {
              let asyncFlag;
              const handleExternalsAndCallback = (err, module) => {
                if (err) return callback(err);
                if (!module) {
                  if (asyncFlag) {
                    asyncFlag = false;
                    return;
                  }
                  return next();
                }
                callback(null, module);
              };

              do {
                asyncFlag = true;
                if (i >= externals.length) return callback();
                handleExternals(externals[i++], handleExternalsAndCallback);
              } while (!asyncFlag);
              asyncFlag = false;
            };

            next();
            return;
          } else if (externals instanceof RegExp) {
            if (externals.test(dependency.request)) {
              return handleExternal(dependency.request, undefined, callback);
            }
          } else if (typeof externals === "function") {
            const cb = (err, value, type) => {
              if (err) return callback(err);
              if (value !== undefined) {
                handleExternal(value, type, callback);
              } else {
                callback();
              }
            };
            if (externals.length === 3) {
              // TODO webpack 6 remove this
              callDeprecatedExternals(
                externals,
                context,
                dependency.request,
                cb
              );
            } else {
              const promise = externals(
                {
                  context,
                  request: dependency.request,
                  dependencyType,
                  contextInfo,
                  getResolve: options => (context, request, callback) => {
                    const resolveContext = {
                      fileDependencies: data.fileDependencies,
                      missingDependencies: data.missingDependencies,
                      contextDependencies: data.contextDependencies
                    };
                    let resolver = normalModuleFactory.getResolver(
                      "normal",
                      dependencyType
                        ? cachedSetProperty(
                          data.resolveOptions || EMPTY_RESOLVE_OPTIONS,
                          "dependencyType",
                          dependencyType
                        )
                        : data.resolveOptions
                    );
                    if (options) resolver = resolver.withOptions(options);
                    if (callback) {
                      resolver.resolve(
                        {},
                        context,
                        request,
                        resolveContext,
                        callback
                      );
                    } else {
                      return new Promise((resolve, reject) => {
                        resolver.resolve(
                          {},
                          context,
                          request,
                          resolveContext,
                          (err, result) => {
                            if (err) reject(err);
                            else resolve(result);
                          }
                        );
                      });
                    }
                  }
                },
                cb
              );
              if (promise && promise.then) promise.then(r => cb(null, r), cb);
            }
            return;// {webpack/container/reference/app_exposes: 'app_exposes@http://localhost:8081/remoteEntry.js'}
          } else if (typeof externals === "object") {
            const resolvedExternals = resolveLayer(
              externals,
              contextInfo.issuerLayer
            );
            if (
              Object.prototype.hasOwnProperty.call(
                resolvedExternals,
                dependency.request
              )
            ) {// 7.最终执行
              return handleExternal(
                resolvedExternals[dependency.request],
                undefined,
                callback
              );
            }
          }
          callback();
        };

        handleExternals(this.externals, callback);
      }
    );
  }
}
module.exports = ExternalModuleFactoryPlugin;

该插件主要执行normalModuleFactory.hooks.factorize监听解析路径,主要是用来处理webpack/container/reference/app_exposes依赖,最终执行ExternalModule模块。我们看看ExternalModule模块返回了什么内容。

const getSourceForScriptExternal = (urlAndGlobal, runtimeTemplate) => {
  if (typeof urlAndGlobal === "string") {
    urlAndGlobal = extractUrlAndGlobal(urlAndGlobal);
  }// 'http://localhost:8081/remoteEntry.js'
  const url = urlAndGlobal[0];
  const globalName = urlAndGlobal[1];
  return {
    init: "var __webpack_error__ = new Error();",
    expression: `new Promise(${runtimeTemplate.basicFunction(
      "resolve, reject",
      [
        `if(typeof ${globalName} !== "undefined") return resolve();`,
        `${RuntimeGlobals.loadScript}(${JSON.stringify(
          url
        )}, ${runtimeTemplate.basicFunction("event", [
          `if(typeof ${globalName} !== "undefined") return resolve();`,
          "var errorType = event && (event.type === 'load' ? 'missing' : event.type);",
          "var realSrc = event && event.target && event.target.src;",
          "__webpack_error__.message = 'Loading script failed.\\n(' + errorType + ': ' + realSrc + ')';",
          "__webpack_error__.name = 'ScriptExternalLoadError';",
          "__webpack_error__.type = errorType;",
          "__webpack_error__.request = realSrc;",
          "reject(__webpack_error__);"
        ])}, ${JSON.stringify(globalName)});`
      ]
    )}).then(${runtimeTemplate.returningFunction(
      `${globalName}${propertyAccess(urlAndGlobal, 2)}`
    )})`,
    runtimeRequirements: RUNTIME_REQUIREMENTS_FOR_SCRIPT
  };
};

打包后生成:

/***/ "webpack/container/reference/app_exposes":
/*!*******************************************************************!*\
  !*** external "app_exposes@http://localhost:8081/remoteEntry.js" ***!
  \*******************************************************************/
/***/ ((module, __unused_webpack_exports, __webpack_require__) => {

"use strict";
var __webpack_error__ = new Error();
module.exports = new Promise((resolve, reject) => {
	// 已有全局变量app_exposes说明已加载
	if(typeof app_exposes !== "undefined") return resolve();
	__webpack_require__.l("http://localhost:8081/remoteEntry.js", (event) => {
		if(typeof app_exposes !== "undefined") return resolve();
		var errorType = event && (event.type === 'load' ? 'missing' : event.type);
		var realSrc = event && event.target && event.target.src;
		__webpack_error__.message = 'Loading script failed.\n(' + errorType + ': ' + realSrc + ')';
		__webpack_error__.name = 'ScriptExternalLoadError';
		__webpack_error__.type = errorType;
		__webpack_error__.request = realSrc;
		reject(__webpack_error__);
	}, "app_exposes");
}).then(() => (app_exposes));

/***/ })

可以看出是请求远程的remoteEntry.js模块。我们再来看看解析app_exposes/math的时候是怎么处理的。当解析app_expose/math路径的时候会执行返回RemoteModule模块内容。
在这里插入图片描述
我们重点看RemoteModule模块生成内容:

build(options, compilation, resolver, fs, callback) {
    this.buildMeta = {};
    this.buildInfo = {
      strict: true
    };
    // 5. 添加'webpack/container/reference/app_exposes'依赖会触发normalModuleFactory.hooks.factorize
    this.clearDependenciesAndBlocks();
    if (this.externalRequests.length === 1) {
      this.addDependency(
        new RemoteToExternalDependency(this.externalRequests[0])
      );
    } else {
      this.addDependency(new FallbackDependency(this.externalRequests));
    }

    callback();
  }

该模块添加了webpack/container/reference/app_exposes依赖。再来看看codeGeneration返回:

codeGeneration({ runtimeTemplate, moduleGraph, chunkGraph }) {
    const module = moduleGraph.getModule(this.dependencies[0]);
    const id = module && chunkGraph.getModuleId(module);// 'webpack/container/reference/app_exposes'
    const sources = new Map();
    sources.set("remote", new RawSource(""));
    const data = new Map();//shareScope = 'default' init=initExternal("webpack/container/reference/app_exposes")
    data.set("share-init", [
      {
        shareScope: this.shareScope,// 作为ShareRuntimeModule的switch的default
        initStage: 20,
        init: id === undefined ? "" : `initExternal(${JSON.stringify(id)});`
      }
    ]);
    return { sources, data, runtimeRequirements: RUNTIME_REQUIREMENTS };
  }

设置了data的share-init。重点看init方法,initExternal(${JSON.stringify(id)})主要是用来初始化模块内容。会被应用到__webpack_require__.I的switch方法中。
在这里插入图片描述
至此该插件实现的过程完毕。

总结

模块联邦功能实现主要依赖两个插件:

  1. ContainerPlugin
    首先新增入口,并给该chunk添加依赖ContainerEntryDependency(remoteEntry.js),remoteEntry模块build过程中添加一个异步模块math.js。
  2. ContainerReferencePlugin
    通过normalModuleFactory.hooks.factorize监听依赖解析,当解析app_exposes/math时会返回执行RemoteModule模块的build方法添加依赖webpack/container/reference/app_exposes,在code生成时设置initExternal方法。在解析webpack/container/reference/app_exposes依赖的时候会触发ExternalsPlugin插件的normalModuleFactory.hooks.factorize回调方法。主要返回ExternalModule模块内容(获取需要的远端模块内容)。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/24493.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

使用 JPA、Hibernate 和 Spring Data JPA 进行审计

1. 概述 在ORM的上下文中&#xff0c;数据库审计意味着跟踪和记录与持久实体相关的事件&#xff0c;或者只是实体版本控制。受 SQL 触发器的启发&#xff0c;这些事件是对实体的插入、更新和删除操作。数据库审核的好处类似于源版本控制提供的好处。 在本教程中&#xff0c;我…

Shelby American 汽车 NFT 系列来袭!

我们在 The Sandbox 上推出 Shelby NFT 作品集&#xff0c;加入我们吧&#xff01;该系列包含 Carroll Shelby 制造的一些最稀有和最抢手的汽车&#xff0c;也是现实生活中最具收藏价值的汽车。这些汽车构成了最伟大的汽车历史&#xff0c;也是传奇人物 Carroll Shelby 的伟大代…

为什么开源在线表单工具能做好数据管理?

在数字化时代&#xff0c;数据的有效应用和管理可以说是企业的无形资产&#xff0c;做好数据管理既能提升办公效率&#xff0c;又能帮助企业从规律的数字化管理中获取高效的管理策略。那么&#xff0c;什么样的开源在线表单工具可以实现这一目的&#xff1f;对于企业而言&#…

Axure药企内部管理平台+企业内部管理系统平台

这是一款根据药企的需求设计的内部管理系统&#xff0c;此系统主要是针对市场部和销售部的管理&#xff0c;此作品选择了管理员和地区经理两个角色进行了设计&#xff0c; 设计软件&#xff1a;Axure8.1&#xff08;兼容9和10&#xff09; 作品类型&#xff1a;实战原型 其主要…

抓包神器之Charles(绕过代理屏蔽)以及证书校验绕过

简介 大多数进行渗透测试的时候都可以使用burp抓包,但有的app的部分功能会使用okhttp框架,这种框架会使App不使用默认的系统代理,解决方法就是通过proxy的方式走charles,下面是具体使用方法; Charles 是常用的网络封包截取工具, 通过将自己设置成系统的网络访问代{过}{…

11.21SSM-spring 第一天学习总结

1 Spring 是什么&#xff1f; 针对Bean 生命周期进行管理的轻量级容器 IOC : 浅谈IOC--说清楚IOC是什么_ivan820819的博客-CSDN博客_ioc 软件设计六大原则 : 设计模式六大原则 六大设计原则 1.开闭原则 定义&#xff1a;一个软件实体如类、模块和函数应该对扩展开放&a…

JavaScript/uni-app对接海康ISC openapi

JavaScript/uni-app对接海康ISC openapiJavaScript实现HMAC SHA256下载安装使用crypto-js使用签名生成工具参考JavaScript实现HMAC SHA256 Run the code online with this jsfiddle. Dependent upon an open source js library calledhttp://code.google.com/p/crypto-js/.<…

如何将驱动编译为kernel 模块

前言&#xff1a; 本文章目标平台是PC Linux,不包含其他平台。 执行下面的步骤之前&#xff0c;请先编译kernel通过。 linux KO编译 将驱动程序源码集成到Linux内核中&#xff1a; 将驱动源码文件放到drivers/net/wireless并命名 自己简单创建的几个没有任何关联的源文件&…

力扣(LeetCode)30. 串联所有单词的子串(C++)

滑动窗口哈希表 哈希表 tottottot 存 wordswordswords 所有单词的出现次数。 维护滑动窗口&#xff0c;窗口长度 mwm\times wmw &#xff0c; mmm 是单词数量 www是单词长度 &#xff0c; 窗口长度对应可行解的长度。哈希表 wdwdwd 维护滑动窗口内每个单词的出现次数。 维护…

jstack问题定位分析

目录 1、jstack是什么 2、jstack的使用 1、jstack是什么 jstack是java虚拟机自带的一种堆栈跟踪工具。jstack用来打印出给定的java进程ID或者core file或者远程调试服务的java堆栈信息。 主要是用于生成java虚拟机当前时刻的线程快照&#xff0c;线程快照是当前java虚拟机…

记录运行项目的一些报错

一、git pull 报错 There is no tracking information for the current branch. Please specify whic... There is no tracking information for the current branch.Please specify which branch you want to merge with.See git-pull(1) for detailsgit pull <remote>…

身份安全的零信任方法

一、什么是零信任&#xff1f; 零信任是一组不断发展的网络安全范例术语&#xff0c;它将组织的防御措施从静态的、基于网络的边界转移到关注用户、资产和资源。这是一种安全心态&#xff0c;在明确验证之前&#xff0c;每个传入连接都被视为潜在的恶意请求。这个概念是由世界…

【特征选择】基于二元多邻域人工蜂群 (BMNABC) 特征选择问题(Matlab代码实现)

&#x1f468;‍&#x1f393;个人主页&#xff1a;研学社的博客 &#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜…

1.EdgeX实战 Ubuntu18.04搭建运行环境

文章目录前言:1、搭建Ubuntu18.04服务器平台2、安装docker和docker-compose3、运行EdgeX4、使用浏览器访问EdgeX前言: 想着把之前arduino和树莓派搭建的物联网平台迁移到EdgeX上来&#xff0c;原因有二&#xff1a; 不想去造轮子&#xff0c;自己从零开始写一个物联网的框架&…

影响 SEO 的排名优化的因素

我们在做网站SEO运营的时候&#xff0c;经常会遇到没有排名的情况。即使我们每天保持原创内容的更新和发布&#xff0c;也没有用。这时候就要马上检查网站存在哪些问题&#xff0c;及时解决&#xff0c;让我们的日常努力尽快盈利。以下因素按米贸搜排序&#xff0c;影响SEO排名…

凡亿教育嘉立创宠粉福利,9.9元秒杀PCB多层板设计实战特训班

层林浸染&#xff0c;秋意渐浓 随着双11活动的落幕 意味着工程师最忙碌的时候即将结束 然而在这多事之秋&#xff0c;还是项目高峰期 想必很多工程师都很少过好双11吧 这可不行&#xff01;&#xff01;&#xff01; 别人家有的&#xff0c;我们家的工程师都要有&#xff01;为…

pytest学习和使用10-Pytest中的测试用例如何跳过执行?

10-Pytest中的测试用例如何跳过执行&#xff1f;1 引入2 Unittest中的用例跳过3 pytest.mark.skip4 pytest.skip()5 pytest.mark.skipif()6 跳过标记7 pytest.importorskip1 引入 有时候我们需要对某些指定的用例进行跳过&#xff0c;或者用例执行中进行跳过&#xff0c;在Uni…

高项 成本管理论文

4个过程 1&#xff0c;规划成本&#xff1a;为规划、管理、花费和控制项口成本而制定政策、程序和文档的过程。 2&#xff0c;估算成本&#xff1a;对完成项目活动所需资金进行近似估算的过程。 3&#xff0c;制定预算&#xff1a;汇总所有单个活动或工作包的估算成本&…

【面试题】JavaScript面试题详细总结(一)

js基础部分 01 值类型与引用类型 1.1 问法 js判断数据类型&#xff1f;js值类型与引用类型有哪些&#xff1f;值类型与引用类型的区别&#xff1f; 1.2 介绍 JavaScript存储数据两个区域&#xff1a;栈和堆 栈&#xff1a;通常空间是固定的&#xff08;占据空间小、大小固定&…

MySQL基础|数据库存储时间段,数字从指定值递增AUTO_INCREMENT【详细版,建议收藏】

今天&#xff0c;在写SQL语句存储时间时遇到了一些问题&#xff0c;最后成功解决了 mysql基础一、时间字段的格式限制&#xff08;一&#xff09;精确到秒的表达1、错误的表达2、解决方式如下3、查看创建的表&#xff08;二&#xff09;存储一个时间段1、错误的表达语句2、解决…