Используйте федеральный модуль для совместного использования кода между приложениями.

Webpack

Автор: Шун Эдж

предисловие

Когда проекту А необходимо совместно использовать определенный компонент проекта Б и необходимо обеспечить, чтобы в последующих итерациях обе стороны оставались согласованными. На данный момент есть два варианта:

  1. Резюме Дафа, полностью скопируйте компоненты проекта Б в проект А.
  2. Отделите компонент, опубликуйте его во внутреннем npm и загрузите компонент через npm.

CV Dafa быстрее, чем независимые компоненты, ему не нужно отделять компоненты от проекта B и публиковать npm, но он не может синхронизировать код во времени.
Когда оба проекта используют Webpack5, передайтеФедеральный модульТолько несколько строк конфигурации могут быть достигнуты в компоненте B проекта синхронизации проекта A.

1. Что такое федеративный модуль (Module Federation)

Федеративные модули — это новая функция, предоставляемая webpack5, которая изначально предоставляется через webpack.ModuleFederationPluginплагин для достижения.

Объединение модулей позволяет использовать несколькоwebpackПостроить для работы вместе. С точки зрения выполнения, несколько созданных модулей будут вести себя как гигантский график подключенных модулей. С точки зрения разработчика модули могут быть импортированы из указанного удаленного сборки и используются с минимальными ограничениями. СделатьJavaScriptПриложение может работать динамически на клиенте или сервере или динамически загружать другое приложение.bundle.jsилиbuildПосле сгенерированного кода и общих зависимостей.

пройти черезModule FederationРеализованное совместное использование кода является двунаправленным, но для каждого случая существуют сценарии деградации.Module federatedВы всегда можете загрузить свои собственные зависимости, но попытаться использовать потребительские зависимости перед загрузкой. Меньшее избыточность кода, зависимости отделяются как одинWebpackПостроить.

2. Как использовать федеративные модули для совместного использования кода между приложениями

Динамический импорт удаленных модулей

Когда удаленный проект не может быть исправлен в конфигурации веб-пакета, объединенный модуль можно использовать с помощью динамического импорта.

Разница между динамическим импортом и статическим импортом заключается в том, что вам нужно самостоятельно реализовать загрузку js и подключение контейнера.

// host
// 加载js标签
function loadScript(src){
  return new Promise((res, rej) =>{
      const srcirpt = document.createElement('script')
      script.src = src;
      script.onload = res;
      script.onfaild = rej
      document.body.appendChild(script);
  })
}

// 连接容器
function loadComponent(scope, module) {
    return async () => {
      // 初始化共享作用域(shared scope)用提供的已知此构建和所有远程的模块填充它
      await __webpack_init_sharing__('default');
      const container = window[scope]; // 或从其他地方获取容器
      // 初始化容器 它可能提供共享模块
      await container.init(__webpack_share_scopes__.default);
      const factory = await window[scope].get(module);
      const Module = factory();
      return Module;
    };
}

// 加载远程模块的入口文件并并连接
loadScript('http://localhost:3001/remote-entry.js').then(() => {
  loadComponent('app', 'button').then(module => {
      console.log(module)
  });    
})

// remote
module.exports = {
  plugins: [new ModuleFederationPlugin({
      name: "app",
      filename: 'remote-entry.js',
      exposes: {
          "button": "./src/button.js"
      },
  })]
}

Динамически импортировать демонстрационный адрес удаленного модуля

Статически импортировать удаленные модули

// host
module.exports = {
  plugins: [new ModuleFederationPlugin({
      name: "host",
      remotes: {
          "app": "app@http://localhost:3001/remote-entry.js"
      },
      shared: {
          react: {
              eager: true
          }
      }
  })]
}

// remote
module.exports = {
  plugins: [new ModuleFederationPlugin({
      name: "app",
      filename: 'remote-entry.js',
      exposes: {
          "button": "./src/button.js"
      },
      shared: ['react']
  })]
}

Демонстрация удаленного модуля импорта статического адреса

