Изучите общую многопроектную архитектуру повторного использования кода webpack4 и webpack5

Webpack
Изучите общую многопроектную архитектуру повторного использования кода webpack4 и webpack5

введение проблемы

Начнем с трудного вопроса:Многостраничное приложение MPA или архитектура микроинтерфейса, как работать с общими частями страниц.

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

Например этот блог:Дилемма документов Tencent

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

// .NET
 @Html.Partial("Header")

или

// Java
<%@ include file="header.jsp" %>

Внедрите общедоступные шаблоны, чтобы общедоступные части отображались непосредственно при доступе к странице.

Но если это современный проект (например, React), проект MPA с неразделенными интерфейсом и сервером (страница по-прежнему обрабатывается серверной частью, а рендеринг Dom берет на себя React), мы скопируем встроенные файлы ресурсов во внутренний проект. , добавив сценарий и стиль на страницу для рендеринга.

На данный момент проблема проявляется: для общей части заголовка страницы это компонент, отображаемый React.

Общепринятой практикой является встраивание общедоступной части в качестве компонента непосредственно в каждый проект уровня страницы, что ж, документы Tencent делают то же самое.

Это имеет следующие недостатки:

  • Создайте избыточность, каждый проект на уровне страницы будет упакован в него при его сборке, что бесполезно тратит пропускную способность при загрузке.

НапримерHeaderЧасть объема индивидуальной сборки составляет400KB, то каждый результат сборки на уровне страницы будет увеличиваться по сравнению с существующим томом.400KB(Игнорировать общие зависимости библиотек, предполагая унифицированное использованиеDllReferencePluginиметь дело с). Без малейшего преувеличения мыHeaderЕсть много функций, плюсchunksПосле этого действительно было почти500KB.

  • Если общественная часть внесена изменения, то все ссылки на его проект восстановить всю выпущенную версию!

особенно дляHeaderВсе приложения уровня страницы должны быть перестроены для распространения с минимальными изменениями в этой общей части, которую использует каждая страница.


Например, центр уведомлений документа Tencent на следующем рисунке:

通知中心


Это казалось невозможным до выхода webpack5!

Передняя часть документов Tencent также изучала эту проблему, но из процесса исследования, описанного в статье, она в основном предназначена для упакованных__webpack_require__В метод встроены крючки для поднятия шума, а эффективного решения предложено не было.

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

- Выдержка из "Дилемма документов Tencent

Однако, после серии исследований, мы отлично решили эту проблему в июле 2019 года, используя существующие функции webpack4! Так совпало, что команда Wepback также добавила в последней версии V5Module-FederationЭта функция, используемая для этой сцены.

Начнем официальную галантерею!

решение webpack4

Причина, по которой друзья Tencent Docs не осмеливаются быть правыми__webpack_require__Делаю это только потому, что это слишком сложно, и я боюсь, что это вызовет другие проблемы после изменения.

На самом деле, они с самого начала пошли не в том направлении, так наз.Ударь змею и ударь семь дюймов, Если вы пропустите семь дюймов, это вызовет ряд проблем, или вы не решитесь попасть.

Итак, обращаем внимание на «семь дюймов»Внешние расширения (externals)Заглянем в свойства (по умолчанию все уже знают его функцию).

Поскольку это мост между внутренними компонентами веб-пакета (npm + сборки) и внешними ссылками, я думаю, что это наиболее подходящее место для использования ножа!


рассмотрениеexternalsа такжеumd

Напомним, что мы используемexternalsНастройте сторонние библиотеки CDN, такие какReact, настроенный следующим образом:

externals: {
  'react-dom': 'ReactDOM',
  'react': 'React'
}

Затем мы смотрим на React Справочная ссылка CDN, обычно мы используемumdверсия сборки, она будет совместимаcommonjs,commonjs2,amd,windowи т. д., в среде нашего браузера он свяжетReactпеременная кwindowначальство:

JoLAfI.png

externalsРоль такова: при сборке веб-пакета он встречаетimport React from 'react'а такжеimport ReactDOM from 'react-dom'будет избегаться при импорте операторовnode_modulesидтиexternalsНастроенное сопоставление найдено, и это значение сопоставления (ReactDOMа такжеReact) Этоwindowнайдено в переменной.

Следующие два рисунка демонстрируют это:

