arui-scripts 14.5.0-feat-modules.2 → 14.5.0-feat-modules.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +19 -0
- package/README.md +2 -0
- package/build/configs/app-configs/calculate-dependent-config.d.ts +2 -1
- package/build/configs/app-configs/get-defaults.js +2 -1
- package/build/configs/app-configs/types.d.ts +2 -1
- package/build/configs/dev-server.d.ts +2 -15
- package/build/configs/dev-server.js +11 -4
- package/build/configs/postcss.config.d.ts +1 -1
- package/build/configs/server-externals-exemptions.js +1 -1
- package/build/tsconfig-local.tsbuildinfo +1 -1
- package/docs/modules.md +265 -194
- package/package.json +1 -1
package/docs/modules.md
CHANGED
|
@@ -1,58 +1,29 @@
|
|
|
1
|
-
#
|
|
2
|
-
|
|
3
|
-
## Мотивация
|
|
1
|
+
# Что такое модули
|
|
4
2
|
Модули приложений предназначены для решения простой проблемы - переиспользование фронтового кода между приложениями.
|
|
5
3
|
|
|
6
4
|
В целом, для того чтобы переиспользовать код у нас есть много разных способов, например:
|
|
7
5
|
- копировать код из одного приложения в другое. Быстро, но неудобно и неэффективно.
|
|
8
6
|
- выносить код в отдельный пакет и подключать его через npm. Понятный и достаточно удобный вариант,
|
|
9
|
-
но ограничивает нас в скорости изменений. Если мы хотим заменить обновить код в пакете, процесс
|
|
10
|
-
раскатки этого обновления на все приложения может занять много времени.
|
|
7
|
+
но ограничивает нас в скорости изменений. Если мы хотим заменить обновить код в пакете, процесс
|
|
8
|
+
раскатки этого обновления на все приложения может занять много времени.
|
|
11
9
|
- Сделать так, чтобы мы могли подключать код из других приложений в свое. При этом мы получаем
|
|
12
|
-
возможность быстро изменить общий код только в одном месте, а все приложения автоматически получат
|
|
13
|
-
обновления.
|
|
10
|
+
возможность быстро изменить общий код только в одном месте, а все приложения автоматически получат
|
|
11
|
+
обновления.
|
|
14
12
|
|
|
15
13
|
Модули позволяют реализовать именно последний вариант. Если вы знакомы с концепцией [module federation](https://webpack.js.org/concepts/module-federation/),
|
|
16
14
|
то модули приложений - это его реализация в рамках arui-scripts, с дополнительным уровнем абстракции, который, в том числе,
|
|
17
15
|
позволяет использовать модули без самого module-federation.
|
|
18
16
|
|
|
17
|
+
## Общие принципы работы модулей
|
|
18
|
+
С точки зрения кода модуль представляет собой простой js-объект, который может быть _каким то образом_ подключен в другое приложение.
|
|
19
19
|
|
|
20
|
-
|
|
21
|
-
Модуль - это некоторая совокупность js и css файлов, которые можно подключить в приложение. У модуля есть
|
|
22
|
-
входная точка - js файл, код которого будет являться его публичным api.
|
|
23
|
-
|
|
24
|
-
Для подключения модулей в приложение предоставляется набор библиотек, которые позволяют указать, какой модуль
|
|
25
|
-
и куда надо подключить.
|
|
26
|
-
|
|
27
|
-
## Типы модулей
|
|
28
|
-
Несмотря на кажущуюся простоту концепции, можно представить разные сценарии использования модулей. В ряде случаев
|
|
29
|
-
мы хотим просто предоставить доступ к коду из другого приложения, в других - мы хотим иметь возможность
|
|
30
|
-
настраивать поведение модуля в зависимости от приложения, в котором он используется. Или мы хотим сделать так, чтобы
|
|
31
|
-
наш модуль мог сразу же получить какие-то данные с сервера, потому что его поведение зависит от этих данных.
|
|
32
|
-
|
|
33
|
-
Из-за того, что эти сценарии различаются, мы выделили два типа модулей:
|
|
34
|
-
|
|
35
|
-
- Клиентские модули. Это модули, которые имеют только клиентскую часть.
|
|
36
|
-
- Модули с серверной частью. Это модули, которые имеют как клиентскую, так и серверную часть. Серверная часть может
|
|
37
|
-
реализовывать какую-то логику, которая не может быть реализована на клиенте, например отдавать разные модули в зависимости
|
|
38
|
-
от пользователя, получать предподготовленные данные с сервера и т.д.
|
|
39
|
-
|
|
40
|
-
### Сравнение
|
|
41
|
-
|
|
42
|
-
Клиентские модули:
|
|
43
|
-
- Можно реализовать в любом приложении, даже если у него нет серверной части.
|
|
44
|
-
- Меньше кода, меньше проблем с поддержкой.
|
|
45
|
-
- Немного проще подключение модуля в приложение-хост.
|
|
46
|
-
|
|
47
|
-
Модули с серверной частью:
|
|
48
|
-
- Можно реализовать дополнительную логику, которая не может быть реализована на клиенте.
|
|
49
|
-
- Возможность изменить режим подключения модуля без изменений на приложениях-хостах.
|
|
20
|
+
`arui-scripts` предоставляет решение для сборки таких модулей, а также отдельную библиотеку для упрощения их подключения в другие приложения.
|
|
50
21
|
|
|
51
22
|
## Режимы подключения модулей
|
|
52
23
|
|
|
53
|
-
|
|
54
|
-
- `mf`
|
|
55
|
-
- `embedded`
|
|
24
|
+
В `arui-scripts` есть два способа сборки модулей:
|
|
25
|
+
- `mf` - Это модули, которые подключаются с помощью [webpack module federation](https://webpack.js.org/concepts/module-federation/).
|
|
26
|
+
- `embedded` - Это модули, которые подключаются просто добавлением нужных скриптов на страницу.
|
|
56
27
|
|
|
57
28
|
Основная проблема, которую решает ModuleFederation - это возможность не загружать на хост-приложение код библиотек уже подключенных в него.
|
|
58
29
|
Например, хост-приложение уже использует `react`, модуль так же написан на `react`. ModuleFederation дает нам легко "переиспользовать"
|
|
@@ -74,6 +45,43 @@
|
|
|
74
45
|
- **+++** Встроенная изоляция стилей. Стили модуля не будут применены к хост-приложению, если только вы не захотите этого.
|
|
75
46
|
- **---** Нет возможности использовать разные версии общих библиотек в разных модулях/хостах, если вы хотите их шарить.
|
|
76
47
|
|
|
48
|
+
## Возможность управления модулями с сервера
|
|
49
|
+
Сами модули используются только на клиентской части приложения. Но, в некоторых случаях, может быть полезно иметь возможность
|
|
50
|
+
управлять тем, какой модуль должен быть подключен на странице с сервера, или же иметь модуль, который будет при загрузке
|
|
51
|
+
иметь доступ к данным, доступным только на сервере (аналогично тому, как мы передаем серверный стейт в приложения при SSR).
|
|
52
|
+
|
|
53
|
+
Поэтому `arui-scripts` предоставляет возможность создать специальный эндпоинт на вашем сервере, из которого вы сможете управлять
|
|
54
|
+
состоянием модуля.
|
|
55
|
+
|
|
56
|
+
Модули с такой возможностью мы называем _серверными модулями_ (обычные модули мы называем _клиентскими_).
|
|
57
|
+
|
|
58
|
+
## Особые типы модулей
|
|
59
|
+
Несмотря на то, что сами по себе модули представляют собой простой js-объект, мы определяем один особый тип модулей - _монтируемые модули_.
|
|
60
|
+
|
|
61
|
+
### Монтируемые модули
|
|
62
|
+
Монтируемые модули - это модули, основное предназначение которых - отрендерить какой-то компонент внутри хост-приложения.
|
|
63
|
+
Монтируемые модули могут быть как клиентскими, так и серверными.
|
|
64
|
+
|
|
65
|
+
Такие модули должны экспортировать две функции:
|
|
66
|
+
|
|
67
|
+
```tsx
|
|
68
|
+
export function mount(targetNode, runParams, serverState): void {
|
|
69
|
+
// здесь происходит монтирование модуля в хост-приложение
|
|
70
|
+
// targetNode - это DOM-нода, в которую нужно отрендерить модуль
|
|
71
|
+
// runParams - это параметры, которые были переданы при запуске модуля
|
|
72
|
+
// serverState - это состояние, которое было передано с сервера
|
|
73
|
+
|
|
74
|
+
// Скорее всего это будет что-то вроде:
|
|
75
|
+
ReactDOM.render(<App preparedState={serverState} runParams={runParams} />, targetNode);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export function unmount(targetNode): void {
|
|
79
|
+
// здесь происходит демонтирование модуля из хост-приложения
|
|
80
|
+
// Скорее всего это будет что-то вроде:
|
|
81
|
+
ReactDOM.unmountComponentAtNode(targetNode);
|
|
82
|
+
}
|
|
83
|
+
```
|
|
84
|
+
|
|
77
85
|
## Как создать модуль
|
|
78
86
|
|
|
79
87
|
### Описать модуль в настройках arui-scripts
|
|
@@ -119,59 +127,103 @@ export default aruiScriptsConfig;
|
|
|
119
127
|
Все параметры конфигурации описаны [ниже](#Конфигурация-модулей).
|
|
120
128
|
|
|
121
129
|
### Создать модуль
|
|
122
|
-
Модуль является простым js/ts
|
|
130
|
+
Модуль является простым js/ts файлом. Он может использовать любой код вашего проекта, и любые библиотеки из node_modules.
|
|
123
131
|
|
|
124
|
-
|
|
132
|
+
В зависимости от режима подключения модуля, входная точка будет выглядеть по разному.
|
|
125
133
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
ModuleUnmountFunction,
|
|
130
|
-
WindowWithMountableModule
|
|
131
|
-
} from '@alfalab/scripts-modules';
|
|
132
|
-
|
|
133
|
-
const mountModule: ModuleMountFunction = (moduleId, params, targetNode) => {
|
|
134
|
-
// здесь мы можем отрендерить наш модуль в targetNode
|
|
135
|
-
// например:
|
|
136
|
-
ReactDOM.render(<App />, targetNode);
|
|
137
|
-
};
|
|
134
|
+
#### Embedded модуль
|
|
135
|
+
Входная точка embedded модуля должна писать в глобальную переменную `window` объект с ключом `{НазваниеМодуля}`.
|
|
136
|
+
Все поля этого объекта по сути и будут являться модулем, ваши потребители смогут использовать их.
|
|
138
137
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
138
|
+
```ts
|
|
139
|
+
// src/modules/client-module-embedded/index.ts
|
|
140
|
+
|
|
141
|
+
window.ClientModuleEmbedded = {
|
|
142
|
+
doSomething: () => {
|
|
143
|
+
console.log('Hello from embedded module!');
|
|
144
|
+
},
|
|
145
|
+
publicConstant: 3.14,
|
|
146
|
+
// ...
|
|
143
147
|
};
|
|
148
|
+
```
|
|
144
149
|
|
|
150
|
+
<details>
|
|
151
|
+
<summary>Писать в window? Вы что, с дуба рухнулись?</summary>
|
|
152
|
+
Да, конечно, это может создать определенные проблемы (конфликты имен модулей, определенные ограничения на используемые названия),
|
|
153
|
+
но по сути это единственный способ передать код модуля в хост-приложение.
|
|
145
154
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
155
|
+
Webpack module federation делает абсолютно то же самое, просто прячет работу с глобальными переменными за собой.
|
|
156
|
+
</details>
|
|
157
|
+
|
|
158
|
+
#### MF модуль
|
|
159
|
+
Входная точка MF модуля должна экспортировать все поля модуля через `export`.
|
|
160
|
+
|
|
161
|
+
```ts
|
|
162
|
+
// src/modules/client-module-mf/index.ts
|
|
163
|
+
|
|
164
|
+
export const doSomething = () => {
|
|
165
|
+
console.log('Hello from mf module!');
|
|
150
166
|
};
|
|
167
|
+
|
|
168
|
+
export const publicConstant = 3.14;
|
|
151
169
|
```
|
|
152
170
|
|
|
153
|
-
|
|
154
|
-
работает как то иначе? По сути мы просто воспроизводим то же самое поведение, только в более ручном режиме.
|
|
171
|
+
#### Создание модулей предопределенного типа
|
|
155
172
|
|
|
156
|
-
|
|
157
|
-
экспортировать функции `mount` и `unmount`:
|
|
173
|
+
**Монтируемый модуль, mf**
|
|
158
174
|
|
|
159
175
|
```tsx
|
|
176
|
+
// src/modules/client-module-mf/index.ts
|
|
177
|
+
|
|
178
|
+
import React from 'react';
|
|
179
|
+
import ReactDOM from 'react-dom';
|
|
160
180
|
import type { ModuleMountFunction, ModuleUnmountFunction } from '@alfalab/scripts-modules';
|
|
181
|
+
import { ClientModuleMf } from './ClientModuleMf';
|
|
161
182
|
|
|
162
|
-
export const mount: ModuleMountFunction = (
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
}
|
|
183
|
+
export const mount: ModuleMountFunction<any, any> = (targetNode, runParams, serverState) => {
|
|
184
|
+
console.log('ClientModuleMf: mount', { runParams, serverState });
|
|
185
|
+
if (!targetNode) {
|
|
186
|
+
throw new Error(`Target node is not defined for module`);
|
|
187
|
+
}
|
|
167
188
|
|
|
189
|
+
ReactDOM.render(<ClientModuleMf />, targetNode);
|
|
190
|
+
};
|
|
168
191
|
export const unmount: ModuleUnmountFunction = (targetNode) => {
|
|
169
|
-
|
|
170
|
-
|
|
192
|
+
console.log('ClientModuleMf: unmount');
|
|
193
|
+
if (!targetNode) {
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
ReactDOM.unmountComponentAtNode(targetNode);
|
|
198
|
+
};
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
**Монтируемый модуль, embedded**
|
|
202
|
+
|
|
203
|
+
```tsx
|
|
204
|
+
// src/modules/client-module-embedded/index.ts
|
|
205
|
+
import React from 'react';
|
|
206
|
+
import ReactDOM from 'react-dom';
|
|
207
|
+
import type { ModuleMountFunction, ModuleUnmountFunction, WindowWithMountableModule } from '@alfalab/scripts-modules';
|
|
208
|
+
import { ClientModuleMf } from './ClientModuleMf';
|
|
209
|
+
|
|
210
|
+
const mount: ModuleMountFunction<any, any> = (targetNode, runParams, serverState) => {
|
|
211
|
+
console.log('ClientModuleEmbedded: mount', { runParams, serverState });
|
|
212
|
+
ReactDOM.render(<ClientModuleMf />, targetNode);
|
|
213
|
+
};
|
|
214
|
+
const unmount: ModuleUnmountFunction = (targetNode) => {
|
|
215
|
+
console.log('ClientModuleEmbedded: unmount');
|
|
216
|
+
|
|
171
217
|
ReactDOM.unmountComponentAtNode(targetNode);
|
|
172
218
|
};
|
|
219
|
+
|
|
220
|
+
(window as WindowWithMountableModule).ClientModuleEmbedded = {
|
|
221
|
+
mount: mountModule,
|
|
222
|
+
unmount: unmountModule,
|
|
223
|
+
};
|
|
173
224
|
```
|
|
174
225
|
|
|
226
|
+
|
|
175
227
|
### (Опционально) Определить серверный эндпоинт для модуля
|
|
176
228
|
Если вы хотите, чтобы ваш модуль имел серверную часть, которая сможет подготовить данные для модуля, то вам необходимо
|
|
177
229
|
определить серверный эндпоинт для модуля. Для этого вам нужно определить объект, описывающий ваши модули:
|
|
@@ -185,7 +237,8 @@ const modules: ModulesConfig = {
|
|
|
185
237
|
version: '1.0.0',
|
|
186
238
|
getRunParams: async (getResourcesRequest) => ({
|
|
187
239
|
// getResouresRequest - это объект, который будет передан из хост-приложения
|
|
188
|
-
|
|
240
|
+
|
|
241
|
+
// данные, которые вернет эта будут доступны при инициализации модуля
|
|
189
242
|
paramFromServer: 'This can be any data from server',
|
|
190
243
|
asyncData: 'It can be constructed from async data, so you may perform some service calls here',
|
|
191
244
|
contextRoot: 'http://localhost:8081',
|
|
@@ -203,6 +256,8 @@ const modules: ModulesConfig = {
|
|
|
203
256
|
};
|
|
204
257
|
```
|
|
205
258
|
|
|
259
|
+
Подробнее о `getResourcesRequest` и `getRunParams` рассказано в разделе [Подключение модулей](#Подключение-модулей).
|
|
260
|
+
|
|
206
261
|
Далее, в зависимости от того, какой серверный фреймворк вы используете, вам нужно будет подключить ваши модули в
|
|
207
262
|
соответствующий хендлер. Например, для express это будет выглядеть так:
|
|
208
263
|
|
|
@@ -214,7 +269,7 @@ const modulesRouter = createGetModulesExpress(modules);
|
|
|
214
269
|
app.use(modulesRouter);
|
|
215
270
|
```
|
|
216
271
|
|
|
217
|
-
Для hapi@16
|
|
272
|
+
Для `hapi@16`:
|
|
218
273
|
|
|
219
274
|
```ts
|
|
220
275
|
import { createGetModulesHapi16Plugin } from '@alfalab/scripts-server/build/hapi16';
|
|
@@ -224,7 +279,7 @@ const modulesPlugin = createGetModulesHapi16Plugin(modules);
|
|
|
224
279
|
server.register(modulesPlugin);
|
|
225
280
|
```
|
|
226
281
|
|
|
227
|
-
Для hapi@20
|
|
282
|
+
Для `hapi@20`:
|
|
228
283
|
|
|
229
284
|
```ts
|
|
230
285
|
import { createGetModulesHapi20Plugin } from '@alfalab/scripts-server/build/hapi20';
|
|
@@ -265,159 +320,175 @@ type ModulesMethod = {
|
|
|
265
320
|
|
|
266
321
|
Если ваше react-приложение использует порталы, вам так же надо не забыть добавить префикс к элементу-порталу.
|
|
267
322
|
|
|
323
|
+
**Важно** - изоляция стилей работает только в одном направлении - стили модуля не будут применены к элементам
|
|
324
|
+
хост-приложения. Но стили хост-приложения могут быть применены к элементам модуля.
|
|
325
|
+
|
|
268
326
|
#### MF модули
|
|
269
327
|
Никакого встроенного механизма изоляции стилей для MF модулей нет. Если хост-приложение и модуль используют css-modules,
|
|
270
328
|
то конфликтов возникнуть не должно. Если же это не так - вы можете попробовать решить эту проблему используя shadow-dom.
|
|
271
329
|
|
|
272
330
|
### Тестирование модулей
|
|
331
|
+
Поскольку в общем случае модули представляют собой простой js код - для тестирования вы можете пользоваться любыми привычными вам инструментами.
|
|
273
332
|
|
|
274
|
-
|
|
333
|
+
Для тестирования модулей в cypress или playwright вы можете создать отдельный эндпоинт в вашем приложении, который будет
|
|
334
|
+
подключать модуль в ваше же приложение.
|
|
275
335
|
|
|
276
336
|
|
|
277
|
-
|
|
337
|
+
# Подключение модулей
|
|
278
338
|
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
339
|
+
## Создание загрузчика
|
|
340
|
+
Базовый способ подключение модулей - это использование `createModuleLoader` из `@alfalab/scripts-modules`. Этот метод
|
|
341
|
+
вернет вам функцию, которая позволит подключить модуль в ваше приложение.
|
|
282
342
|
|
|
283
343
|
```ts
|
|
284
|
-
import {
|
|
344
|
+
import { createModuleLoader } from '@alfalab/scripts-modules';
|
|
345
|
+
|
|
346
|
+
const loader = createModuleLoader({
|
|
347
|
+
moduleId: 'test', // id модуля, который вы хотите подключить
|
|
348
|
+
// функция, которая должна вернуть описание модуля.
|
|
349
|
+
getModuleResources: async ({ moduleId, hostAppId, params }) => ({
|
|
350
|
+
scripts: ['http://localhost:8081/static/js/main.js'], // скрипты модуля
|
|
351
|
+
styles: ['http://localhost:8081/static/css/main.css'], // стили модуля
|
|
352
|
+
moduleVersion: '1.0.0', // версия модуля
|
|
353
|
+
appName: 'moduleSourceAppName', // имя приложения, которое является источником модуля
|
|
354
|
+
mountMode: 'embedded', // режим монтирования модуля
|
|
355
|
+
moduleRunParams: { // параметры, которые будут доступны при инициализации модуля
|
|
356
|
+
baseUrl: 'http://localhost:8081',
|
|
357
|
+
},
|
|
358
|
+
}),
|
|
359
|
+
});
|
|
360
|
+
```
|
|
285
361
|
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
'react-dom': {
|
|
301
|
-
eager: true,
|
|
302
|
-
singleton: true,
|
|
303
|
-
requiredVersion: '^17.0.0',
|
|
304
|
-
}
|
|
305
|
-
}
|
|
306
|
-
},
|
|
307
|
-
}
|
|
362
|
+
Вам вовсе не обязательно руками описывать функцию `getModuleResources`. В зависимости от типа модуля, вы можете
|
|
363
|
+
использовать один из готовых хелперов:
|
|
364
|
+
|
|
365
|
+
Для клиентских модулей:
|
|
366
|
+
```ts
|
|
367
|
+
import { createModuleLoader, createClientResourcesFetcher } from '@alfalab/scripts-modules';
|
|
368
|
+
|
|
369
|
+
const loader = createModuleLoader({
|
|
370
|
+
moduleId: 'test',
|
|
371
|
+
getModuleResources: createClientResourcesFetcher({
|
|
372
|
+
baseUrl: 'http://localhost:8081',
|
|
373
|
+
mountMode: 'mf',
|
|
374
|
+
}),
|
|
375
|
+
});
|
|
308
376
|
```
|
|
309
377
|
|
|
310
|
-
|
|
311
|
-
Клиентские и серверные модули подключаются на страницу немного по-разному.
|
|
378
|
+
`createClientResourcesFetcher` сам сделает запрос за манифестом приложения, и правильным образом сформирует описание модуля.
|
|
312
379
|
|
|
313
|
-
|
|
380
|
+
Для серверных модулей:
|
|
381
|
+
```ts
|
|
382
|
+
import { createModuleLoader, createServerResourcesFetcher } from '@alfalab/scripts-modules';
|
|
383
|
+
|
|
384
|
+
const loader = createModuleLoader({
|
|
385
|
+
moduleId: 'test',
|
|
386
|
+
getModuleResources: createServerResourcesFetcher({
|
|
387
|
+
baseUrl: 'http://localhost:8081',
|
|
388
|
+
headers: { 'X-Auth': 'bla-bla' } // опционально вы можете передать дополнительные заголовки для запроса
|
|
389
|
+
}),
|
|
390
|
+
});
|
|
391
|
+
```
|
|
314
392
|
|
|
315
|
-
|
|
316
|
-
import React, { useMemo } from 'react';
|
|
317
|
-
import { createLoader, useModuleLoader, getModuleResourcesPath } from '@alfalab/scripts-modules';
|
|
318
|
-
|
|
319
|
-
// Это просто функция, которая должна обратиться к серверу модуля.
|
|
320
|
-
// Скорее всего у вас уже есть хелпер, который создает подобные функции
|
|
321
|
-
// и насыщает запрос дополнительными данными, например, токеном авторизации, traceId и т.д.
|
|
322
|
-
const customFetch = (loaderParams) => {
|
|
323
|
-
// arui-scripts не знает заранее ни дополнительных параметров (авторизация, заголовки), которые вы хотите передать в запрос,
|
|
324
|
-
// ни того, на какой адрес нужно делать запрос. Поэтому вам нужно самим реализовать эту функцию.
|
|
325
|
-
return fetch(`http://localhost:8081/${getModuleResourcesPath}`, {
|
|
326
|
-
method: 'POST',
|
|
327
|
-
body: JSON.stringify(loaderParams),
|
|
328
|
-
headers: {
|
|
329
|
-
'Content-Type': 'application/json',
|
|
330
|
-
},
|
|
331
|
-
}).then((response) => response.json());
|
|
332
|
-
}
|
|
393
|
+
`createServerResourcesFetcher` сам сделает запрос к ручке, которая отдает описание модуля.
|
|
333
394
|
|
|
334
|
-
|
|
335
|
-
// Загрузчик - это функция, которая прячет в себе запрос к серверу, подключение ресурсов на страницу и т.д.
|
|
336
|
-
const loader = useMemo(() => createLoader({
|
|
337
|
-
hostAppId: 'example', // id вашего хост-приложения
|
|
338
|
-
fetchFunction: customFetch,
|
|
339
|
-
// С помощью этой функции вы можете передать дополнительные параметры в запрос к серверу модуля.
|
|
340
|
-
getModuleRequestParams: async () => ({
|
|
341
|
-
paramName: 'some param that will be passed to module',
|
|
342
|
-
}),
|
|
343
|
-
}), []);
|
|
344
|
-
|
|
345
|
-
// useModuleLoader - это простой хук, который с помощью переданного загрузчика подключает модуль на страницу.
|
|
346
|
-
const {
|
|
347
|
-
loadingState, // состояние загрузки модуля. 'pending' - модуль еще не загружен, 'resolved' - модуль загружен, 'rejected' - произошла ошибка при загрузке модуля
|
|
348
|
-
targetElementRef, // ссылка на элемент, в который будет подключен модуль
|
|
349
|
-
} = useModuleLoader(
|
|
350
|
-
"ServerModuleEmbedded", // id модуля, который был указан в arui-scripts.config.ts
|
|
351
|
-
loader,
|
|
352
|
-
);
|
|
395
|
+
В случае же совсем кастомных требований, вы можете реализовать функцию `getModuleResources` самостоятельно.
|
|
353
396
|
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
{ loadingState === 'pending' && <div>Loading...</div> }
|
|
357
|
-
{ loadingState === 'rejected' && <div>Failed to load module</div> }
|
|
397
|
+
## Использование загрузчика
|
|
398
|
+
После того как вы создали `loader` - вы легко можете получить доступ к модулю:
|
|
358
399
|
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
};
|
|
400
|
+
```ts
|
|
401
|
+
const { module, unmount, moduleResources } = await loader({
|
|
402
|
+
getResourcesParams: {}, // параметры, которые будут переданы в getModuleResources
|
|
403
|
+
});
|
|
404
|
+
|
|
405
|
+
console.log(module); // модуль, который вы загрузили. Тут будут доступны всё, что было экспортировано из модуля
|
|
406
|
+
console.log(moduleResources); // полный ответ от getModuleResources
|
|
407
|
+
|
|
408
|
+
// вызов этой функции отмонтирует модуль из вашего приложения - удалит скрипты и стили модуля, а так же удалит
|
|
409
|
+
// все глобальные переменные, которые были определены в модуле.
|
|
410
|
+
unmount();
|
|
411
|
+
```
|
|
412
|
+
|
|
413
|
+
При вызове `loader` вы можете передать параметры, которые попадут в функцию `getModuleResources`. Это может быть полезно,
|
|
414
|
+
если вы хотите передать какие-то параметры на сервер модуля.
|
|
415
|
+
|
|
416
|
+
`getModuleResources` будет вызвана со следующими параметрами:
|
|
417
|
+
```ts
|
|
418
|
+
const getModuleResourcesParams = {
|
|
419
|
+
moduleId: 'test', // id модуля, который вы хотите подключить
|
|
420
|
+
hostAppId: 'hostAppId', // id вашего приложения
|
|
421
|
+
params: {}, // параметры, которые вы передали в loader как `getResourcesParams`
|
|
422
|
+
}
|
|
363
423
|
```
|
|
364
424
|
|
|
365
|
-
|
|
425
|
+
На сервер будут отправлены именно эти параметры, они будут доступны как параметр в `getRunParams` в описании вашего модуля.
|
|
426
|
+
|
|
427
|
+
При использовании клиентских модулей, вам не нужно беспокоиться о том, какие параметры вы передаете в `getModuleResources` - они
|
|
428
|
+
никак не используются в клиентских модулях.
|
|
429
|
+
|
|
430
|
+
## Использования загрузчика в реакт-приложении
|
|
431
|
+
|
|
432
|
+
Для того чтобы упростить работу с загрузчиком в реакт-приложении, мы предоставляем хук `useModuleLoader`:
|
|
366
433
|
|
|
367
434
|
```tsx
|
|
368
|
-
import
|
|
369
|
-
import { createClientLoader, useModuleLoader } from '@alfalab/scripts-modules';
|
|
370
|
-
import { Underlay } from '@alfalab/core-components/underlay';
|
|
371
|
-
import { Spinner } from '@alfalab/core-components/spinner';
|
|
435
|
+
import { createModuleLoader, useModuleLoader } from '@alfalab/scripts-modules';
|
|
372
436
|
|
|
373
|
-
const loader =
|
|
374
|
-
|
|
437
|
+
const loader = createModuleLoader({
|
|
438
|
+
moduleId: 'test',
|
|
439
|
+
getModuleResources: createClientResourcesFetcher({
|
|
440
|
+
baseUrl: 'http://localhost:8081',
|
|
441
|
+
mountMode: 'mf',
|
|
442
|
+
}),
|
|
375
443
|
});
|
|
376
444
|
|
|
377
|
-
|
|
378
|
-
const { loadingState,
|
|
379
|
-
"ClientModuleEmbedded", // id модуля, который был указан в arui-scripts.config.ts
|
|
380
|
-
loader
|
|
381
|
-
);
|
|
445
|
+
const MyComponent = () => {
|
|
446
|
+
const { loadingState, module, resources } = useModuleLoader(loader); // вторым параметром можно передать параметры, которые будут переданы в getModuleResources
|
|
382
447
|
|
|
383
448
|
return (
|
|
384
449
|
<div>
|
|
385
|
-
{
|
|
386
|
-
{
|
|
387
|
-
|
|
388
|
-
|
|
450
|
+
{loadingState === 'loading' && <div>Loading...</div>}
|
|
451
|
+
{loadingState === 'error' && <div>Error</div>}
|
|
452
|
+
{loadingState === 'success' && (
|
|
453
|
+
<div>
|
|
454
|
+
<div>Module loaded</div>
|
|
455
|
+
<div>{module}</div> {/* модуль, который вы загрузили. Тут будет доступно всё, что было экспортировано из модуля */}
|
|
456
|
+
<div>{resources}</div>
|
|
457
|
+
</div>
|
|
458
|
+
)}
|
|
389
459
|
</div>
|
|
390
460
|
);
|
|
391
|
-
}
|
|
392
|
-
|
|
461
|
+
};
|
|
393
462
|
```
|
|
394
463
|
|
|
464
|
+
### Использование монтируемых модулей
|
|
395
465
|
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
## Embedded модули
|
|
466
|
+
Для работы с монтируемыми модулями так же есть готовый хук `useModuleMounter`:
|
|
399
467
|
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
- `embeddedModules.shared` - объект, описывающий библиотеки, которые приложение будет предоставлять модулям. Ключ объекта - название библиотеки,
|
|
403
|
-
значение - название переменной в window, в которую будет записана библиотека.
|
|
404
|
-
- `embeddedModules.exposes` - объект, описывающий embedded модули. Ключ объекта - id модуля, значение - объект с конфигурацией модуля.
|
|
405
|
-
- `embeddedModules.exposes[id].entry` - путь до точки входа модуля. Должен быть либо абсолютным, либо относительным от корня проекта.
|
|
406
|
-
- `embeddedModules.exposes[id].cssPrefix` - префикс для css классов модуля. По умолчанию все стили модуля будут префиксированы с `.module-{id модуля}`. Вы можете передать сюда
|
|
407
|
-
свой префикс, или `false` чтобы отключить префиксирование.
|
|
408
|
-
- `embeddedModules.exposes[id].embeddedConfig` - объект, описывающий какие библиотеки должны быть помечены для модуля как `external`. Ключ объекта - название библиотеки,
|
|
409
|
-
значение - название переменной в window, в которой модуль будет искать эту библиотеку.
|
|
410
|
-
|
|
411
|
-
## MF модули
|
|
468
|
+
```tsx
|
|
469
|
+
import { createModuleLoader, useModuleMounter } from '@alfalab/scripts-modules';
|
|
412
470
|
|
|
413
|
-
|
|
471
|
+
const loader = createModuleLoader({
|
|
472
|
+
moduleId: 'test',
|
|
473
|
+
getModuleResources: createClientResourcesFetcher({
|
|
474
|
+
baseUrl: 'http://localhost:8081',
|
|
475
|
+
mountMode: 'embedded',
|
|
476
|
+
}),
|
|
477
|
+
});
|
|
414
478
|
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
значение - точка входа модуля.
|
|
479
|
+
const MyComponent = () => {
|
|
480
|
+
const { loadingState, targetElementRef } = useModuleMounter({
|
|
481
|
+
loader,
|
|
482
|
+
loaderParams: {}, // параметры, которые будут переданы в getModuleResources, опционально
|
|
483
|
+
runParams: {}, // параметры, которые будут переданы в mount функцию модуля, опционально
|
|
484
|
+
});
|
|
422
485
|
|
|
423
|
-
|
|
486
|
+
return (
|
|
487
|
+
<div>
|
|
488
|
+
{loadingState === 'loading' && <div>Loading...</div>}
|
|
489
|
+
{loadingState === 'error' && <div>Error</div>}
|
|
490
|
+
<div ref={targetElementRef} /> {/* сюда будет монтироваться модуль */}
|
|
491
|
+
</div>
|
|
492
|
+
);
|
|
493
|
+
};
|
|
494
|
+
```
|