Три основные функции модуля федерации

  • remotesИспользуется для объявления того, на какие удаленные файлы будут ссылаться и куда импортировать удаленные ресурсы.

    // 在此项配置中,会从远程的 http://localhost:3000/app-entry.js 读取文件来加载 app 模块
    new ModuleFederationPlugin({
        name: "template",
        remotes: {
            app: "http://localhost:3000/app-entry.js"
        }
    })
    
  • exposesИспользуется для объявления того, какие ресурсы будут доступны для удаленного использования.

    // 此项配置会相当于建立一个新的entry入口从 ./src/button.js 文件开始进行打包生产新的资源
    // Tips: 
    //   从这个entry打包的资源不会与webpack主入口的资源共享内容。即两个入口都引用了react的话,react会在两个项目中分别存在
    new ModuleFederationPlugin({
        name: "template",
        exposes: {
            button: "./src/button.js"
        }
    })
    
  • shared

    // 什么哪些模块会在主项目和远程项目中共享
    // - 例如:当主项目提供了react的话,远程项目则会直接使用主项目的react模块,而不会再次从远程加载(远程项目的react会单独打包)
    // - 主项目的shared集合需要包含远程项目的shared
    // - eager: 是否立即加载模块而不是异步加载。如果在主项目的入口文件中依赖了这个模块就必须设置eager,否则报错
    // - singleton: 是否确保使用单例模式
    new ModuleFederationPlugin({
        name: "template",
        shared: {
            react: {
                eager: true
            }
        }
    })
    

Если вы хотите узнать больше оФедеральный модуль.

3. Принципиальный анализ модуля федерации

Федеративные модули изначально предоставляются через WebPackModuleFederationPluginОн реализуется плагином, который имеет два основных концепция: хост (потребляющий другие пульты) и удаленный (потребляющий хост). Каждый элемент может быть хостом или удаленным или оба.

  • В качестве хоста вам необходимо настроить удаленный список и общий модуль.
  • В качестве удаленного вам необходимо настроить имя проекта (имя), метод упаковки (библиотека), имя упакованного файла (имя файла), предоставленный модуль (предоставляет) и модуль, используемый совместно с хостом (общий).

принцип упаковки webpack

Федеративный модуль оптимизирован на основе веб-пакета, поэтому, прежде чем углубляться в федеративный модуль, мы должны сначала узнать, как веб-пакет работает с пакетом.

Читая результаты упаковки webpack, мы видим, что каждый раз, когда webpack упаковывается, все ресурсы оборачиваются функцией немедленного выполнения, что позволяет избежать загрязнения глобальной среды, но также делает внутренние модули недоступными для внешнего мира.

В этой функции выполнения Webpack использует__webpack_modules__объект содержит весь код модуля, а затем использует внутренне определенный__webpack_require__метод из__webpack_modules__Загрузите модуль. И выставить глобальный в обоих случаях асинхронной загрузки и разбиения файлаwebpackChunkМассив используется для связи нескольких ресурсов веб-пакета.Этот массив перезаписывается методом push-уведомлений веб-пакета и будет отправляться другим ресурсам в других ресурсах.webpackChunkСинхронно добавлять в массив при добавлении нового контента__webpack_modules__для достижения интеграции модуля.

Федеральный модуль основан на этом механизме, модифицированном__webpack_require__Часть реализации, загружайте ресурсы с удаленного компьютера, когда это требуется, а затем объединяйтесь в__webpack_modules__середина.

Tips:

  • webpackChunkв состоянии пройтиoutput.chunkLoadingGlobalМодификация конфигурации

ресурсы сборки webpack

Модульный механизм JS имеет множество стандартов, но в webpack все унифицировано в собственной реализации.__webpack_require__

однофайловая структура webpack

В единой файловой структуре WebPack все модули хранятся в__webpack_modules__объект, а затем единообразно использовать внутренне определенный__webpack_require__метод загрузки модуля.

В этом случае внешний вообще не может получить доступ к внутренним работающим ресурсам.