JoO4qU.png

JoXwWR.png

Почему я трачу так много места на то, чтобы заложить основу для этого?externalsШерстяная ткань? Потому что это мост, мост для подключения внешних модулей!

Давайте сделаем смелую идею: В идеале моя общедоступная часть — это компонент заголовка! Если он построен самостоятельно какumdпакет вexternalsнастроен в видеimport Header from 'header';Импортировать, потом использовать как компонент, как?

Я проверил это и не имею никаких проблем! ! !

Однако идеальной ситуации не существует, и вероятность так же мала, как выигрыш в благотворительной лотерее.

Большинство из нас такие:

import { PredefinedRole, PredefinedClient } from '@core/common/public/enums/base';
import { isReadOnlyUser } from '@core/common/public/moon/role';
import { setWebICON } from '@core/common/public/moon/base';
import ErrorBoundary from '@core/common/public/wrapper/errorBoundary';
import OutClick from '@core/common/public/utils/outClick';
import { combine } from '@core/common/entry/fetoolkit';
import { getExtremePoint } from '@core/common/public/utils/map';
import { cookie } from '@core/common/public/utils/storage';
import Header from '@common/containers/header/header';
import { ICommonStoreContainer } from '@common/interface/store';
import { cutTextForSelect } from '@common/public/moon/format';
import { withAuthority } from '@common/hoc';
......

Подобные ссылки встречаются в десятках проектов, особенно псевдонимы (alias) используйте, есть десятки ситуаций цитирования!

ПС: мыmonorepoархитектура,@core/commonЭто общедоступный проект зависимостей. Здесь поддерживаются методы инструментов, перечисления, экземпляры axios, общедоступные компоненты, меню и т. д., поэтому мы испробовали все средства для независимой сборки этого проекта.

а такжеexternalsПриведенный выше метод конфигурации поддерживает преобразование только в следующих случаях, он просто точно соответствует имени модуля:

import React from 'react'; => 'react': 'React' => e.exports = React;
import ReactDom from 'react-dom'; => 'react-dom': 'ReactDOM' => e.exports = ReactDOM;

За именем сторонней библиотеки не может следовать/Пути! Например, не поддерживается следующее:

import xxx from 'react/xxx';

Ивы и цветы

Я думал, маловероятно, что разработчики веб-пакетов будут настолько жесткими в отношении API, должны быть скрытые входы. как и ожидалось! Прочитав официальную документацию, я нашел подсказку:Он также поддерживает функции!

J5olT0.png

Функция функции состоит в том, чтобы:свободно распоряжаться любымimportутверждение!

Мы можем попробовать напечатать входные параметры в этой функцииrequestзначение, результат показан на следующем рисунке:

J7Sefg.png

