arui-scripts 15.4.0 → 15.5.0
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/.turbo/turbo-test.log +2 -2
- package/CHANGELOG.md +18 -0
- package/README.md +6 -1
- package/build/commands/bundle-analyze/index.js +6 -5
- package/build/configs/app-configs/calculate-dependent-config.d.ts +17 -0
- package/build/configs/app-configs/get-defaults.js +11 -2
- package/build/configs/app-configs/types.d.ts +22 -0
- package/build/configs/dev-server.d.ts +1 -15
- package/build/configs/dev-server.js +11 -4
- package/build/configs/modules.d.ts +16 -0
- package/build/configs/modules.js +101 -0
- package/build/configs/process-assets-plugin-output.js +14 -0
- package/build/configs/server-externals-exemptions.js +1 -0
- package/build/configs/util/find-loader.d.ts +2 -0
- package/build/configs/util/find-loader.js +23 -0
- package/build/configs/webpack.client.d.ts +1 -1
- package/build/configs/webpack.client.dev.d.ts +1 -1
- package/build/configs/webpack.client.js +28 -28
- package/build/configs/webpack.client.prod.d.ts +1 -1
- package/build/tsconfig-local.tsbuildinfo +1 -1
- package/docs/modules.md +572 -0
- package/package.json +7 -5
package/docs/modules.md
ADDED
|
@@ -0,0 +1,572 @@
|
|
|
1
|
+
# Что такое модули
|
|
2
|
+
Модули приложений предназначены для решения простой проблемы - переиспользование фронтового кода между приложениями.
|
|
3
|
+
|
|
4
|
+
В целом, для того чтобы переиспользовать код у нас есть много разных способов, например:
|
|
5
|
+
- копировать код из одного приложения в другое. Быстро, но неудобно и неэффективно.
|
|
6
|
+
- выносить код в отдельный пакет и подключать его через npm. Понятный и достаточно удобный вариант,
|
|
7
|
+
но ограничивает нас в скорости изменений. Если мы хотим заменить обновить код в пакете, процесс
|
|
8
|
+
раскатки этого обновления на все приложения может занять много времени.
|
|
9
|
+
- Сделать так, чтобы мы могли подключать код из других приложений в свое. При этом мы получаем
|
|
10
|
+
возможность быстро изменить общий код только в одном месте, а все приложения автоматически получат
|
|
11
|
+
обновления.
|
|
12
|
+
|
|
13
|
+
Модули позволяют реализовать именно последний вариант. Если вы знакомы с концепцией [module federation](https://webpack.js.org/concepts/module-federation/),
|
|
14
|
+
то модули приложений - это его реализация в рамках arui-scripts, с дополнительным уровнем абстракции, который, в том числе,
|
|
15
|
+
позволяет использовать модули без самого module-federation.
|
|
16
|
+
|
|
17
|
+
## Общие принципы работы модулей
|
|
18
|
+
С точки зрения кода модуль представляет собой простой js-объект, который может быть _каким то образом_ подключен в другое приложение.
|
|
19
|
+
|
|
20
|
+
`arui-scripts` предоставляет решение для сборки таких модулей, а также отдельную библиотеку для упрощения их подключения в другие приложения.
|
|
21
|
+
|
|
22
|
+
## Режимы подключения модулей
|
|
23
|
+
|
|
24
|
+
В `arui-scripts` есть два способа сборки модулей:
|
|
25
|
+
- `default` - Это стандартные модули, которые подключаются с помощью [webpack module federation](https://webpack.js.org/concepts/module-federation/).
|
|
26
|
+
- `compat` - Это модули, которые подключаются просто добавлением нужных скриптов на страницу.
|
|
27
|
+
|
|
28
|
+
Основная проблема, которую решает ModuleFederation - это возможность не загружать на хост-приложение код библиотек уже подключенных в него.
|
|
29
|
+
Например, хост-приложение уже использует `react`, модуль так же написан на `react`. ModuleFederation дает нам легко "переиспользовать"
|
|
30
|
+
уже загруженный в браузер код `react` в модуле, не загружая его еще раз.
|
|
31
|
+
|
|
32
|
+
### Сравнение
|
|
33
|
+
|
|
34
|
+
`default` модули:
|
|
35
|
+
- **+++** Простой способ для переиспользования библиотек между модулем и приложением-хостом.
|
|
36
|
+
- **+++** Возможность использовать разные версии общих библиотек в разных модулях/хостах (речь про те библиотеки, которые будут шарится).
|
|
37
|
+
- **---** Нет встроенной изоляции стилей. Стили модуля будут применены к хост-приложению.
|
|
38
|
+
- **---** Нет возможности использовать модуль в приложении, которое не использует webpack.
|
|
39
|
+
|
|
40
|
+
Проблема изоляции стилей может быть решена с помощью [shadow dom](https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_shadow_DOM),
|
|
41
|
+
или с помощью css modules. Но это накладывает некоторые ограничения либо на поддерживаемые браузеры (shadow dom), либо на
|
|
42
|
+
существующую кодовую базу (css modules должны использоваться везде, если у вас будет две версии arui-feather на странице - будет не очень приятно).
|
|
43
|
+
|
|
44
|
+
`compat` модули:
|
|
45
|
+
- **+++** Встроенная изоляция стилей. Стили модуля не будут применены к хост-приложению, если только вы не захотите этого.
|
|
46
|
+
- **---** Нет возможности использовать разные версии общих библиотек в разных модулях/хостах, если вы хотите их шарить.
|
|
47
|
+
|
|
48
|
+
*Как понять какой режим использовать?*
|
|
49
|
+
В целом, если ваше приложение и модули используют только css-modules, то можно использовать `default` режим. Конфликты в стилях
|
|
50
|
+
вам в таком случае не грозят. Если же вы используете обычный css, или ваши библиотеки используют обычный css, то лучше
|
|
51
|
+
использовать `compat` режим.
|
|
52
|
+
|
|
53
|
+
## Возможность управления модулями с сервера
|
|
54
|
+
Сами модули используются только на клиентской части приложения. Но, в некоторых случаях, может быть полезно иметь возможность
|
|
55
|
+
управлять тем, какой модуль должен быть подключен на странице с сервера, или же иметь модуль, который будет при загрузке
|
|
56
|
+
иметь доступ к данным, доступным только на сервере (аналогично тому, как мы передаем серверный стейт в приложения при SSR).
|
|
57
|
+
|
|
58
|
+
Поэтому `arui-scripts` предоставляет возможность создать специальный эндпоинт на вашем сервере, из которого вы сможете управлять
|
|
59
|
+
состоянием модуля.
|
|
60
|
+
|
|
61
|
+
Модули с такой возможностью мы называем _модулями с серверным состоянием_ (_server state_).
|
|
62
|
+
|
|
63
|
+
## Особые типы модулей
|
|
64
|
+
Несмотря на то, что сами по себе модули представляют собой простой js-объект, мы определяем один особый тип модулей - _монтируемые модули_.
|
|
65
|
+
|
|
66
|
+
### Монтируемые модули
|
|
67
|
+
Монтируемые модули - это модули, основное предназначение которых - отрендерить какой-то компонент внутри хост-приложения.
|
|
68
|
+
Монтируемые модули могут быть как клиентскими, так и серверными.
|
|
69
|
+
|
|
70
|
+
Такие модули должны экспортировать две функции:
|
|
71
|
+
|
|
72
|
+
```tsx
|
|
73
|
+
export function mount(targetNode, runParams, serverState): void {
|
|
74
|
+
// здесь происходит монтирование модуля в хост-приложение
|
|
75
|
+
// targetNode - это DOM-нода, в которую нужно отрендерить модуль
|
|
76
|
+
// runParams - это параметры, которые были переданы при запуске модуля
|
|
77
|
+
// serverState - это состояние, которое было передано с сервера
|
|
78
|
+
|
|
79
|
+
// Скорее всего это будет что-то вроде:
|
|
80
|
+
ReactDOM.render(<App preparedState={serverState} runParams={runParams} />, targetNode);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export function unmount(targetNode): void {
|
|
84
|
+
// здесь происходит демонтирование модуля из хост-приложения
|
|
85
|
+
// Скорее всего это будет что-то вроде:
|
|
86
|
+
ReactDOM.unmountComponentAtNode(targetNode);
|
|
87
|
+
}
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
## Как создать модуль
|
|
91
|
+
|
|
92
|
+
### Описать модуль в настройках arui-scripts
|
|
93
|
+
Для того чтобы ваше приложение начало предоставлять модули, вам необходимо добавить настройки в `arui-scripts.config.ts`:
|
|
94
|
+
|
|
95
|
+
```ts
|
|
96
|
+
import { PackageSettings } from 'arui-scripts';
|
|
97
|
+
|
|
98
|
+
const aruiScriptsConfig: PackageSettings = {
|
|
99
|
+
compatModules: {
|
|
100
|
+
exposes: {
|
|
101
|
+
'ClientModuleCompat': { // имя модуля, будет использоваться приложениями-потребителями
|
|
102
|
+
entry: './src/modules/module-compat/index', // точка входа модуля
|
|
103
|
+
},
|
|
104
|
+
'ServerStateModuleCompat': {
|
|
105
|
+
entry: './src/modules/server-state-module-compat/index',
|
|
106
|
+
// этот модуль будет ожидать на странице глобальные переменные react и reactDOM,
|
|
107
|
+
// он будет использовать их вместо библиотек из своего node_modules
|
|
108
|
+
compatConfig: {
|
|
109
|
+
react: 'react',
|
|
110
|
+
'react-dom': 'reactDOM',
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
},
|
|
115
|
+
modules: {
|
|
116
|
+
// модули тут смогут переиспользовать react и react-dom из хост-приложения,
|
|
117
|
+
// если хост приложение шарит эти библиотеки, и они совпадают по версии
|
|
118
|
+
shared: {
|
|
119
|
+
'react': '^17.0.0', // так же поддерживаются более сложные версии например { requiredVersion: '^17.0.0', singleton: true }
|
|
120
|
+
'react-dom': '^17.0.0',
|
|
121
|
+
},
|
|
122
|
+
exposes: {
|
|
123
|
+
'module': './src/modules/module/index',
|
|
124
|
+
'ServerStateModule': './src/modules/server-state-module/index',
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export default aruiScriptsConfig;
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
Все параметры конфигурации описаны [ниже](#Конфигурация-модулей).
|
|
133
|
+
|
|
134
|
+
### Создать модуль
|
|
135
|
+
Модуль является простым js/ts файлом. Он может использовать любой код вашего проекта, и любые библиотеки из node_modules.
|
|
136
|
+
|
|
137
|
+
В зависимости от режима подключения модуля, входная точка будет выглядеть по-разному.
|
|
138
|
+
|
|
139
|
+
#### Default модуль
|
|
140
|
+
Входная точка модуля должна экспортировать все поля модуля через `export`.
|
|
141
|
+
|
|
142
|
+
```ts
|
|
143
|
+
// src/modules/module/index.ts
|
|
144
|
+
|
|
145
|
+
export const doSomething = () => {
|
|
146
|
+
console.log('Hello from module!');
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
export const publicConstant = 3.14;
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
#### Compat модуль
|
|
153
|
+
Входная точка compat модуля должна писать в глобальную переменную `window` объект с ключом `{НазваниеМодуля}`.
|
|
154
|
+
Все поля этого объекта по сути и будут являться модулем, ваши потребители смогут использовать их.
|
|
155
|
+
|
|
156
|
+
```ts
|
|
157
|
+
// src/modules/module-compat/index.ts
|
|
158
|
+
|
|
159
|
+
window.ModuleCompat = {
|
|
160
|
+
doSomething: () => {
|
|
161
|
+
console.log('Hello from compat module!');
|
|
162
|
+
},
|
|
163
|
+
publicConstant: 3.14,
|
|
164
|
+
// ...
|
|
165
|
+
};
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
<details>
|
|
169
|
+
<summary>Писать в window? Вы что, с дуба рухнулись?</summary>
|
|
170
|
+
Да, конечно, это может создать определенные проблемы (конфликты имен модулей, определенные ограничения на используемые названия),
|
|
171
|
+
но по сути это единственный способ передать код модуля в хост-приложение.
|
|
172
|
+
|
|
173
|
+
Webpack module federation делает абсолютно то же самое, просто прячет работу с глобальными переменными за собой.
|
|
174
|
+
</details>
|
|
175
|
+
|
|
176
|
+
#### Создание модулей предопределенного типа
|
|
177
|
+
|
|
178
|
+
**Монтируемый модуль, default**
|
|
179
|
+
|
|
180
|
+
```tsx
|
|
181
|
+
// src/modules/module/index.ts
|
|
182
|
+
|
|
183
|
+
import React from 'react';
|
|
184
|
+
import ReactDOM from 'react-dom';
|
|
185
|
+
import type { ModuleMountFunction, ModuleUnmountFunction } from '@alfalab/scripts-modules';
|
|
186
|
+
import { Module } from './Module';
|
|
187
|
+
|
|
188
|
+
export const mount: ModuleMountFunction<any, any> = (targetNode, runParams, serverState) => {
|
|
189
|
+
console.log('Module: mount', { runParams, serverState });
|
|
190
|
+
if (!targetNode) {
|
|
191
|
+
throw new Error(`Target node is not defined for module`);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
ReactDOM.render(<Module />, targetNode);
|
|
195
|
+
};
|
|
196
|
+
export const unmount: ModuleUnmountFunction = (targetNode) => {
|
|
197
|
+
console.log('Module: unmount');
|
|
198
|
+
if (!targetNode) {
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
ReactDOM.unmountComponentAtNode(targetNode);
|
|
203
|
+
};
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
**Монтируемый модуль, compat**
|
|
207
|
+
|
|
208
|
+
```tsx
|
|
209
|
+
// src/modules/module-compat/index.ts
|
|
210
|
+
import React from 'react';
|
|
211
|
+
import ReactDOM from 'react-dom';
|
|
212
|
+
import type { ModuleMountFunction, ModuleUnmountFunction, WindowWithMountableModule } from '@alfalab/scripts-modules';
|
|
213
|
+
import { ModuleCompat } from './ModuleCompat';
|
|
214
|
+
|
|
215
|
+
const mount: ModuleMountFunction<any, any> = (targetNode, runParams, serverState) => {
|
|
216
|
+
console.log('ModuleCompat: mount', { runParams, serverState });
|
|
217
|
+
ReactDOM.render(<ModuleCompat />, targetNode);
|
|
218
|
+
};
|
|
219
|
+
const unmount: ModuleUnmountFunction = (targetNode) => {
|
|
220
|
+
console.log('ModuleCompat: unmount');
|
|
221
|
+
|
|
222
|
+
ReactDOM.unmountComponentAtNode(targetNode);
|
|
223
|
+
};
|
|
224
|
+
|
|
225
|
+
(window as WindowWithMountableModule).ModuleCompat = {
|
|
226
|
+
mount: mountModule,
|
|
227
|
+
unmount: unmountModule,
|
|
228
|
+
};
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
### (Опционально) Определить серверный эндпоинт для модуля
|
|
233
|
+
Если вы хотите, чтобы ваш модуль имел серверную часть, которая сможет подготовить данные для модуля, то вам необходимо
|
|
234
|
+
определить серверный эндпоинт для модуля. Для этого вам нужно определить объект, описывающий ваши модули:
|
|
235
|
+
|
|
236
|
+
```ts
|
|
237
|
+
import type { ModulesConfig } from '@alfalab/scripts-server';
|
|
238
|
+
|
|
239
|
+
const modules: ModulesConfig = {
|
|
240
|
+
'ServerStateModuleCompat': {
|
|
241
|
+
mountMode: 'compat',
|
|
242
|
+
version: '1.0.0',
|
|
243
|
+
getRunParams: async (getResourcesRequest) => ({
|
|
244
|
+
// getResouresRequest - это объект, который будет передан из хост-приложения
|
|
245
|
+
|
|
246
|
+
// данные, которые вернет эта будут доступны при инициализации модуля
|
|
247
|
+
paramFromServer: 'This can be any data from server',
|
|
248
|
+
asyncData: 'It can be constructed from async data, so you may perform some service calls here',
|
|
249
|
+
contextRoot: 'http://localhost:8081',
|
|
250
|
+
}),
|
|
251
|
+
},
|
|
252
|
+
'ServerModule': {
|
|
253
|
+
mountMode: 'default',
|
|
254
|
+
version: '1.0.0',
|
|
255
|
+
getRunParams: async () => ({
|
|
256
|
+
paramFromServer: 'This can be any data from server',
|
|
257
|
+
asyncData: 'It can be constructed from async data, so you may perform some service calls here',
|
|
258
|
+
contextRoot: 'http://localhost:8081',
|
|
259
|
+
}),
|
|
260
|
+
},
|
|
261
|
+
};
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
Подробнее о `getResourcesRequest` и `getRunParams` рассказано в разделе [Подключение модулей](#Подключение-модулей).
|
|
265
|
+
|
|
266
|
+
Далее, в зависимости от того, какой серверный фреймворк вы используете, вам нужно будет подключить ваши модули в
|
|
267
|
+
соответствующий хендлер. Например, для express это будет выглядеть так:
|
|
268
|
+
|
|
269
|
+
```ts
|
|
270
|
+
import { createGetModulesExpress } from '@alfalab/scripts-server/build/express';
|
|
271
|
+
|
|
272
|
+
const modulesRouter = createGetModulesExpress(modules);
|
|
273
|
+
|
|
274
|
+
app.use(modulesRouter);
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
Для `hapi@16`:
|
|
278
|
+
|
|
279
|
+
```ts
|
|
280
|
+
import { createGetModulesHapi16Plugin } from '@alfalab/scripts-server/build/hapi16';
|
|
281
|
+
|
|
282
|
+
const modulesPlugin = createGetModulesHapi16Plugin(modules);
|
|
283
|
+
|
|
284
|
+
server.register(modulesPlugin);
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
Для `hapi@20`:
|
|
288
|
+
|
|
289
|
+
```ts
|
|
290
|
+
import { createGetModulesHapi20Plugin } from '@alfalab/scripts-server/build/hapi20';
|
|
291
|
+
|
|
292
|
+
const modulesPlugin = createGetModulesHapi20Plugin(modules);
|
|
293
|
+
|
|
294
|
+
server.register(modulesPlugin);
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
Если вы хотите использовать другой серверный фреймворк, вы можете использовать общий хелпер:
|
|
298
|
+
|
|
299
|
+
```ts
|
|
300
|
+
import { createGetModulesMethod } from '@alfalab/scripts-server';
|
|
301
|
+
|
|
302
|
+
const getModules = createGetModulesMethod(modules);
|
|
303
|
+
|
|
304
|
+
// getModules будет иметь следующую сигнатуру:
|
|
305
|
+
type ModulesMethod = {
|
|
306
|
+
method: string; // http метод, который нужно использовать для обработки запроса
|
|
307
|
+
path: string; // путь, который нужно использовать для обработки запроса
|
|
308
|
+
handler: (request: GetResourcesRequest) => Promise<GetResourcesResponse>; // обработчик запроса
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// далее в зависимости от фреймворка вы можете использовать этот метод
|
|
312
|
+
// для конфигурации вашего сервера
|
|
313
|
+
// вы можете посмотреть примеры реализации тких методов для express, hapi@16 и hapi@20.
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
### (Опционально) Разобраться с изоляцией стилей
|
|
317
|
+
|
|
318
|
+
#### Compat модули
|
|
319
|
+
В случае с compat модулями, стили модуля будут применены только к элементам, которые находятся внутри элемента
|
|
320
|
+
с классом `module-{имя модуля}`. Это позволяет изолировать стили модуля от стилей хост-приложения.
|
|
321
|
+
|
|
322
|
+
Вашей ответственностью будет добавить к рут-элементу модуля класс .module-nameOfModule. Вы должны сделать это в самом верхнем компоненте/элементе вашего модуля.
|
|
323
|
+
|
|
324
|
+
Вы можете переопределить префикс для css классов модуля, в `arui-scripts.config.ts`, подробнее в [конфигурации модулей](#Конфигурация-модулей).
|
|
325
|
+
|
|
326
|
+
Если ваше react-приложение использует порталы, вам так же надо не забыть добавить префикс к элементу-порталу.
|
|
327
|
+
|
|
328
|
+
**Важно** - изоляция стилей работает только в одном направлении - стили модуля не будут применены к элементам
|
|
329
|
+
хост-приложения. Но стили хост-приложения могут быть применены к элементам модуля.
|
|
330
|
+
|
|
331
|
+
#### Стандартные модули
|
|
332
|
+
Никакого встроенного механизма изоляции стилей для стандартных модулей нет. Если хост-приложение и модуль используют css-modules,
|
|
333
|
+
то конфликтов возникнуть не должно. Если же это не так - вы можете попробовать решить эту проблему используя shadow-dom.
|
|
334
|
+
|
|
335
|
+
### Тестирование модулей
|
|
336
|
+
Поскольку в общем случае модули представляют собой простой js код - для тестирования вы можете пользоваться любыми привычными вам инструментами.
|
|
337
|
+
|
|
338
|
+
Для тестирования модулей в cypress или playwright вы можете создать отдельный эндпоинт в вашем приложении, который будет
|
|
339
|
+
подключать модуль в ваше же приложение.
|
|
340
|
+
|
|
341
|
+
|
|
342
|
+
# Подключение модулей
|
|
343
|
+
|
|
344
|
+
## Создание загрузчика
|
|
345
|
+
Базовый способ подключение модулей - это использование `createModuleLoader` из `@alfalab/scripts-modules`. Этот метод
|
|
346
|
+
вернет вам функцию, которая позволит подключить модуль в ваше приложение.
|
|
347
|
+
|
|
348
|
+
```ts
|
|
349
|
+
import { createModuleLoader } from '@alfalab/scripts-modules';
|
|
350
|
+
|
|
351
|
+
const loader = createModuleLoader({
|
|
352
|
+
hostAppId: 'my-app', // id вашего приложения, оно будет передаваться в серверную ручку модуля
|
|
353
|
+
moduleId: 'test', // id модуля, который вы хотите подключить
|
|
354
|
+
// функция, которая должна вернуть описание модуля.
|
|
355
|
+
getModuleResources: async ({ moduleId, hostAppId, params }) => ({
|
|
356
|
+
scripts: ['http://localhost:8081/static/js/main.js'], // скрипты модуля
|
|
357
|
+
styles: ['http://localhost:8081/static/css/main.css'], // стили модуля
|
|
358
|
+
moduleVersion: '1.0.0', // версия модуля
|
|
359
|
+
appName: 'moduleSourceAppName', // имя приложения, которое является источником модуля
|
|
360
|
+
mountMode: 'compat', // режим монтирования модуля
|
|
361
|
+
moduleRunParams: { // параметры, которые будут доступны при инициализации модуля
|
|
362
|
+
baseUrl: 'http://localhost:8081',
|
|
363
|
+
},
|
|
364
|
+
}),
|
|
365
|
+
});
|
|
366
|
+
```
|
|
367
|
+
|
|
368
|
+
Вам вовсе не обязательно руками описывать функцию `getModuleResources`. В зависимости от типа модуля, вы можете
|
|
369
|
+
использовать один из готовых хелперов:
|
|
370
|
+
|
|
371
|
+
Для модулей без серверного стейта:
|
|
372
|
+
```ts
|
|
373
|
+
import { createModuleLoader, createModuleFetcher } from '@alfalab/scripts-modules';
|
|
374
|
+
|
|
375
|
+
const loader = createModuleLoader({
|
|
376
|
+
hostAppId: 'my-app',
|
|
377
|
+
moduleId: 'test',
|
|
378
|
+
getModuleResources: createModuleFetcher({
|
|
379
|
+
baseUrl: 'http://localhost:8081',
|
|
380
|
+
}),
|
|
381
|
+
});
|
|
382
|
+
```
|
|
383
|
+
|
|
384
|
+
`createModuleFetcher` сам сделает запрос за манифестом приложения, и правильным образом сформирует описание модуля.
|
|
385
|
+
|
|
386
|
+
Для модулей с серверным стейтом:
|
|
387
|
+
```ts
|
|
388
|
+
import { createModuleLoader, createServerStateModuleFetcher } from '@alfalab/scripts-modules';
|
|
389
|
+
|
|
390
|
+
const loader = createModuleLoader({
|
|
391
|
+
hostAppId: 'my-app',
|
|
392
|
+
moduleId: 'test',
|
|
393
|
+
getModuleResources: createServerStateModuleFetcher({
|
|
394
|
+
baseUrl: 'http://localhost:8081',
|
|
395
|
+
headers: { 'X-Auth': 'bla-bla' } // опционально вы можете передать дополнительные заголовки для запроса
|
|
396
|
+
}),
|
|
397
|
+
});
|
|
398
|
+
```
|
|
399
|
+
|
|
400
|
+
`createServerStateModuleFetcher` сам сделает запрос к ручке, которая отдает описание модуля.
|
|
401
|
+
|
|
402
|
+
В случае же совсем кастомных требований, вы можете реализовать функцию `getModuleResources` самостоятельно.
|
|
403
|
+
|
|
404
|
+
## Использование загрузчика
|
|
405
|
+
После того как вы создали `loader` - вы легко можете получить доступ к модулю:
|
|
406
|
+
|
|
407
|
+
```ts
|
|
408
|
+
const { module, unmount, moduleResources } = await loader({
|
|
409
|
+
getResourcesParams: { foo: 'bar' }, // параметры, которые будут переданы в getModuleResources
|
|
410
|
+
});
|
|
411
|
+
|
|
412
|
+
console.log(module); // модуль, который вы загрузили. Тут будут доступны всё, что было экспортировано из модуля
|
|
413
|
+
console.log(moduleResources); // полный ответ от getModuleResources
|
|
414
|
+
|
|
415
|
+
// вызов этой функции отмонтирует модуль из вашего приложения - удалит скрипты и стили модуля, а так же удалит
|
|
416
|
+
// все глобальные переменные, которые были определены в модуле.
|
|
417
|
+
unmount();
|
|
418
|
+
```
|
|
419
|
+
|
|
420
|
+
При вызове `loader` вы можете передать параметры, которые попадут в функцию `getModuleResources`. Это может быть полезно,
|
|
421
|
+
если вы хотите передать какие-то параметры на сервер модуля.
|
|
422
|
+
|
|
423
|
+
`getModuleResources` будет вызвана со следующими параметрами:
|
|
424
|
+
```ts
|
|
425
|
+
const getModuleResourcesParams = {
|
|
426
|
+
moduleId: 'test', // id модуля, который вы хотите подключить
|
|
427
|
+
hostAppId: 'my-app', // id вашего приложения
|
|
428
|
+
params: { foo: 'bar' }, // параметры, которые вы передали в loader как `getResourcesParams`
|
|
429
|
+
}
|
|
430
|
+
```
|
|
431
|
+
|
|
432
|
+
При использовании `createServerStateModuleFetcher` именно эти данные будут отправлены на сервер и будут доступны в функции `getRunParams` модуля.
|
|
433
|
+
|
|
434
|
+
При использовании `createModuleFetcher` вам не нужно беспокоиться о том, какие параметры вы передаете в `getModuleResources` - они
|
|
435
|
+
никак не используются в клиентских модулях.
|
|
436
|
+
|
|
437
|
+
## Использования загрузчика в реакт-приложении
|
|
438
|
+
|
|
439
|
+
Для того чтобы упростить работу с загрузчиком в реакт-приложении, мы предоставляем хук `useModuleLoader`:
|
|
440
|
+
|
|
441
|
+
```tsx
|
|
442
|
+
import { createModuleLoader, useModuleLoader, createModuleFetcher } from '@alfalab/scripts-modules';
|
|
443
|
+
|
|
444
|
+
const loader = createModuleLoader({
|
|
445
|
+
moduleId: 'test',
|
|
446
|
+
getModuleResources: createModuleFetcher({
|
|
447
|
+
baseUrl: 'http://localhost:8081',
|
|
448
|
+
}),
|
|
449
|
+
});
|
|
450
|
+
|
|
451
|
+
const MyComponent = () => {
|
|
452
|
+
const { loadingState, module, resources } = useModuleLoader(loader); // вторым параметром можно передать параметры, которые будут переданы в getModuleResources
|
|
453
|
+
|
|
454
|
+
return (
|
|
455
|
+
<div>
|
|
456
|
+
{loadingState === 'loading' && <div>Loading...</div>}
|
|
457
|
+
{loadingState === 'error' && <div>Error</div>}
|
|
458
|
+
{loadingState === 'success' && (
|
|
459
|
+
<div>
|
|
460
|
+
<div>Module loaded</div>
|
|
461
|
+
<div>{module}</div> {/* модуль, который вы загрузили. Тут будет доступно всё, что было экспортировано из модуля */}
|
|
462
|
+
<div>{resources}</div>
|
|
463
|
+
</div>
|
|
464
|
+
)}
|
|
465
|
+
</div>
|
|
466
|
+
);
|
|
467
|
+
};
|
|
468
|
+
```
|
|
469
|
+
|
|
470
|
+
### Использование монтируемых модулей
|
|
471
|
+
|
|
472
|
+
Для работы с монтируемыми модулями так же есть готовый хук `useModuleMounter`:
|
|
473
|
+
|
|
474
|
+
```tsx
|
|
475
|
+
import { createModuleLoader, useModuleMounter, createModuleFetcher } from '@alfalab/scripts-modules';
|
|
476
|
+
|
|
477
|
+
const loader = createModuleLoader({
|
|
478
|
+
moduleId: 'test',
|
|
479
|
+
getModuleResources: createModuleFetcher({
|
|
480
|
+
baseUrl: 'http://localhost:8081',
|
|
481
|
+
}),
|
|
482
|
+
});
|
|
483
|
+
|
|
484
|
+
const MyComponent = () => {
|
|
485
|
+
const { loadingState, targetElementRef } = useModuleMounter({
|
|
486
|
+
loader,
|
|
487
|
+
loaderParams: {}, // параметры, которые будут переданы в getModuleResources, опционально
|
|
488
|
+
runParams: {}, // параметры, которые будут переданы в mount функцию модуля, опционально
|
|
489
|
+
});
|
|
490
|
+
|
|
491
|
+
return (
|
|
492
|
+
<div>
|
|
493
|
+
{loadingState === 'loading' && <div>Loading...</div>}
|
|
494
|
+
{loadingState === 'error' && <div>Error</div>}
|
|
495
|
+
<div ref={targetElementRef} /> {/* сюда будет монтироваться модуль */}
|
|
496
|
+
</div>
|
|
497
|
+
);
|
|
498
|
+
};
|
|
499
|
+
```
|
|
500
|
+
|
|
501
|
+
# Документация API
|
|
502
|
+
|
|
503
|
+
## Конфигурация модулей
|
|
504
|
+
Типы, которые используются для конфигурирования модулей в `arui-scripts.config.ts`:
|
|
505
|
+
|
|
506
|
+
### Modules
|
|
507
|
+
```ts
|
|
508
|
+
type Modules = {
|
|
509
|
+
name?: string; // имя приложения, которое будет исползоваться как имя module federation контейнера. По-умолчанию будет использовано имя из package.json (оно будет преобразовано в snake-case)
|
|
510
|
+
exposes?: { // модули, которые приложение будет предоставлять
|
|
511
|
+
[moduleId: string]: string; // moduleId - id модуля, value - путь до точки входа модуля
|
|
512
|
+
};
|
|
513
|
+
shared?: (string | SharedObject)[] | SharedObject; // конфигурация shared параметра для ModuleFederationPlugin
|
|
514
|
+
};
|
|
515
|
+
|
|
516
|
+
type SharedObject = {
|
|
517
|
+
[index: string]: string | SharedConfig;
|
|
518
|
+
};
|
|
519
|
+
|
|
520
|
+
type SharedConfig = {
|
|
521
|
+
eager?: boolean; // включить модуль в сборку приложения
|
|
522
|
+
import?: string | false; // путь до модуля, который будет предоставлен в share scope
|
|
523
|
+
packageName?: string; // имя пакета, из которого будет взята версия модуля
|
|
524
|
+
requiredVersion?: string | false; // версия модуля, которая будет использована для проверки версии модуля в share scope
|
|
525
|
+
shareKey?: string; // ключ, по которому будет искаться модуль в share scope
|
|
526
|
+
shareScope?: string; // имя share scope
|
|
527
|
+
singleton?: boolean; // использовать только одну версию модуля в share scope
|
|
528
|
+
strictVersion?: boolean; // использовать только версию модуля, которая указана в requiredVersion
|
|
529
|
+
version?: string | false; // версия модуля, которая будет использована для проверки версии модуля в share scope
|
|
530
|
+
}
|
|
531
|
+
```
|
|
532
|
+
|
|
533
|
+
### CompatModules
|
|
534
|
+
```ts
|
|
535
|
+
type CompatModules = {
|
|
536
|
+
shared?: { // библиотеки, которые будут доступны compat модулям при подключении в этом приложении
|
|
537
|
+
[libraryName: string]: string; // libraryName - имя библиотеки, которое вы указали в package.json, value - название глобальной переменной, в которой будет доступна библиотека
|
|
538
|
+
};
|
|
539
|
+
exposes?: { // модули, которые приложение будет предоставлять
|
|
540
|
+
[moduleId: string]: CompatModuleConfig;
|
|
541
|
+
};
|
|
542
|
+
};
|
|
543
|
+
|
|
544
|
+
type CompatModuleConfig = {
|
|
545
|
+
entry: string; // путь до точки входа модуля
|
|
546
|
+
externals?: Record<string, string>; // список библиотек, которые модуль будет пытаться получить из приложения, в которое он будет встроен. Аналогично CompatModules.shared
|
|
547
|
+
cssPrefix?: false | string; // опционально, префикс для css-классов модуля. По-умолчанию будет использовано имя модуля
|
|
548
|
+
};
|
|
549
|
+
```
|
|
550
|
+
|
|
551
|
+
## Конфигурация серверной части
|
|
552
|
+
|
|
553
|
+
### createGetModulesMethod
|
|
554
|
+
```ts
|
|
555
|
+
type ModulesConfig = {
|
|
556
|
+
[moduleId: string]: {
|
|
557
|
+
mountMode: 'compat' | 'default'; // режим монтирования модуля
|
|
558
|
+
version?: string; // версия модуля, по умолчанию 'unknown'
|
|
559
|
+
getModuleState: GetModuleStateMethod; // метод, который будет вызван для получения состояния модуля
|
|
560
|
+
};
|
|
561
|
+
};
|
|
562
|
+
|
|
563
|
+
type GetModuleStateMethod = (
|
|
564
|
+
getResourcesRequest: GetResourcesRequest,
|
|
565
|
+
) => Promise<any>;
|
|
566
|
+
|
|
567
|
+
type GetResourcesRequest<GetResourcesParams = void> = {
|
|
568
|
+
moduleId: string; // id загружаемого модуля
|
|
569
|
+
hostAppId: string; // id приложения-хоста
|
|
570
|
+
params: GetResourcesParams; // параметры, которые передаются в функцию получения ресурсов модуля
|
|
571
|
+
};
|
|
572
|
+
```
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "arui-scripts",
|
|
3
|
-
"version": "15.
|
|
3
|
+
"version": "15.5.0",
|
|
4
4
|
"main": "./build/index.js",
|
|
5
5
|
"typings": "./build/index.d.ts",
|
|
6
6
|
"license": "MPL-2.0",
|
|
@@ -57,9 +57,6 @@
|
|
|
57
57
|
"gzip-size": "5.1.1",
|
|
58
58
|
"image-minimizer-webpack-plugin": "^3.8.3",
|
|
59
59
|
"imagemin": "^8.0.1",
|
|
60
|
-
"imagemin-gifsicle": "^7.0.0",
|
|
61
|
-
"imagemin-mozjpeg": "^10.0.0",
|
|
62
|
-
"imagemin-optipng": "^8.0.0",
|
|
63
60
|
"imagemin-svgo": "^10.0.1",
|
|
64
61
|
"jest": "28.1.3",
|
|
65
62
|
"jest-snapshot-serializer-class-name-to-string": "1.0.0",
|
|
@@ -83,6 +80,7 @@
|
|
|
83
80
|
"postcss-mixins": "^9.0.0",
|
|
84
81
|
"postcss-nested": "^6.0.0",
|
|
85
82
|
"postcss-omit-import-tilde": "^1.0.0",
|
|
83
|
+
"postcss-prefix-selector": "^1.16.0",
|
|
86
84
|
"postcss-strip-units": "^2.0.0",
|
|
87
85
|
"postcss-url": "^10.0.0",
|
|
88
86
|
"react-dev-utils": "11.0.4",
|
|
@@ -104,7 +102,7 @@
|
|
|
104
102
|
"webpack": "^5.0.0",
|
|
105
103
|
"webpack-bundle-analyzer": "4.5.0",
|
|
106
104
|
"webpack-deduplication-plugin": "^0.0.8",
|
|
107
|
-
"webpack-dev-server": "
|
|
105
|
+
"webpack-dev-server": "4.15.1",
|
|
108
106
|
"webpack-manifest-plugin": "3.2.0",
|
|
109
107
|
"webpack-node-externals": "3.0.0"
|
|
110
108
|
},
|
|
@@ -125,6 +123,7 @@
|
|
|
125
123
|
"@types/lodash.merge": "^4.6.6",
|
|
126
124
|
"@types/mini-css-extract-plugin": "1.2.2",
|
|
127
125
|
"@types/node": "12",
|
|
126
|
+
"@types/postcss-prefix-selector": "^1.15.0",
|
|
128
127
|
"@types/react-dev-utils": "9.0.8",
|
|
129
128
|
"@types/semver": "^7.3.13",
|
|
130
129
|
"@types/shelljs": "^0.8.11",
|
|
@@ -153,6 +152,9 @@
|
|
|
153
152
|
"test": "jest"
|
|
154
153
|
},
|
|
155
154
|
"peerDependencies": {
|
|
155
|
+
"imagemin-gifsicle": "^7.0.0",
|
|
156
|
+
"imagemin-mozjpeg": "^10.0.0",
|
|
157
|
+
"imagemin-optipng": "^8.0.0",
|
|
156
158
|
"react": ">=16.3.0",
|
|
157
159
|
"react-dom": ">=16.3.0",
|
|
158
160
|
"typescript": ">=4.0.0"
|