оригинал:Изучите применение федерации модулей, новой функции webpack5, в документах Tencent | AlloyTeam
Добавить Автора
Предисловие:
Захватывающая новая функция webpack5, федерация модулей, может не понравиться многим разработчикам, но она действительно привлекательна для документов Tencent, которые сильно пострадали от нескольких приложений.Эта статья поможет вам понять Tencent Дилемма документации и как федерация модулей может помочь нам выбраться из этого.
0x1 Дилемма документов Tencent
1.1 Фон сцены с несколькими приложениями
На функциональном уровне Tencent Documents пользователям наиболее знакомы четыре категории: word, excel, ppt и формы.Эти четыре категории не зависят друг от друга и могут разрабатываться и поддерживаться разными командами.С точки зрения разработчиков , четыре категории и четыре склада ведутся независимо.Кажется, что все очень просто, но на самом деле все намного сложнее. Давайте рассмотрим сценарий:
Требования к Центру уведомлений
Для сложных сценариев разрешений, чтобы позволить пользователям быстро получать последний статус, у нас действительно есть требование для центра уведомлений, а стиль ПК примерно такой, как на картинке выше. Вот запись, видимая на странице списка документа Tencent, Фактически, в четырех категориях, упомянутых выше, такая страница должна быть встроена.
Итак, возникает проблема. Чтобы свести к минимуму затраты на разработку и обслуживание, определенно лучше всего использовать общий набор кода для каждой категории. Самый простой способ представить это - использовать независимый пакет npm для его внедрения. Действительно, многие внутренние функции документов Tencent сейчас внедряются с помощью npm-пакетов, но на самом деле здесь будут некоторые проблемы:
Вопрос 1: Исторический кодекс
История документов Tencent очень запутанная, короче, в начале написание ES6 не поддерживалось в коде, поэтому не было возможности внедрить npm-пакеты. Нереально думать, что трансформация завершена на какое-то время, и спрос на продукцию не будет ждать, пока вы завершите такую трансформацию.
Вопрос 2: Эффективность релиза
Проблема здесь на самом деле в том, что мы сейчас используем npm-пакеты, а на самом деле мы ленивы и хотим по возможности. Если вводить в виде npm-пакета, то раз есть изменения, то нужно менять 5 складов (четыре категории + страница со списком), чтобы обновить здесь версию.На самом деле стоимость публикации достаточно велика, и она на самом деле очень болезненно для разработчиков.
1.2 Наше решение
Чтобы быстро внедрить React для ускорения разработки требований в среде, не поддерживающей код ES6, мы придумали так называемый режим Script-Loader (далее SL).
Общая структура показана на рисунке:
Это просто эталонный способ введения jquery, мы используем другой проект для реализации этих функций, затем код упакован в код ES5, предоставляет много внешнего интерфейса, а затем на каждой странице категории введение скрипта загрузки, который мы предоставляем, внутренний автоматически для загрузки документов, доступа к js-файлу адреса CDN для каждого модуля и загрузки. Сделайте это независимо от каждого модуля, и все модули будут сформированы независимо от каждой категории.
В этом режиме каждый раз, когда мы публикуем, нам нужно публиковать только каждый измененный модуль и последний файл конфигурации, а другие категории могут обновляться автоматически.
Этот режим не обязательно подходит для всех проектов, и это не обязательно лучшее решение.С текущей точки зрения, это немного похоже на концепцию микро-интерфейса, но на самом деле это другое, поэтому я не буду расширяться. это здесь. Этот режим действительно может удовлетворить потребности повторного использования кода нескольких приложений, таких как Tencent Documents.
1.3 Возникшие проблемы
Серьезной проблемы с этим режимом по сути нет, но есть очень болевой момент, который нас беспокоил, а именно проблема разделения кода между кодом категории и SL. Например:
После преобразования категории Excel использовался React, а модуль A, модуль B и модуль C SL представили React.
Поскольку модули SL независимы друг от друга, React также упакован отдельно, то есть, когда вы открываете Excel, если вы используете модули A, B и C, ваша последняя страница загрузит четыре копии кода React, Хотя это и не принесет никаких проблем, но для погони за передком мы все же хотим решить такие проблемы.
Решение: внешние
Для React мы можем загрузить React по умолчанию, поэтому напрямую настраиваем React в SL как External, чтобы он не был запакован, но на самом деле ситуация не так проста:
Проблема 1: Модули могут быть независимыми страницами
Что касается вышеупомянутого центра уведомлений, он не встроен в мобильную версию и является независимой страницей, поэтому эта независимая страница требует, чтобы вы вручную вводили React.
Проблема 2: общедоступный пакет не соответствует
Проще говоря, это пакет, от которого зависит SL, который нельзя использовать в категории, например Mobx или Redux.
Вопрос 3: Не все пакеты могут напрямую настраивать External
Проблема здесь в том, что мы можем совместно использовать такие пакеты, как React, настроив External как window.React, но не все пакеты могут это сделать.Для пакетов, которые нельзя настроить как глобальную среду, здесь нет решения.
Исходя из этих проблем, наш текущий выбор — это компромиссное решение, мы извлекаем пакеты, которые могут настроить глобальное окружение, указываем зависимости для каждого модуля, а затем внутри SL зависимости будут обнаружены до загрузки кода модуля, и зависимости будет загружен после завершения, загрузит и выполнит фактический код модуля.
С этим методом есть большая проблема.Вам нужно поддерживать такие зависимости вручную.На самом деле каждый общий пакет нужно упаковывать в файл CDN отдельно,чтобы при неудачном обнаружении зависимости у вас был файл с нижней загрузкой. Поэтому, по сути, только пакет React в настоящее время делает этот обмен.
Итак, здесь основной проблемой становится код категории и то, как делать SL.代码共享
. Для других проектов это фактически то, как это сделать с несколькими приложениями.代码共享
.
0x2 Принцип упаковки webpack
Чтобы решить вышеуказанную проблему, мы на самом деле хотим начать с веб-пакета и реализовать такой плагин, который поможет нам решить эту проблему. Основная идея состоит в том, чтобы подключить внутреннюю функцию запроса веб-пакета.Перед этим давайте рассмотрим некоторые принципы упаковки веб-пакета, которые позже также станут основой для понимания федерации модулей. Если вы знакомы здесь, вы можете быстро перейти к третьему разделу, но незнакомым студентам рекомендуется присмотреться.
2.1 чанк и модуль
В веб-пакете есть две очень важные концепции, называемые чанк и модуль.Для простоты мы рассматриваем только концепции, связанные с js, и используем собственное понимание автора, чтобы объяснить их прямые различия:
модуль: каждый исходный файл js фактически можно рассматривать как модуль
Чанк: каждый упакованный файл js на самом деле является фрагментом, и каждый фрагмент содержит множество модулей.
Количество чанков по умолчанию фактически определяется количеством js в вашем входном файле, но если вы настроите динамическую загрузку или извлечете общедоступные пакеты, новые чанки также будут сгенерированы.
2.2 Интерпретация кода упаковки
После того, как мы получили общее представление, нам нужно понять, как код, упакованный webpack, загружается и выполняется на стороне браузера. С этой целью мы подготовим очень простую демонстрацию и посмотрим на ее сгенерированный файл.
src
---main.js
---moduleA.js
---moduleB.js
/**
* moduleA.js
*/
export default function testA() {
console.log('this is A');
}
/**
* main.js
*/
import testA from './moduleA';
testA();
import('./moduleB').then(module => {
});
Очень просто, запись jsmain.js
, который напрямую импортируетсяmoduleA.js
, а затем динамически импортироватьmoduleB.js
, то окончательный сгенерированный файл состоит из двух фрагментов, а именно:
-
main.js
а такжеmoduleA.js
состоит изbundle.js
- ``moduleB.js
组成的
0.bundle.js`
Если вы понимаете основные принципы веб-пакета, то вы знаете, что mainTemplate и chunkTemplate используются для рендеринга отдельно.Неважно, если вы не понимаете, давайте продолжим интерпретировать сгенерированный код.
во что превращается импорт
весьmain.js
Код упакован следующим образом
(function (module, __webpack_exports__, __webpack_require__) {
"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony import */
var _moduleA__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__( /*! ./moduleA */ "./src/moduleA.js");
Object(_moduleA__WEBPACK_IMPORTED_MODULE_0__["default"])();
__webpack_require__.e( /*! import() */ 0).then(__webpack_require__.bind(null, /*! ./moduleB */ "./src/moduleB.js")).then(module => {
});
})
Можно видеть, что наш модуль прямого импорта A в конечном итоге станет webpack_require, и эта функция является основной функцией после упаковки webpack, которая предназначена для решения проблемы введения зависимостей.
Как реализован webpack_require?
Тогда давайте посмотрим, как реализован webpack_require:
function __webpack_require__(moduleId) {
// Check if module is in cache
// 先检查模块是否已经加载过了,如果加载过了直接返回
if (installedModules[moduleId]) {
return installedModules[moduleId].exports;
}
// Create a new module (and put it into the cache)
// 如果一个import的模块是第一次加载,那之前必然没有加载过,就会去执行加载过程
var module = installedModules[moduleId] = {
i: moduleId,
l: false,
exports: {}
};
// Execute the module function
modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
// Flag the module as loaded
module.l = true;
// Return the exports of the module
return module.exports;
}
Если упростить его реализацию, то на самом деле очень просто, то есть каждый раз, когда вам требуется, сначала заходите в кэшированную карту кеша installModules, чтобы посмотреть, загрузился ли он.modules
Это загружается в карту всех модулей.
откуда берутся модули
Я уверен, что многие люди сомневаются.Откуда такая важная карта модулей?, мы положилиbundle.js
Сгенерированный js снова упрощается:
(function (modules) {})({
"./src/main.js": (function (module, __webpack_exports__, __webpack_require__) {}),
"./src/moduleA.js": (function (module, __webpack_exports__, __webpack_require__) {})
});
Итак, вы можете видеть, что на самом деле это функция немедленного выполнения,modules
Это входной параметр функции, а конкретное значение — это все модули, которые мы включаем.На данный момент, как загружается чанк и как чанк содержит модули, я думаю, у каждого будет свое понимание.
Как работает динамический импорт?
Приведенный выше фрагмент представляет собой файл js, поэтому он поддерживает свою собственную часть.modules
, а затем использовать его самостоятельно не проблема, но мы знаем, что новый файл js будет сгенерирован динамическим введением,Затем этот новый файл js0.bundle.js
У вас есть в нем свое?modules
Шерстяная ткань? Этоbundle.js
как знать0.bundle.js
внутриmodules
Шерстяная ткань?
Давайте посмотрим, как выглядит код динамического импорта:
__webpack_require__.e( /*! import() */ 0).then(__webpack_require__.bind(null, /*! ./moduleB */ "./src/moduleB.js")).then(module => {
});
С точки зрения кода, это на самом деле слой webpck_require.e снаружи, а затем это промис, а затем выполняется webpack_require.
По сути, webpck_require.e должен загрузить js-файл чанка0.bundle.js
, конкретный код не выложен, ничего особенного.
подожди пока загрузится он думает **bundle.js
внутриmodules
будут0.bundle.js
включенныеmodules
**, как это делается?
мы видим0.bundle.js
Какое содержание придает ему такую волшебную силу:
(window["webpackJsonp"] = window["webpackJsonp"] || []).push(
[
[0],
{
"./src/moduleB.js": (function (module, __webpack_exports__, __webpack_require__) {})
}
]
);
Взглянув на упрощенный код, первое, что приходит на ум, это jsonp, но, к сожалению, это не функция, а просто помещает свой собственный идентификатор модуля и соответствующий идентификатор модуля в глобальный массив.modules
. Кажется, что ядро магии должно быть вbundle.js
Это внутри, и это правда.
var jsonpArray = window["webpackJsonp"] = window["webpackJsonp"] || [];
var oldJsonpFunction = jsonpArray.push.bind(jsonpArray);
jsonpArray.push = webpackJsonpCallback;
jsonpArray = jsonpArray.slice();
for(var i = 0; i < jsonpArray.length; i++) webpackJsonpCallback(jsonpArray[i]);
var parentJsonpFunction = oldJsonpFunction;
существуетbundle.js
Внутри мы видим такой кусок кода, который собственно и означает, чтоМы угнали функцию push,Это0.bundle.js
Как только загрузка будет завершена, не выполним ли мы это, тогда мы сможем получить все параметры, а затем поставить0.bundle.js
Все модули в нем добавлены в своиmodules
Зайти внутрь!
2.3 Подводя итог
Если вы не очень хорошо это понимаете, вы можете прочитать приведенный выше код несколько раз с картинкой ниже.
На самом деле, проще говоря, для файла mainChunk мы сохраняемmodules
Все модули, подобные этой карте, и предоставляют такие функции, как WebPack_require. Для файлов Chunka (которые могут быть сгенерированы путем извлечения публичного кода или динамически загружены), мы используем метод, аналогичный JSONP, чтобы позволить ему хранить все свой собственныйmodules
Добавить в основной блокmodules
Зайти внутрь.
2.4 Как решить проблему с документами Tencent?
Основываясь на таком понимании, мы думаем, можно ли решить проблему совместного использования кода документов Tencent для нескольких приложений?
Что касается фактической сцены документа Tencent, это выглядит следующим образом:
Поскольку это независимый проект, пакет webpack также имеет два основных чанка, а затем имеет свои собственные чанки (на самом деле в чанке будет покрытие чанка или модуля, поэтому идентификатор должен быть md5).
Суть проблемы в том, как пройти через два mainChunk'а.modules
?
Если это бесплатное программирование, я думаю, что у всех слишком много способов его реализовать, но в рамках ограничений фреймворка webpack мы думали о том, как это быстро реализовать.Решения, которые мы придумали, следующие:
Webpack_require внутри модуля SL взламывается нами каждый раз в
modules
Когда мы не можем найти его внутри, мы идем в Excelmodules
Найдите его внутри, поэтому вам нужно поставить Excelmodules
как глобальная переменная
Но что нам делать с модулями, которых нет в Excel?
Это, очевидно, среда выполнения. Нам нужно хорошо поработать над ухудшением сбоя при загрузке, но это столкнется с проблемой синхронного в асинхронный. Первоначально вы ввели модуль синхронно, но если он не существует в модулях Excel. Когда вам нужно сначала загрузить чанк, соответствующий этому модулю, это становится похоже на динамическую загрузку, но ваш код все еще синхронен, поэтому будут проблемы.
Так что нам это нужноЗависит от фронта, то есть после загрузки SL модуля он знает от каких разделяемых модулей он зависит, а потом определяет есть ли он.Если его нет то он будет загружен по очереди, и только после всего начнет выполняться сам зависимости есть.
0x3 Модульная федерация webpack5
Если честно, нижний слой вебпака пока очень сложен, и степень кастомизации в случае незнакомости определить нельзя, так что мы этим давно особо не занимались. Но случайно я узнал о федерации модулей webpack 5. Глядя на описание, я понял, что это очень похоже на то, что мы хотели, поэтому мы начали выяснять!
3.1 Введение в федерацию модулей
О том, что такое федерация модулей и что она делает
Проще говоря, это позволяет среде выполнения динамически определять введение и загрузку кода.
3.2 Демонстрация объединения модулей
Что нас больше всего беспокоит, так это реализация федерации модулей, чтобы решить, действительно ли она подходит для документов Tencent.
Здесь мы используем существующую демонстрацию:
module-federation-examples/basic-host-remote
Перед этим мне еще нужно представить вам, что делает эта демонстрация.
app1
---index.js 入口文件
---bootstrap.js 启动文件
---App.js react组件
app2
---index.js 入口文件
---bootstrap.js 启动文件
---App.js react组件
---Button.js react组件
Это структура файла.На самом деле, вы можете думать об этом как о двух независимых приложениях app1 и app2.Какая любовь и ненависть были у них раньше?
/** app1 **/
/**
* index.js
**/
import('./bootstrap');
/**
* bootstrap.js
**/
import('./bootstrap');
import App from "./App";
import React from "react";
import ReactDOM from "react-dom";
ReactDOM.render(<App />, document.getElementById("root"));
/**
* App.js
**/
import('./bootstrap');
import React from "react";
import RemoteButton from 'app2/Button';
const App = () => (
<div>
<h1>Basic Host-Remote</h1>
<h2>App 1</h2>
<React.Suspense fallback="Loading Button">
<RemoteButton />
</React.Suspense>
</div>
);
export default App;
Я разместил здесь только js-код app1, вам не нужно заботиться о коде app2. В коде нет ничего особенного, только один момент в App.js app1:
import RemoteButton from 'app2/Button';
То есть ключ здесь, и повторное использование кода в приложениях здесь! Код app1 использует код app2, но как в итоге выглядит этот код? Как вы представили код app2?
3.3 Конфигурация объединения модулей
Давайте посмотрим, как нужно настроить наш веб-пакет:
/**
* app1/webpack.js
*/
{
plugins: [
new ModuleFederationPlugin({
name: "app1",
library: {
type: "var",
name: "app1"
},
remotes: {
app2: "app2"
},
shared: ["react", "react-dom"]
})
]
}
На самом деле это конфигурация федерации модулей.Возможно, вы видите, что хотите выразить:
- Используется удаленный модуль app2, он называется app2
- Используется общий модуль, он называется общим
Между удаленными и общими все еще есть небольшая разница, давайте сначала посмотрим на эффект.
Сгенерированный html-файл:
<html>
<head>
<script src="app2/remoteEntry.js"></script>
</head>
<body>
<div id="root"></div>
<script src="app1/app1.js"></script><script src="app1/main.js"></script></body>
</html>
ps: путь js здесь был изменен, это настраивается, он просто показывает, какие файлы js загружаются откуда
Файлы, сгенерированные упаковкой app1:
app1/index.html
app1/app1.js
app1/main.js
app1/react.js
app1/react-dom.js
app1/src_bootstrap.js
ps: Так же нужно запаковать app2, но я не стал вставлять код и файлы конфигурации app2, и выложу позже, когда понадобится.
Финальная производительность страницы и загруженный js:
Время загрузки js сверху вниз на самом деле очень специфично, и ключ к расшифровке будет позже:
app2/remoteEntry.js
app1/app1.js
app1/main.js
app1/react.js
app1/react-dom.js
app2/src_button_js.js
app1/src_bootstrap.js
Самое главное здесь обратить внимание на то, откуда загружается каждый файл.Прежде чем анализировать принцип, мы можем, по крайней мере, сделать такие выводы, глядя на загрузку файла:
- Код пультов не упакован сам по себе, как и внешний, например app2/button для загрузки кода, упакованного app2
- Сам общий код упакован
Принцип объединения модулей
Прежде чем объяснять принцип, я все же отпускаю предыдущую картинку, потому что это ядро файлового модуля вебпака, даже если его обновить до 5, изменений нет
app1 и app2 по-прежнему имеют свои собственныеmodules
, так что ключом к реализации является дваmodules
Как синхронизировать, или как инжектить, тогда давайте посмотрим, как реализована федерация модулей.
3.3.1 Во что превращается импорт
// import源码
import RemoteButton from 'app2/Button';
// import打包代码 在app1/src_bootstrap.js里面
/* harmony import */
var app2_Button__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__( /*! app2/Button */ "?ad8d");
/* harmony import */
var app2_Button__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/ __webpack_require__.n(app2_Button__WEBPACK_IMPORTED_MODULE_1__);
Отсюда мы как будто ничего не видим, потому что это все еще обычный webpack_require.Он действительно переписывает webpack_require, как мы думали раньше?
К сожалению, в этой функции нет никаких изменений по сравнению с исходным кодом, поэтому суть здесь не в этом.
Но вы обратите внимание на порядок загрузки js:
app2/remoteEntry.js
app1/app1.js
app1/main.js
app1/react.js
app1/react-dom.js
app2/src_button_js.js // app2的button竟然先加载了,比我们的自己启动文件还前面
app1/src_bootstrap.js
Вспомним наш собственный анализ в предыдущем разделе.
Итак, нам нужно добавить зависимости, то есть после загрузки модуля SL он знает, от каких общих модулей он зависит, а затем определяет, существует ли он или нет.
Так это решается, полагаясь на начало?
3.3.2 Содержимое файла main.js
Потому что в html есть только два файла, связанных с app1: app1/app1.js и app1/main.js.
Тогда посмотрим, что написано в main.js
(() => { // webpackBootstrap
var __webpack_modules__ = ({})
var __webpack_module_cache__ = {};
function __webpack_require__(moduleId) {
if (__webpack_module_cache__[moduleId]) {
return __webpack_module_cache__[moduleId].exports;
}
var module = __webpack_module_cache__[moduleId] = {
exports: {}
};
__webpack_modules__[moduleId](module, module.exports, __webpack_require__);
return module.exports;
}
__webpack_require__.m = __webpack_modules__;
__webpack_require__("./src/index.js");
})()
Видно что разница не большая, просто поставил предыдущуюmodules
заменяетсяwebpack_modules
, затем поместите этоmodules
Инициализация параметра изменена на внутренне объявленную переменную.
Затем давайте посмотрим на реализацию внутри webpack_modules:
var __webpack_modules__ = ({
"./src/index.js": ((__unused_webpack_module, __unused_webpack_exports, __webpack_require__) => {
__webpack_require__.e( /*! import() */ "src_bootstrap_js").then(__webpack_require__.bind(__webpack_require__, /*! ./bootstrap */ "./src/bootstrap.js"));
}),
"container-reference/app2": ((module) => {
"use strict";
module.exports = app2;
}),
"?8bfd": ((module, __unused_webpack_exports, __webpack_require__) => {
"use strict";
var external = __webpack_require__("container-reference/app2");
module.exports = external;
})
});
Из кода видно, что есть три модуля:
./src/index.js 这个看起来就是我们的app1/index.js,里面去动态加载bootstrap.js对应的chunk src_bootstrap_js
container-reference/app2 直接返回一个全局的app2,这里感觉和我们的app2有关系
?8bfd 这个字符串是我们上面提到的app2/button对应的文件引用id
Кто эти файлы реакции и файлы app2/button загружали перед загрузкой src_bootstrap.js? Путем отладки мы обнаружили, что секрет находится в предложении webpack_require__.e("src_bootstrap_js")
При анализе загрузки веб-пакета в Разделе 2 мы узнали, что:
По сути, webpck_require.e должен загрузить js-файл чанка
0.bundle.js
, подожди пока после загрузки обратно он думаетbundle.js
внутриmodules
будут0.bundle.js
включенныеmodules
То есть исходный webpack_require__.e был пресным, просто грузил скрипт, так что мы не хотели выкладывать его код, но после этого апгрейда все изменилось, и он стал ключом к ключу!
3.3.3 Что делает webpack_require__.e
__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.f.После того, как все функции выполнены, то выполняется промис.
Суть проблемы заключалась в том, какие функции есть на webpack_require.f, и в итоге обнаружил, что там три функции:
Один: переопределяемые
/* webpack/runtime/overridables */
__webpack_require__.O = {};
var chunkMapping = {
"src_bootstrap_js": [
"?a75e",
"?6365"
]
};
var idToNameMapping = {
"?a75e": "react-dom",
"?6365": "react"
};
var fallbackMapping = {
"?a75e": () => {
return __webpack_require__.e("vendors-node_modules_react-dom_index_js").then(() => () => __webpack_require__("./node_modules/react-dom/index.js"))
},
"?6365": () => {
return __webpack_require__.e("vendors-node_modules_react_index_js").then(() => () => __webpack_require__("./node_modules/react/index.js"))
}
};
__webpack_require__.f.overridables = (chunkId, promises) => {}
Два: пульты
/* webpack/runtime/remotes loading */
var chunkMapping = {
"src_bootstrap_js": [
"?ad8d"
]
};
var idToExternalAndNameMapping = {
"?ad8d": [
"?8bfd",
"Button"
]
};
__webpack_require__.f.remotes = (chunkId, promises) => {}
Три: jsonp
/* webpack/runtime/jsonp chunk loading */
var installedChunks = {
"main": 0
};
__webpack_require__.f.j = (chunkId, promises) => {}
Я выделил основные части этих трех функций, на самом деле комментарии написаны более четко, поясню:
- overridables можно переопределить, глядя на код, который вы уже должны знать об общей конфигурации
- remotes является удаленным, из кода очень очевидно, что он связан с конфигурацией remotes
- jsonp Это исходная функция фрагмента загрузки, которая соответствует предыдущей ленивой загрузке или обычному извлечению кода.
3.3.4 Процесс загрузки
Узнав, что ядро реализовано в webpack_require.e и внутри, интересно, есть ли у вас в голове определенное представление обо всем процессе загрузки.Если нет, позвольте мне проанализировать его для вас
- Сначала загрузите src_main.js, это ни о чем не говорит, введено в html
- Выполнить webpack_require("./src/index.js") в src_main.js
- Логика модуля src/index.js очень проста: динамически загружать фрагмент src_bootstrap_js.
- При динамической загрузке чанка src_bootstrap_js, после overridables, обнаруживается, что чанк зависит от react и react-dom, тогда проверяем, загрузился ли он, если нет, загружаем соответствующий js файл, и адрес вам тоже говорят
- При динамической загрузке чанка src_bootstrap_js через пульты обнаруживается, что этот чанк зависит от ?ad8d, то загружаем этот js
- При динамической загрузке чанка src_bootstrap_js после jsonp он будет загружаться нормально
- После загрузки всех зависимостей и фрагментов выполните модуль then logic: в webpack_require src_bootstrap_js: ./src/bootstrap.js
В этот момент все начинается нормально.По сути, это предварительная зависимость, о которой мы упоминали ранее, сначала проанализируйте, затем сгенерируйте файл конфигурации, а затем загрузите его..
Похоже, все идет хорошо, но остается неразрешенной одна ключевая информация!
3.3.5 Как узнать о существовании app2
При загрузке реакции на шаге 4 выше, поскольку мы сами упаковали файл реакции, когда он не загружен, мы можем загрузить копию и узнать адрес
Но на пятом шаге, когда страница никогда не загружала app2/Button, куда мы идем, чтобы загрузить какой файл?
На данный момент мы будем использовать main.js, о котором упоминалось ранее.webpack_modules
охватывать
var __webpack_modules__ = ({
"container-reference/app2":
((module) => {
"use strict";
module.exports = app2;
}),
"?8bfd":
((module, __unused_webpack_exports, __webpack_require__) => {
"use strict";
var external = __webpack_require__("container-reference/app2");
module.exports = external;
})
});
Здесь три модуля, у нас тоже есть?8bfd, ссылка на контейнер/приложение2Не используется, давайте посмотрим на реализацию пультов
/* webpack/runtime/remotes loading */
var chunkMapping = {
"src_bootstrap_js": [
"?ad8d"
]
};
var idToExternalAndNameMapping = {
"?ad8d": [
"?8bfd",
"Button"
]
};
__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();
}
}))
});
}
}
Когда мы загружаем чанк src_bootstrap_js, после remotes мы обнаруживаем, что этот чанк зависит от ?ad8d, то во время выполнения:
id = "?8bfd"
data = [
"?8bfd",
"Button"
]
// 源码
__webpack_require__(data[0]).get(data[1])
// 运行时
__webpack_require__('?8bfd').get("Button")
В сочетании с кодом модуля ?8bfd из main.js это, наконец, app2.get("Button")
Это не глобальная переменная? Это выглядит немного странно!
3.3.6 Еще раз посмотрите на app2/remoteEntry.js
Мы как будто все время игнорировали этот файл.Он загружается первым,и у него должна быть своя функция.С некоторыми сомнениями по поводу глобального app2 мы просмотрели этот файл и нашли загадку!
var app2;
app2 =
(() => {
"use strict";
var __webpack_modules__ = ({
"?8619": ((__unused_webpack_module, exports, __webpack_require__) => {
var moduleMap = {
"Button": () => {
return __webpack_require__.e("src_Button_js").then(() => () => __webpack_require__( /*! ./src/Button */ "./src/Button.js"));
}
};
var get = (module) => {
return (
__webpack_require__.o(moduleMap, module) ?
moduleMap[module]() :
Promise.resolve().then(() => {
throw new Error("Module " + module + " does not exist in container.");
})
);
};
var override = (override) => {
Object.assign(__webpack_require__.O, override);
}
__webpack_require__.d(exports, {
get: () => get,
override: () => override
});
})
});
return __webpack_require__("?8619");
})()
Если вы посмотрите внимательно, вы обнаружите, что этот файл определяет глобальную переменную app2, а затем предоставляет функцию get, которая фактически загружает определенные модули.
Итак, app2.get("Button") становится здесь функцией get, определенной внутри app2, а затем выполняет собственный webpack_require.
Нет ощущения просветления!
Получается, что он строит радужный мост между двумя независимо упакованными приложениями через глобальные переменные!
Конечно, app2/remoteEntry.js упаковывается app2 в соответствии с конфигурацией, которая фактически основана на модуле экспорта файла конфигурации для создания соответствующего внутреннегоmodules
bootstrap.js вы можете не заметить
Если внимательные читатели обратят внимание, они обнаружат, что между входным файлом index.js и реальным файлом app.js есть bootstrap.js, а содержимое внутри — асинхронная загрузка app.js.
Является ли этот файл избыточным?Автор пробовал, и напрямую заменял запись на app.js или заменял синхронной загрузкой, и все приложение не могло запуститься.
На самом деле это можно понять из принципиального анализа:
Поскольку зависимости должны быть предварительно загружены, а их входные файлы могут быть выполнены только после загрузки зависимостей, если запись не превращена в асинхронный чанк, как можно реализовать такую препозицию зависимости? В конце концов, ядром реализации предварительной загрузки зависимостей является webpack_require.e.
3.3.7 Резюме
До сих пор я полагаю, что все поняли, как федерация модулей реализует две конфигурации общего и удаленного доступа, На самом деле, все еще невозможно избежать проблемы, упомянутой в конце второго раздела:
-
Как решить проблему с зависимостями, реализация здесь состоит в том, чтобы переписать webpack_require.e, который загружает куски, чтобы предварительно загрузить зависимости.
-
Как решить
modules
Проблема совместного использования, здесь использование глобальных переменных для перехвата
Общая реализация кажется довольно оригинальной.Я не являюсь основным разработчиком веб-пакета.По оценкам, я не могу придумать такое решение.На самом деле изменения довольно большие.
Достоинства и недостатки такой реализации также очевидны:
Преимущества: код можно загрузить во время выполнения, и общий код не нужно упаковывать вручную.
Недостатки: Зависимости от других приложений на самом деле сильные зависимости, то есть реализовано ли app2 по интерфейсу, вы не знаете
Что же касается пакета app2, упомянутого в некоторых других статьях в Интернете, то он должен использоваться в коде асинхронно.В этом можно убедиться, посмотрев предыдущую демку и зная принцип.Такого ограничения нет вообще!
0x4 Сводка
Для документов Tencent на самом деле больше нужна текущая общая возможность, которую можно решить после настройки общего доступа для некоторых распространенных общедоступных библиотек зависимостей, но это только в идеале, на самом деле все еще будут некоторые видимые проблемы.
- Идентификаторы публичных библиотек, сгенерированные разными версиями, различаются, что все равно приведет к повторной загрузке.
- Как получить последний адрес после обновления remoteEntry приложения2
- Как узнать, что другие приложения экспортируют интерфейс
Но, по крайней мере, это дает надежду на решение этой проблемы.Конфигурация удаленного доступа также позволяет нам увидеть возможность совместного использования кода с несколькими приложениями, так что это все равно заставит людей сиять в глазах, ожидающих официального выпуска webpack5!
Наконец, если что-то не так, поправьте, пожалуйста~
AlloyTeam приглашает отличных друзей присоединиться к нам.
Возобновление доставки:Alloyteam@qq.com
Нажми для деталейTencent AlloyTeam набирает веб-дизайнеров (социальный набор)