всеimportЦитаты все напечатаны! Итак, мы можем манипулировать по желанию@commonа также@core/commonРелевантные ссылки! Например:

    function(context, request, callback) {
        if (/^@common\/?.*$/.test(request) && !isDevelopment) {
          return callback(
            null,
            request.replace(/@common/, '$common').replace(/\//g, '.')
          );
        }
        if (/^@moon$/.test(request) && !isDevelopment) {
          return callback(null, '$common.Moon');
        }
        if (/^@http$/.test(request) && !isDevelopment) {
          return callback(null, '$common.utils.http');
        }
        callback();
      }

Объясните здесь,callbackявляется callback-функцией (что также означает, что она поддерживает асинхронное суждение), ее первый параметр имеет неизвестное назначение и не указан в документации, второй параметр — это строка, которая будет отправлена ​​вwindowвыполнить это выражение на$common.Moon, он найдетwindow.$common.Moon.

Таким образом, цель приведенного выше кода предельно ясна:@commonзаменить$common, который будет ссылаться на путь в/заменить.иди вместо этогоwindowНайти на.

Имена переменных не могут начинаться с@символ, так что я будуlibraryЗначение заменяется на$common

Так,Теперь при создании проекта на уровне страницы можно удалить общую часть и оставить ее автоматически.windowискал на, но в это времяwindowеще нет$commonобъект!

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

Прежде всего, в конце предыдущего раздела наши потребности ясны, нам нужно построить$commonОбъектыwindowвыше, для этого мы можем использоватьumd,windowилиglobalформа для построения. но,$commonДолжен быть ряд податрибутов наimportПуть для иерархического дизайна, например:

import $http, { Api } from '@http';
import Header from '@common/containers/header/header';
import { CommonStore } from '@common/store';
import { timeout } from '@packages/@core/common/public/moon/base';
import * as Enums2 from '@common/public/enums/enum';
import { Localstorage } from '@common/utils/storage';

Нам нужно$commonИмеет следующую структуру:

J7AwTA.png

Итак, как построить эту иерархию$commonЧто с объектом? Ответ очень прост, достаточно экспортировать объект соответствующей структуры для записи компиляции!

Вставьте код напрямую:

// webpack.config.js
    output: {
      filename: "public.js",
      chunkFilename: 'app/public/chunks/[name].[chunkhash:8].js',
      libraryTarget: 'window',
      library: '$common',
      libraryExport: "default",
    },
    entry: "../packages/@core/common/entry/index.tsx",
// @core/common/entry/index.tsx
import * as baseEnum from '../public/enums/base';
import * as Enum from '../public/enums/enum';
import * as exportExcel from '../public/enums/exportExcel';
import * as message from '../public/enums/message';
import commonStore from '../store';
import * as client from '../public/moon/client';
import * as moonBase from '../public/moon/base';
import AuthorityWrapper from '../public/wrapper/authority';
import ErrorBoundary from '../public/wrapper/errorBoundary';
import * as map from '../containers/map';
import pubsub from '../public/utils/pubsub';
import * as format from '../public/moon/format';
import termCheck from '../containers/termCheck/termCheck';
import filterManage from '../containers/filterManage/filterManage';
import * as post from '../public/utils/post';
import * as role from '../public/moon/role';
import resourceCode from '../public/moon/resourceCode';
import outClick from '../public/utils/outClick';
import newFeature from '../containers/newFeature';
import * as exportExcelBusiness from '../business/exportExcel';
import * as storage from '../public/utils/storage';
import * as _export from '../public/utils/export';
import * as _map from '../public/utils/map';
import * as date from '../public/moon/date';
import * as abFeature from '../public/moon/abFeature';
import * as behavior from '../public/moon/behavior';
import * as _message from '../public/moon/message';
import * as http from '../public/utils/http';
import Moon from '../public/moon';
import initFeToolkit from '../initFeToolkit';
import '../containers/header/style.less';
import withMonthPicker from '../public/hoc/searchBar/withMonthPicker';
import withDateRangePickerWeek from '../public/hoc/searchBar/withDateRangePickerWeek';
import withDateRangePickerClear from '../public/hoc/searchBar/withDateRangePickerClear';
import MessageCenterPush from '../public/moon/messageCenter/messageCenterPush';

import { AuthorityBusiness, ExportExcelBusiness, FeedbackBusinessBusiness,
  FilterManageBusiness, HeaderBusiness, IAuthorityBusinessProps,
  IExportExcelBusiness, IFeedbackBusiness, IFilterManageBusinessProps,
  IHeaderBusinessProps, IMustDoBusinessProps, INewFeatureBusinessProps,
  MustDoBusiness, NewFeatureBusiness } from '../business';

import {
  Header, FeedBack, MustDoV1, MustDoV2, Weather,
  withSearchBarCol, withAuthority,
  withIconFilter, withExportToEmail, withSelectExport, withPageTable, withVisualEventLog
} from '../async';

const enums = {
  base: baseEnum,
  enum: Enum,
  exportExcel,
  message
};

const business = {
  exportExcel: exportExcelBusiness,
  feedback: FeedbackBusinessBusiness,
  filterManage: { FilterManageBusiness },
  header: { HeaderBusiness },
  mustDo: { MustDoBusiness },
  newFeature: { NewFeatureBusiness },
  authority: { AuthorityBusiness },
};

const containers = {
  map,
  feedback: FeedBack,
  newFeature,
  weather: Weather,
  header: { header: Header },
  filterManage: { filterManage },
  termCheck: { termCheck },
  mustdo: {
    mustdoV1: { mustDo: MustDoV1 },
    mustdoV2: { mustDo: MustDoV2 },
  }
};

const utils = {
  pubsub,
  post,
  outClick,
  storage,
  http,
  export: _export,
  map: _map
};

const hoc = {
  exportExcel: {
    withExportToEmail: withExportToEmail,
    withSelectExport: withSelectExport
  },
  searchBar: {
    withDateRangePickerClear: withDateRangePickerClear,
    withDateRangePickerWeek: withDateRangePickerWeek,
    withMonthPicker: withMonthPicker,
    withSearchBarCol: withSearchBarCol,
  },
  wo: {
    withVisualEventLog: withVisualEventLog
  },
  withAuthority: withAuthority,
  withIconFilter: withIconFilter,
  withPageTable: withPageTable,
  withVisualEventLog,
  withSearchBarCol,
  withMonthPicker,
  withDateRangePickerWeek,
  withDateRangePickerClear,
  withSelectExport,
  withExportToEmail,
};

export default {
  enums,
  utils,
  business,
  containers,
  hoc,
  initFeToolkit,
  store: commonStore,
  Moon: Moon,
  wrapper: {
    authority: AuthorityWrapper,
    errorBoundary: ErrorBoundary,
  },
  public: {
    enums,
    hoc,
    moon: {
      date,
      client,
      role,
      MessageCenterPush,
      resourceCode,
      format,
      abFeature,
      behavior,
      message: _message,
      base: moonBase,
    }
  }
};

Хотя код немного длинный, его нетрудно прочитать. Наша цель — построить объект экспорта, иерархия которого исчерпывает всеimportВозможности Пути!

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

Этот файл такой длинный, с одной стороны, потому что там действительно много публичных функций, а с другой стороны, потому что мы используем веб-пакетaliasфункции, что приводит к разнообразию эталонных методов, а возможность их исчерпания несколько больше (например,withSearchBarColЕсть два способа импорта, поэтому в структуре он появляется дважды). Поэтому, если вы хотите использовать эту программу, рекомендуется установить стандарт для ее управления.

использовать в сочетании

Самостоятельное построение публичных частей завершено, и приложение страницы тоже их извлекло, так как же использовать их вместе?

J7nwTg.png

Просто обратитесь к нему по порядку!

Как отлаживать

Осторожная детская обувь может попросить, поэтому приложение подстраницы относится к упакованномуpublic.js, Как отладить среду разработки во время реальной разработки?

J7uJN4.png

Когда приложение страницы создается или запускается, я добавляюisDevelopmentПеременные контролируются и извлекаются только после создания рабочей среды. В противном случае звоните напрямуюcallback()Возвращает как есть, ничего не делая.

Таким образом, при написании кода в среде разработки фактическая ссылка по-прежнему сохраняется.node_modulesпо местному проекту.

дляmonorepoлокальные проектные зависимости архитектуры,lernaУстанавливается мягкое соединение.

На самом деле достичь этого уровня с существующими функциями webpack 4 непросто, ведь передовые технические команды в стране и за границей бьются над этим уже несколько лет!

Далее, давайте взглянем на webpack5, решение, которое заставило их сиять!

решение webpack5

Module Federation

webpack5 предоставляет нам встроенный плагин:ModuleFederationPlugin

Автор определяет его следующим образом:

Федерация модулей позволяет приложению JavaScript динамически загружать код из другого приложения — в процессе обмена зависимостями, если приложение, использующее федеративный модуль, не имеет зависимости, необходимой для федеративного кода — Webpack загрузит отсутствующую зависимость из этого источника федеративной сборки. .

Федерация модулей позволяет приложению JavaScript динамически загружать код из другого приложения JavaScript при совместном использовании зависимостей. Если приложение использует федеративный модуль, который не имеет необходимых зависимостей в федеративном коде, Webpack загрузит отсутствующие зависимости из федеративного источника сборки.

Терминология Объяснение

несколько терминов

  • Module federation: а такжеApollo GraphQL federationИдея та же, но для модулей JavaScript, работающих в браузере или Node.js.

  • host: первая сборка Webpack, которая будет инициализирована во время загрузки страницы (когда запускается событие onLoad);

  • remote: еще одна сборка Webpack, частично используемая «хостом»;

  • Bidirectional(双向的) hosts: когда пакет или сборка веб-пакета запускается как хост или удаленный, он либо потребляет, либо потребляется другими приложениями — как во время выполнения.

  • Оркестрационный слой (orchestration layer): Это специально разработанная среда выполнения Webpack и точка входа, но это не обычная точка входа приложения и занимает всего несколько КБ.

Разбор конфигурации

Давайте сначала перечислим методы использования для всеобщего обозрения, а позже мы углубимся в детали:

// app1 webpack.config.js
const ModuleFederationPlugin = require("webpack/lib/container/ModuleFederationPlugin");
...
plugins: [
   new ModuleFederationPlugin({
      name: "app1",
      library: { type: "var", name: "app1" },
      remotes: {
        app2: "app2"
      },
      shared: ["react", "react-dom"]
    }),
]

// app1 App.tsx
import * as React from "react";
import Button from 'app2/Button';

const RemoteButton = React.lazy(() => import("app2/Button"));
const RemoteTable = React.lazy(() => import("app2/Table"));

const App = () => (
  <div>
    <h1>Typescript</h1>
    <h2>App 1</h2>
    <Button />
    <React.Suspense fallback="Loading Button">
      <RemoteButton />
      <RemoteTable />
    </React.Suspense>
  </div>
);

export default App;

// app2 webpack.config.js
...
plugins: [
  new ModuleFederationPlugin({
      name: "app2",
      library: { type: "var", name: "app2" },
      filename: "remoteEntry.js",
      exposes: {
        Button: "./src/Button",
        Table: "./src/Table"
      },
      shared: ["react", "react-dom"]
    })
]

Вот демонстрация того, как использовать общий доступ app2 в app1Buttonа такжеTableкомпоненты.

Небольшое пояснение значения этих элементов конфигурации:

  • ModuleFederationPluginОтwebpack/lib/container/ModuleFederationPlugin,Являетсяplugin.

  • либоhostилиremoteнужно инициализироватьModuleFederationPluginплагин.

  • Можно использовать любой модульhostилиremoteили оба.

  • nameОбязательно, не настроеноfilenameсвойство будет использоваться в качестве уровня оркестровки текущего проекта (orchestration layer)имя файла

  • filenameНеобязательно, имя файла слоя оркестровки, используется, если не настроеноnameстоимость имущества.

  • libraryТребуется, определите структуру модуля и имя переменной уровня оркестровки, а такжеoutputизlibraryTargetФункциональность похожа, но только для уровня оркестровки.

  • exposesНеобязательные (требуются для общих модулей) внешние элементы, пары ключ-значение,keyзначениеapp1(общим модулем), на который ссылаетсяimport Button from 'app2/Button';средняя и задняя половина пути,valueзначениеapp2Фактический путь в проекте.

  • remoteпара ключ-значение, что означает что-то вродеexternal,keyзначениеimport Button from 'app2/Button';первая половинаvalueзначениеapp2настроен вlibrary -> name, которое является именем глобальной переменной.

  • sharedОбщие модули для совместного использования сторонних библиотек. Напримерapp1Загрузите сначала, поделитесьapp2компонент вapp2Этот компонент зависит отreact. при загрузкеapp2в этом компоненте он пойдет наapp1изsharedнайти вreactЗависимость, если она есть, то она будет использоваться первой, не загружая свою (fallback)

Наконец в app1index.htmlвведен в

    <script src="http://app2/remoteEntry.js"></script>

Вот и все.

С приведенной выше конфигурациейapp1могут быть свободно ввезены и использованы вapp2/Buttonа такжеapp2/Table.

Анатомия файла сборки

Так,ModuleFederationPluginКак возникла эта волшебная черная магия?

Ответ находится в следующем фрагменте встроенного кода:

__webpack_require__.e = (chunkId) => {
 	return Promise.all(Object.keys(__webpack_require__.f).reduce((promises, key) => {
 		__webpack_require__.f[key](chunkId, promises);
 		return promises;
 	}, []));
 };
__webpack_require__.e(/* import() */ "src_bootstrap_tsx").then(__webpack_require__.bind(__webpack_require__, 601));

Этоapp1код запуска,__webpack_require__.eДля входа ищитеsrc_bootstrap_tsxЗависимости модуля ввода, где их найти?

Object.keys(__webpack_require__.f).reduce((promises, key) => {
 	__webpack_require__.f[key](chunkId, promises);
 	return promises;
 }, [])

пройдено здесьfВсе методы на объекте.

размещен нижеfВсе три метода привязаны к объектуoverridables remotes j:

/******/ 	/* webpack/runtime/overridables */
/******/ 	(() => {
/******/ 		__webpack_require__.O = {};
/******/ 		var chunkMapping = {
/******/ 			"src_bootstrap_tsx": [
/******/ 				471,
/******/ 				14
/******/ 			]
/******/ 		};
/******/ 		var idToNameMapping = {
/******/ 			"14": "react",
/******/ 			"471": "react-dom"
/******/ 		};
/******/ 		var fallbackMapping = {
/******/ 			471: () => {
/******/ 				return __webpack_require__.e("vendors-node_modules_react-dom_index_js").then(() => () => __webpack_require__(316))
/******/ 			},
/******/ 			14: () => {
/******/ 				return __webpack_require__.e("node_modules_react_index_js").then(() => () => __webpack_require__(784))
/******/ 			}
/******/ 		};
/******/ 		__webpack_require__.f.overridables = (chunkId, promises) => {
/******/ 			if(__webpack_require__.o(chunkMapping, chunkId)) {
/******/ 				chunkMapping[chunkId].forEach((id) => {
/******/ 					if(__webpack_modules__[id]) return;
/******/ 					promises.push(Promise.resolve((__webpack_require__.O[idToNameMapping[id]] || fallbackMapping[id])()).then((factory) => {
/******/ 						__webpack_modules__[id] = (module) => {
/******/ 							module.exports = factory();
/******/ 						}
/******/ 					}))
/******/ 				});
/******/ 			}
/******/ 		}
/******/ 	})();

/******/ 	/* webpack/runtime/remotes loading */
/******/ 	(() => {
/******/ 		var chunkMapping = {
/******/ 			"src_bootstrap_tsx": [
/******/ 				341,
/******/ 				980
/******/ 			]
/******/ 		};
/******/ 		var idToExternalAndNameMapping = {
/******/ 			"341": [
/******/ 				731,
/******/ 				"Button"
/******/ 			],
/******/ 			"980": [
/******/ 				731,
/******/ 				"Table"
/******/ 			]
/******/ 		};
/******/ 		__webpack_require__.f.remotes = (chunkId, promises) => {
/******/ 			if(__webpack_require__.o(chunkMapping, chunkId)) {
/******/ 				chunkMapping[chunkId].forEach((id) => {
/******/ 					if(__webpack_modules__[id]) return;
/******/ 					var data = idToExternalAndNameMapping[id];
/******/ 					promises.push(Promise.resolve(__webpack_require__(data[0]).get(data[1])).then((factory) => {
/******/ 						__webpack_modules__[id] = (module) => {
/******/ 							module.exports = factory();
/******/ 						}
/******/ 					}))
/******/ 				});
/******/ 			}
/******/ 		}
/******/ 	})();

/******/ 	/* webpack/runtime/jsonp chunk loading */
__webpack_require__.f.j = (chunkId, promises) => {
  ...
/******/ 	})();

