Чистая информация не является знанием. Истинный источник знаний – это опыт.
Приветствую всех читателей, просмотревших эту страницу. Возможно, как и я, вы не нашли нужной информации по этой теме, поэтому воспользуйтесь ею, ведь там будет вся информация, необходимая вам для корректной работы импорта во время выполнения!
Contents
Немного предыстории того, почему была написана эта статья
Погруженный в работу с Объединение модулей, столкнулся с такой проблемой, как отсутствие информации для продвинутых разработчиков. Большая часть информации, с которой я столкнулся, касалась таких технологий, как React или Angular. Но примеры видимый как такового я не нашел, только самые простые, которые, конечно же, не работают для продвинутых 🙂 Поэтому пришлось разбираться самому и после тысяч проб и ошибок я наконец выполнил эту задачу, и я готов рассказать вам, что такого волшебного стоит за динамическим импортом во Vue.
Кратко о федерации модулей
Объединение модулей — это подход к разработке приложений, введенный веб-стандартом, который позволяет разделить приложение на отдельные модули, которые можно разрабатывать, развертывать и подключать независимо друг от друга. Он позволяет комбинировать различные модули и приложения для создания масштабируемых и гибких архитектур.
Основные принципы Объединение модулей:
-
Независимость модуля: Каждый модуль представляет собой отдельное независимое приложение, которое можно разрабатывать и развертывать отдельно от других модулей.
-
Динамическая загрузка модулей: Модули можно динамически загружать и подключать во время выполнения. Это позволяет эффективно использовать ресурсы и снижает начальную нагрузку приложения.
-
Обмен данными и функциями: Модули могут взаимодействовать и предоставлять свои функции другим модулям. Это позволяет создавать гибкие и расширяемые приложения.
-
Управление зависимостями: Объединение модулей позволяет явно управлять зависимостями между модулями. Для каждого модуля можно указать модули и версии, необходимые для работы.
Объединение модулей особенно полезно в архитектуре микросервисов и распределенной среде разработки, где разные группы могут независимо разрабатывать и подключать свои модули к общему приложению.
В контексте разработки интерфейса модульная федерация стала широко использоваться в сочетании с такими инструментами, как Webpack, для создания масштабируемых и гибких архитектур микросервисов на стороне клиента.
Напишем Host и Remote приложения шаг за шагом
Идея заключается в следующем:
Хост — разделяет свой компонент Content, и он будет на порту 3002. Затем запустите приложение Remote, подождите, пока пользователь введет нужный порт во входных данных, затем загрузите компонент, если он существует. Выгода!
Немного конфигурации:
1) webpack.config.js – описывать в принципе нечего, базовая структура плагина федерации модулей
...
plugins: [
new MiniCssExtractPlugin({
filename: '[name].css',
}),
new ModuleFederationPlugin({
name: 'home',
filename: 'remoteEntry.js',
exposes: {
'./Content': './src/components/Content',
},
shared: {
vue: {
singleton: true,
},
},
}),
...
});
2) Content.view:
<template>
<div style="color: #d9c1e4;">{{ title }}</div>
</template>
<script>
export default {
data() {
return {
title: "Remote content component",
};
},
};
</script>
3) app.view
<template>
<main class="main">
<h3>Host App</h3>
<Content />
</main>
</template>
<script>
import { ref, defineAsyncComponent } from "vue";
export default {
components: {
Content: defineAsyncComponent(() => import("./components/Content")),
},
setup() {
const count = ref(0);
const inc = () => {
count.value++;
};
return {
count,
inc,
};
},
};
</script>
<style>
/* Немного стилей */
@import url('
img {
width: 200px;
}
h1 {
font-family: Arial, Helvetica, sans-serif;
}
html,body {
margin: 0;
}
h3 {
margin: 0;
color:#d9c1e4;
}
.main{
height: 100vh;
background: gray;
display: flex;
flex-direction: column;
justify-content: center;
font-family: 'Montserrat', sans-serif;
align-items: center;
color: #fff;
}
</style>
4) Параметры для package.json:
"scripts": {
"start": "webpack-cli serve",
"serve": "serve dist -p 3002",
"build": "webpack --mode production",
"clean": "rm -rf dist"
},
С этим наш хост готов к работе. Вам просто нужно выбрать порт 3002 и правильно его обработать.
Теперь конфигурация для удаленного приложения:
1) веб-пакет.config.js:
...
new ModuleFederationPlugin({
name: 'layout',
filename: 'remoteEntry.js',
exposes: {},
shared: {
vue: {
singleton: true,
},
},
}),
...
2) макет.вид. Здесь я разберу еще немного, т.к. этот компонент содержит ключевые функции для работы программы. Каков текущий алгоритм?
-
Есть запись с прикрепленной к ней переменной порт
-
Войдя в порт, мы можем нажать на кнопку, которая запускает функцию, которая захватывает то, что манифест на введенном порту
-
Пытаемся создать скрипт из этого манифеста и подключить его к нашему приложению
-
Как только скрипт загрузится, мы можем взять оттуда объект и прикрепить его к динамическому компоненту
3) пакет.json:
"scripts": {
"start": "webpack-cli serve",
"serve": "serve dist -p 3001",
"build": "webpack --mode production",
"clean": "rm -rf dist"
},
Переходим к коду:
Форма ввода выглядит так:
<div class="component">
<p style="font-size:22px; margin-bottom: 5px">Layout App</p>
<div class="form">
<label>Enter port for loading</label>
<input type="text" v-model="port">
<button @click="getRemoteComponent">Get remote component</button>
</div>
В принципе, осталось написать функцию getRemoteComponent И вот вы идете. Опишем тело функции:
// Для начала зададим конфигурацию для запроса
const uiApplication = {
protocol: 'http',
host: 'localhost',
port: this.port,
fileName: 'remoteEntry.js'
}
// Теперь построим ссылку
const remoteURL = `${uiApplication.protocol}://${uiApplication.host}:${uiApplication.port}/${uiApplication.fileName}`;
Далее нужно создать скрипт, который будет подключаться к приложению:
const moduleScope="home" // Переменные для дальнейшей конфигурации
const moduleName="Content"
const element = document.createElement('script');
element.type="text/javascript";
element.async = true;
element.src = remoteURL;
Если возникает ошибка, мы обработаем ее следующим образом:
element.onerror = () => {
alert(`Port ${this.port} doesn't have any content! Try another`)
}
Если скрипт успешно загрузился, мы можем его обработать, но для его написания потребуется дополнительная функция, которую можно найти в документации веб-пакет‘А:
async loadModule(scope, module) {
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;
}
Эта функция возвращает объект вида:
Невооруженным глазом видно, что это какая-то компонентная штука, но что с этим делать? Ответ прост — передайте этот объект динамическому компоненту Vue, и он волшебным образом соберет этот компонент!
Теперь вернемся к обработке скрипта:
element.onload = () => {
const remoteComponent = this.loadModule(moduleScope, `./${moduleName}`)
remoteComponent.then(res => {
console.log(res.default);
this.dynamicComponent = res.default;
})
};
document.head.appendChild(element);
Вот и все! Наша работа на этом закончена, осталось только присвоить этот объект компоненту следующим образом:
<div class="component">
<p style="font-size:22px; margin-bottom: 5px">Remote App</p>
<component :is="dynamicComponent"></component>
</div>
Так что проблема решена, можем радоваться 🙂
Окончательный код компонента layout.view:
<template>
<div class="main">
<div class="component">
<p style="font-size:22px; margin-bottom: 5px">Layout App</p>
<div class="form">
<label>Enter port for loading</label>
<input type="text" v-model="port">
<button @click="getRemoteComponent">Get remote component</button>
</div>
</div>
<div class="component">
<p style="font-size:22px; margin-bottom: 5px">Remote App</p>
<component :is="dynamicComponent"></component>
</div>
</div>
</template>
<script>
export default {
data() {
return {
port: null,
dynamicComponent: null
}
},
methods: {
getRemoteComponent() {
console.log(this.port, '<- Подгружаем по порту')
// Можно конфигурировать любые параметры динамически
const uiApplication = {
protocol: 'http',
host: 'localhost',
port: this.port,
fileName: 'remoteEntry.js'
}
const remoteURL = `${uiApplication.protocol}://${uiApplication.host}:${uiApplication.port}/${uiApplication.fileName}`;
console.log(remoteURL)
const moduleScope="home"
const moduleName="Content"
const element = document.createElement('script');
element.type="text/javascript";
element.async = true;
element.src = remoteURL;
element.onload = () => {
const remoteComponent = this.loadModule(moduleScope, `./${moduleName}`)
remoteComponent.then(res => {
console.log(res.default);
this.dynamicComponent = res.default;
})
};
element.onerror = () => {
alert(`Port ${this.port} doesn't have any content! Try another`)
}
document.head.appendChild(element);
},
async loadModule(scope, module) {
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;
}
}
};
</script>
<style>
@import url('
* {
font-family: 'Montserrat', sans-serif;
color:#fff;
}
body, p {
margin: 0;
}
.main {
height: 100vh;
display: flex;
background: gray;
}
.component {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
border: 2px solid #ffff;
padding: 5px;
border-radius: 10px;
width: 100%;
}
.form {
display: flex;
max-width: 300px;
flex-direction: column;
}
input {
margin: 10px 0;
color:black;
}
button {
color: black;
}
</style>
Результат такой (напомню, что хост раздает компонент на порт 3002):

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