(() => {
    var __webpack_modules__ = {
        './src/other.js': () => {
			// ...
        } 
    }
    
    var __webpack_module_cache__ = {} // 模块导出结果的缓存
    
    function __webpack_require__() {  // 内部模块导入方法
    	// .....
    } 
    // .....
    
    
    // 入口文件内容
    (() => {
        __webpack_require__('./src/other.js')
    })()
})()

многофайловая структура webpack

В многофайловой структуре есть два исходных кода локального модуля хранения, один внутренний__webpack_modules__объект, другой глобальныйwebpackChunkмножество.

webpackChunkМассив фактически действует как мост для загрузки модулей из других файлов в файл записи.__webpack_modules__объект, и это делается путем переопределенияwebpackChunkРеализован методом push массива.

// index.js
(() => {
    var __webpack_modules__ = {
        './src/other.js': () => {
			// 源码
        } 
    }
    
    var __webpack_module_cache__ = {} // 模块导出结果的缓存
    
    function __webpack_require__() {} // 内部模块导入方法
    
    // 向全局暴露出一个对象
    self["webpackChunk"].push = webpackJsonpCallback.bind(null, chunkLoadingGlobal.push.bind(chunkLoadingGlobal));
    // 入口文件内容
    (() => {
        __webpack_require__('./src/other.js')
    })()
})()
// ./src/other2.js

(self["webpackChunk"] = self["webpackChunk"] || []).push([
  ["src_other2_js"],
  {
    "./src/other2.js": () => {
      console.log("in other2.js");
    }
  }
]);

Использование модуля ModuleFederationPlugin

new ModuleFederationPlugin({
  name: "app1",
  library: { type: "var", name: "app1" },
  filename: "remoteEntry.js",
  remotes: {
    app2: 'app2',
    app3: 'app3',
  },
  remoteType: 'var',
  exposes: {
    antd: './src/antd',
    button: './src/button',
  },
  shared: ['react', 'react-dom'],
  shareScope: 'default'
})

Свойства конфигурации:

  • Имя, необходимо, уникальный идентификатор, как выходной модуль (контейнер), прошедшийname/{name}/{выставить} метод;
  • библиотека, необязательно, метод упаковки, по умолчанию{ type: "var", name: options.name }, где имя здесь — это имя umd, которое является именем переменной, смонтированной под глобальным;
  • имя файла, необязательно, имя упакованного файла;
  • remotes, необязательный параметр, указывает, что текущее приложение является хостом, который может ссылаться на открытый модуль в Remote;
  • remoteType, необязательный, по умолчаниюvar, ("var"|"модуль"|"назначить"|"это"|"окно"|"я"|"глобальный"|"commonjs"|"commonjs2"| "commonjs-модуль"|"amd"|"amd -require"|"umd"|"umd2"|"jsonp"|"system"|"promise"|"import"|"script"), внешний тип удаленного контейнера;
  • exposes, необязательный, указывает, что текущее приложение является удаленным, на модули в экспозициях могут ссылаться другие хосты, а метод ссылки — import(name/{name}/{разоблачать});
  • общий, необязательный, в основном используется для того, чтобы избежать множественных публичных зависимостей в проекте.Если это свойство настроено, вебпак сначала определит, есть ли соответствующий пакет в локальном приложении при загрузке, а если нет, загрузит пакет зависимостей удаленного приложение. ;
  • shareScope, необязательный, имя общей области, используемое для всех общих модулей.