последнийf.jМетод подробностей постить не буду, это в эпоху wepback4jsonpнагрузка.

Наше основное вниманиеf.remotesа такжеf.overridablesДва новых метода для webpack5.Zack Jackson (Автор) решил использовать здесь нож, что очень тонко. а такжеexternalразные(externalЭто вход контакта с внешним миром во время строительства), вот вход контакта с внешним миром после строительства.

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

Давайте поговорим о коде вышеreduceроль: в основномПройдите по трем вышеуказанным методам, чтобы узнать, существует ли зависимость один за другим.!

overridables

общие общедоступные сторонние зависимости,reactа такжеreact-domДругие общедоступные зависимости будут разрешены здесь.app1При сборке эти два файла будут создаваться независимо,app2внутреннийexposesМодули ищутся первыми, когда они загружаютсяapp1внизsharedЗависимость, если есть, используйте ее напрямую, если нет — используйте свою.

remotes

удаленная зависимость, будет настроена вremotesпары ключ-значение генерируются вidToExternalAndNameMappingСреди переменных наиболее важным моментом является:

YZNCTK.png

YZpdmT.png

Разместите две картинки, разберем их по порядку:

Во-первых, это было сказано ранее__webpack_require__.eбудем искать один за другимoverridables remotes jТри метода, когда они найденыremotesКак показано на рисунке выше, введитеremotesметод.

В настоящее времяchunkIdзначение переменнойsrc_bootstrap_tsx, то первый слой пройдет341а также980, а затем по этим двум значениям найтиidToExternalAndNameMapping, чтобы найти341ценность[731, "Button"],980ценность[731, "Table"].

Выделенная строка кода на картинке__webpack_require__(data[0]).get(data[1])Цель состоит в том, чтобы взять731этот модуль, а затем вызвать егоgetметод, параметрыButton | Table, чтобы получить компонент Button или Table.

Итак, вот вопрос, это731Что такое модуль? почему у него естьgetметод?

Продолжайте смотреть на вторую картинку выше, которую я выделил731Этот модуль, его внутренняя ссылка907модуль иoverrideохватыватьreact react-domдва модуля, указывающие на14а также471(эти два значения происходят именно изoverridablesопределяется в методеidToNameMappingотображение).

а также907Модули ссылаются на глобальные переменныеapp2!

Зачемapp2Эта переменная будет существоватьgetметод? мы строимapp2Этот метод пока не определен, давайте продолжим и посмотрим на него.app2Результат сборки:

YZu8SK.png

нажмите наremoteEntry.js, ответ раскрывается:

YZuaTA.png

ModuleFederationPluginНа уровне оркестровки будут определены два метода.getа такжеoverride,в:

getнайти себяmoduleMapкарта (изexposesконфигурация), это глобальная переменнаяapp2+ егоgetМетод соединяет два несвязанных модуля!

overrideиспользуется, чтобы найтиsharedЗависимости от третьих лиц здесь тоже очень тонкие, почему вы так говорите? В опубликованном ранее коде мы фокусируемся наapp1в слое оркестровки найдите__webpack_require__.Oобъект, который определен вoverridablesМетод запущен, его начальное значение равно{}, но в__webpack_require__.f.overridablesОн пуст при формальном исполнении. Это делаетapp1используется непосредственно во время выполненияfallbackMapping(то есть локальные сторонние зависимости).

YZ80HO.png

в то время как вышеупомянутый731точно используется в модулеapp2который предоставилoverrideметод будетreactа такжеreact-domизapp1Цитаты в переопределеныapp2Внутри мы смотрим наapp2Уровень оркестрации (все коды уровня оркестровки согласованы),app2серединаoverridablesпросто используйте__webpack_require__.Oсерединаreactа такжеreact-domполагаться!

YZJy6I.png

можно увидеть,app2серединаoverrideметод будет вызываться извнеapp1Сторонние зависимости в переопределенном__webpack_require__.OПеременная!

Вот почему автор подчеркивает, что вряд ли существует избыточность зависимостей:

Дублирование зависимостей практически отсутствует.Через разделяемый вариант — удаленные устройства будут зависеть от зависимостей хоста, если у хоста нет зависимости, удаленный будет загружать свой собственный.Без дублирования кода, но встроенная избыточность.

По состоянию на 13 мая 2010 г. я обнаружил, что этот код плагина webpack5 все еще объединяет новые коммиты в основную ветку. Я взглянул на последние два представления и обнаружил, что уровень упаковки сильно изменился (элементы конфигурации пока не изменились), поэтому приведенный выше код результата упаковки приведен только для справки, и вы можете примерно понять Принцип. Код результата сборки, который я тестировал сегодня, отличается.

Суммировать

ModuleFederationPluginЭто дает нам неограниченное пространство для воображения, и существует множество сценариев приложений, таких как совместное использование зависимостей микроприложений на микроинтерфейсах, совместное использование модулей и т. д.

Два недостатка, о которых я могу думать:

  • Во-первых, для раскрытия модулей требуются дополнительные модули.exposesКонфигурация (не подходит для нашего собственного сценария в предыдущем разделе этой статьи,entryструктура экспорта слишком сложная), и все используемые модули должны быть проинформированы о правильной конфигурации;

  • Во-вторых, при отладке локальных зависимостей после настройки локальных зависимостей npm link или lerna вам также необходимо настроить таргетингremotesнастроить то же имяwebpack aliasа такжеtsconfig paths, немного сложно.

Однако,wepback4а такжеwebpack5Когда эти два решения сочетаются и используются в соответствии со сценой, получается почти идеально!

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

использованная литература

Webpack 5 Module Federation: A game-changer in JavaScript architecture