// 公共依赖shared的配置项
Shared = string[] | {
  [string]: {
    eager?: boolean; // 是否立即加载模块而不是异步加载
    import?: false | SharedItem; // 应该提供给共享作用域的模块。如果在共享范围中没有发现共享模块或版本无效,还充当回退模块。默认为属性名
    packageName?: string; // 设置包名称以查找所需的版本。只有当包名不能根据请求自动确定时,才需要这样做(如要禁用自动推断,请将requiredVersion设置为false)。
    requiredVersion?: false | string; // 共享范围内模块的版本要求
    shareKey?: string; // 用这个名称在共享范围中查找模块
    shareScope?: string; // 共享范围名称
    singleton?: boolean; // 是否在共享作用域中只允许共享模块的一个版本 (单例模式).
    strictVersion?: boolean; // 如果版本无效则不接受共享模块(默认为true,如果本地回退模块可用且共享模块不是一个单例,否则为false,如果没有指定所需的版本则无效)
    version?: false | string; // 所提供模块的版本,将替换较低的匹配版本
  }[]
}

Tips:

  • В использованииModule FederationОбязательно помните, настройте публично на Shared. Кроме того, обязательно заодно настройте Shared, иначе будет сообщено об ошибке.
  • Сам входной файл index.js не должен иметь логики, поместите логику в bootstrap.js, и index.js будет динамически загружать bootstrap.js. Если логика напрямую помещена в index.js, будет сообщено об ошибке.Если это общая ошибка конфигурации общедоступных зависимостей, вы можете настроить параметр стремления для ее решения. Основная причина в том, что если логика выполняется непосредственно в index.js, она будет опираться на js, выставленные Remote.В это время, если remote.js не загружен, будут проблемы.

Принцип модуля ModuleFederationPlugin

ModuleFederationPluginДелайте в основном три вещи:

  • Как поделиться зависимостями: использованиеSharePlugin
  • Как выставить модуль: используйтеContainerPlugin
  • Как сослаться на модуль: используйтеContainerReferencePlugin

SharePlugin

Этот плагин позволяет совместно использовать публичные зависимости

ContainerPlugin

Плагин создает запись для указанного общедоступного модуля.entry.jsПосле выполнения объект будет висеть на окне Объект имеет два метода.getа такжеinit.getметод получения модуля.initметод используется для инициализации контейнера, который может предоставлять общие модули.

// entry.js
var remote;remote = (() => {
    // ...
})()
// remote对象里的get和init方法
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 oldScope = __webpack_require__.S["default"];
	var name = "default"
	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
});

При использовании удаленного модуля передайтеinitНапишите свой общий доступ к Remote, а затем передайтеgetПолучите выставленный компонент в Remote, и когда он используется как Remote, оцените, есть ли доступные общие зависимости в Host, если да, загрузите эту часть зависимостей Host, если нет, загрузите свои собственные зависимости.

ContainerReferencePlugin

Этот плагин добавляет определенные ссылки на контейнеры как внешние и позволяет импортировать удаленные модули из этих контейнеров. При импорте пульт, предоставленный пользователем контейнера, будет вызываться для перегрузки.

Модули, определяемые пультами, также будут__webpack_modules__Он объявлен, но не будет иметь конкретной реализации, аналогичной асинхронному импорту.
Добавлено в вебпак5__webpack_require__.eметод, выполните следующие три функции в модуле, импортированном с помощью подметода, и вернитесь после успешного выполнения всех из них.

  • __webpack_require__.f.consumesОбщий модуль и используется для определения потребления, если текущая среда не имеет запроса к удаленному модулю
  • __webpack_require__.f.remotesиспользуется для подключения контейнера
  • __webpack_require__.f.jИспользуется для загрузки JS

4. Сценарии использования

  1. Он подходит для создания выделенной службы приложений компонентов для управления всеми компонентами и приложениями.Другим бизнес-уровням нужно только загружать соответствующие компоненты и функциональные модули в соответствии с их собственными бизнес-потребностями.
  2. Управление модулями является единым управлением, качество кода высокое, а скорость строительства высокая. Подходит для матричного приложения или визуального построения страницы и других сценариев.

Справочная статья

официальная документация веб-пакета 5
Совместное использование кода между приложениями Webpack5 — федерация модулей
Попробуйте федерацию модулей webpack5
Исследуйте Revolution Module Module JavaScript, вызванную новой функцией WebPack5, федерации модуля
Исследования трех сценариев приложений, WebPack New Function Module Федерация углубленного анализа