arui-scripts 15.8.1 → 15.9.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/.eslintrc.js +2 -0
- package/.turbo/turbo-lint.log +22 -14
- package/.turbo/turbo-test.log +9 -4
- package/CHANGELOG.md +12 -0
- package/build/configs/util/apply-overrides.d.ts +8 -0
- package/build/configs/util/find-plugin.d.ts +83 -0
- package/build/configs/util/find-plugin.js +23 -0
- package/build/configs/webpack.client.dev.js +2 -0
- package/build/configs/webpack.client.prod.js +2 -0
- package/build/configs/webpack.server.dev.js +2 -1
- package/build/configs/webpack.server.prod.js +2 -1
- package/build/tsconfig-local.tsbuildinfo +1 -1
- package/docs/modules.md +429 -358
- package/docs/overrides.md +59 -0
- package/package.json +2 -2
package/docs/modules.md
CHANGED
|
@@ -14,306 +14,248 @@
|
|
|
14
14
|
то модули приложений - это его реализация в рамках arui-scripts, с дополнительным уровнем абстракции, который, в том числе,
|
|
15
15
|
позволяет использовать модули без самого module-federation.
|
|
16
16
|
|
|
17
|
-
|
|
18
|
-
С точки зрения кода модуль представляет собой простой js-объект, который может быть _каким то образом_ подключен в другое приложение.
|
|
17
|
+
# Как использовать
|
|
19
18
|
|
|
20
|
-
|
|
19
|
+
Предположим у вас есть два приложения, `foo-app` и `bar-app`. Вы хотите предоставлять модуль из `foo-app` и использовать
|
|
20
|
+
его в `bar-app`.
|
|
21
21
|
|
|
22
|
-
##
|
|
22
|
+
## 1. Добавляем конфигурацию в arui-scripts.config.ts (в foo-app)
|
|
23
23
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
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
|
-
состоянием модуля.
|
|
24
|
+
```ts
|
|
25
|
+
// ./arui-scripts.config.ts
|
|
26
|
+
import type { PackageSettings } from 'arui-scripts';
|
|
60
27
|
|
|
61
|
-
|
|
28
|
+
const aruiScriptsConfig: PackageSettings = {
|
|
29
|
+
modules: {
|
|
30
|
+
// если хост приложение шарит эти библиотеки, и они совпадают по версии
|
|
31
|
+
shared: {
|
|
32
|
+
'react': '^17.0.0', // так же поддерживаются более сложные версии например { requiredVersion: '^17.0.0', singleton: true }
|
|
33
|
+
'react-dom': '^17.0.0',
|
|
34
|
+
},
|
|
35
|
+
exposes: {
|
|
36
|
+
'SomeModule': './src/modules/some-module/index',
|
|
37
|
+
'AnotherModule': './src/modules/another-module/index',
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
62
41
|
|
|
63
|
-
|
|
64
|
-
|
|
42
|
+
export default aruiScriptsConfig;
|
|
43
|
+
```
|
|
65
44
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
Монтируемые модули могут быть как клиентскими, так и серверными.
|
|
45
|
+
Эта конфигурация объявляет два модуля, `SomeModule` и `AnotherModule`. Эти модули смогут получать react и react-dom из
|
|
46
|
+
подключившего их приложения, если оно содержит конфигурацию для `modules.shared`, и версии библиотек подходят по semver.
|
|
69
47
|
|
|
70
|
-
|
|
48
|
+
## 2. Создаем входную точку модуля (в foo-app)
|
|
71
49
|
|
|
72
50
|
```tsx
|
|
73
|
-
|
|
51
|
+
// src/modules/some-module/index
|
|
52
|
+
import type { ModuleMountFunction, ModuleUnmountFunction } from '@alfalab/scripts-modules';
|
|
74
53
|
export const mount: ModuleMountFunction = (targetNode, runParams, serverState) => {
|
|
75
|
-
//
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
54
|
+
console.log( // Вы можете передать эти переменные в ваши компоненты, подробнее ниже
|
|
55
|
+
runParams, // undefined
|
|
56
|
+
serverState // { baseUrl: "https://example.com/foo-app", hostAppId: "bar-app" } // Эти данные определяются тем, что было передано в загрузчик в приложении-хосте
|
|
57
|
+
);
|
|
79
58
|
|
|
80
|
-
|
|
81
|
-
|
|
59
|
+
ReactDOM.render(
|
|
60
|
+
<div>Hello from module!</div>,
|
|
61
|
+
targetNode,
|
|
62
|
+
);
|
|
82
63
|
}
|
|
83
64
|
|
|
84
65
|
export const unmount: ModuleUnmountFunction = (targetNode) => {
|
|
85
|
-
// здесь происходит демонтирование модуля из хост-приложения
|
|
86
|
-
// Скорее всего это будет что-то вроде:
|
|
87
66
|
ReactDOM.unmountComponentAtNode(targetNode);
|
|
88
67
|
}
|
|
89
68
|
```
|
|
90
69
|
|
|
91
|
-
|
|
92
|
-
Модули-фабрики - это модули, которые поставляют фабрики, которые в свою очередь вызываются в рантайме со стейтом (клиентским или серверным, в зависимости от типа поставляемого модуля).
|
|
70
|
+
Пример для React@18
|
|
93
71
|
|
|
94
|
-
|
|
72
|
+
```tsx
|
|
73
|
+
import type { ModuleMountFunction, ModuleUnmountFunction } from '@alfalab/scripts-modules';
|
|
74
|
+
import ReactDOM from 'react-dom';
|
|
95
75
|
|
|
96
|
-
|
|
76
|
+
let root: ReturnType<typeof ReactDOM.createRoot>
|
|
97
77
|
|
|
98
|
-
|
|
99
|
-
|
|
78
|
+
export const mount: ModuleMountFunction = (targetNide, runParams, serverState) => {
|
|
79
|
+
root = ReactDOM.createRoot(targetNode);
|
|
100
80
|
|
|
101
|
-
|
|
102
|
-
// serverState - это состояние, которое подготовлено на сервере модуля
|
|
103
|
-
// runParams - это параметры, которые были переданы при запуске модуля клиентом
|
|
104
|
-
// в фабрике можно на основе стейта вернуть готовый модуль
|
|
105
|
-
return {
|
|
106
|
-
serverState,
|
|
107
|
-
doSomething: () => {
|
|
108
|
-
fetch(serverState.baseUrl + '/api/getData')
|
|
109
|
-
}
|
|
110
|
-
};
|
|
81
|
+
root.render(<App preparedState={serverState} runParams={runParams} />);
|
|
111
82
|
}
|
|
112
83
|
|
|
113
|
-
export
|
|
114
|
-
|
|
84
|
+
export const unmount: ModuleUnmountFunction = () => {
|
|
85
|
+
root?.unmount();
|
|
86
|
+
}
|
|
115
87
|
```
|
|
116
88
|
|
|
117
|
-
|
|
118
|
-
```ts
|
|
119
|
-
import type { FactoryModule } from '@alfalab/scripts-modules';
|
|
89
|
+
## 3. Подключите модуль в другом приложении (в bar-app)
|
|
120
90
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
91
|
+
Если предположить что приложение с модулем уже развернуто по адресу https://examle.com/foo-app
|
|
92
|
+
```tsx
|
|
93
|
+
import {
|
|
94
|
+
createModuleLoader,
|
|
95
|
+
createModuleFetcher,
|
|
96
|
+
useModuleMounter,
|
|
97
|
+
MountableModule,
|
|
98
|
+
} from '@alfalab/scripts-modules';
|
|
99
|
+
|
|
100
|
+
const loader = createModuleLoader<MountableModule>({
|
|
101
|
+
hostAppId: 'bar-app',
|
|
102
|
+
moduleId: 'test',
|
|
103
|
+
getModuleResources: createModuleFetcher({
|
|
104
|
+
baseUrl: 'https://examle.com/foo-app',
|
|
105
|
+
}),
|
|
106
|
+
});
|
|
130
107
|
|
|
131
|
-
|
|
108
|
+
export const MyAwesomeComponent = () => {
|
|
109
|
+
const { loadingState, targetElementRef } = useModuleMounter({ loader });
|
|
110
|
+
|
|
111
|
+
return (
|
|
112
|
+
<div>
|
|
113
|
+
{loadingState === 'pending' && <div>pending...</div>}
|
|
114
|
+
{loadingState === 'rejected' && <div>Error</div>}
|
|
115
|
+
<div ref={targetElementRef} /> {/* сюда будет монтироваться модуль */}
|
|
116
|
+
</div>
|
|
117
|
+
);
|
|
118
|
+
}
|
|
132
119
|
```
|
|
133
120
|
|
|
134
|
-
|
|
121
|
+
На этом этапе вы получите подгружающееся в bar-app модуль.
|
|
135
122
|
|
|
136
|
-
|
|
137
|
-
Для того чтобы ваше приложение начало предоставлять модули, вам необходимо добавить настройки в `arui-scripts.config.ts`:
|
|
123
|
+
## 4. (Опционально). Определить shared-библиотеки в приложении потребителе (в bar-app)
|
|
138
124
|
|
|
139
125
|
```ts
|
|
140
|
-
|
|
126
|
+
// ./arui-scripts.config.ts
|
|
127
|
+
import type { PackageSettings } from 'arui-scripts';
|
|
141
128
|
|
|
142
129
|
const aruiScriptsConfig: PackageSettings = {
|
|
143
|
-
compatModules: {
|
|
144
|
-
exposes: {
|
|
145
|
-
'ClientModuleCompat': { // имя модуля, будет использоваться приложениями-потребителями
|
|
146
|
-
entry: './src/modules/module-compat/index', // точка входа модуля
|
|
147
|
-
},
|
|
148
|
-
'ServerStateModuleCompat': {
|
|
149
|
-
entry: './src/modules/server-state-module-compat/index',
|
|
150
|
-
// этот модуль будет ожидать на странице глобальные переменные react и reactDOM,
|
|
151
|
-
// он будет использовать их вместо библиотек из своего node_modules
|
|
152
|
-
compatConfig: {
|
|
153
|
-
react: 'react',
|
|
154
|
-
'react-dom': 'reactDOM',
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
},
|
|
159
130
|
modules: {
|
|
160
|
-
// модули тут смогут переиспользовать react и react-dom из хост-приложения,
|
|
161
|
-
// если хост приложение шарит эти библиотеки, и они совпадают по версии
|
|
162
131
|
shared: {
|
|
163
|
-
'react': '^17.0.0',
|
|
132
|
+
'react': '^17.0.0',
|
|
164
133
|
'react-dom': '^17.0.0',
|
|
165
134
|
},
|
|
166
|
-
exposes: {
|
|
167
|
-
'module': './src/modules/module/index',
|
|
168
|
-
'ServerStateModule': './src/modules/server-state-module/index',
|
|
169
|
-
}
|
|
170
135
|
}
|
|
171
136
|
}
|
|
172
137
|
|
|
173
138
|
export default aruiScriptsConfig;
|
|
174
139
|
```
|
|
140
|
+
Эта конфигурация даст возможность использовать react и react-dom библиотеки из bar-app, не загружая их из foo-app.
|
|
175
141
|
|
|
176
|
-
|
|
142
|
+
## Готово!
|
|
177
143
|
|
|
178
|
-
|
|
179
|
-
|
|
144
|
+
Эта минимальная конфигурация, которая нужна для работы с модулями. Далее идет информация об advanced настройках работы с модулями.
|
|
145
|
+
Рекомендуется с ней ознакомиться хотя бы верхнеуровнево, для того, чтобы понимать какие возможности есть у модулей.
|
|
180
146
|
|
|
181
|
-
|
|
147
|
+
# Передача параметров в модуль из приложения-потребителя
|
|
148
|
+
Зачастую модули должны получать какую-то информацию из приложения потребителя при своей инициализации.
|
|
182
149
|
|
|
183
|
-
|
|
184
|
-
|
|
150
|
+
На стороне модуля эти параметры приходят в параметр `runParams` функции mount. Предположим ваш модуль должен получать из
|
|
151
|
+
приложения-потребителя тему (`theme`) и ширину (`width`). Тогда входная точка будет выглядеть примерно так:
|
|
185
152
|
|
|
186
|
-
```
|
|
187
|
-
|
|
153
|
+
```tsx
|
|
154
|
+
import type { ModuleMountFunction, ModuleUnmountFunction } from '@alfalab/scripts-modules';
|
|
188
155
|
|
|
189
|
-
|
|
190
|
-
|
|
156
|
+
type ModuleRunParams = {
|
|
157
|
+
theme: stirng;
|
|
158
|
+
width: number;
|
|
191
159
|
};
|
|
192
160
|
|
|
193
|
-
export const
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
```ts
|
|
201
|
-
// src/modules/module-compat/index.ts
|
|
202
|
-
|
|
203
|
-
window.ModuleCompat = {
|
|
204
|
-
doSomething: () => {
|
|
205
|
-
console.log('Hello from compat module!');
|
|
206
|
-
},
|
|
207
|
-
publicConstant: 3.14,
|
|
208
|
-
// ...
|
|
209
|
-
};
|
|
161
|
+
export const mount: ModuleMountFunction<ModuleRunParams> = (targetNode, runParams) => {
|
|
162
|
+
ReactDOM.render(
|
|
163
|
+
<MyAwesomeComponent theme={runParams.theme} width={runParams.width} />,
|
|
164
|
+
targetNode,
|
|
165
|
+
);
|
|
166
|
+
}
|
|
167
|
+
// далее unmount как и раньше
|
|
210
168
|
```
|
|
211
169
|
|
|
212
|
-
|
|
213
|
-
<summary>Писать в window? Вы что, с дуба рухнулись?</summary>
|
|
214
|
-
Да, конечно, это может создать определенные проблемы (конфликты имен модулей, определенные ограничения на используемые названия),
|
|
215
|
-
но по сути это единственный способ передать код модуля в хост-приложение.
|
|
216
|
-
|
|
217
|
-
Webpack module federation делает абсолютно то же самое, просто прячет работу с глобальными переменными за собой.
|
|
218
|
-
</details>
|
|
219
|
-
|
|
220
|
-
#### Создание модулей предопределенного типа
|
|
221
|
-
|
|
222
|
-
**Монтируемый модуль, default**
|
|
170
|
+
На стороне потребителя:
|
|
223
171
|
|
|
224
172
|
```tsx
|
|
225
|
-
|
|
173
|
+
import { createModuleLoader, MountableModule, useModuleMounter } from '@alfalab/scripts-modules';
|
|
226
174
|
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
175
|
+
// У нас возможности передать типы из приложения-модуля в приложение-хост
|
|
176
|
+
// При желании вы можете вынести эти типы в общую библиотеку и использовать оттуда
|
|
177
|
+
type ModuleRunParams = {
|
|
178
|
+
theme: string;
|
|
179
|
+
width: number;
|
|
180
|
+
};
|
|
231
181
|
|
|
232
|
-
|
|
233
|
-
console.log('Module: mount', { runParams, serverState });
|
|
234
|
-
if (!targetNode) {
|
|
235
|
-
throw new Error(`Target node is not defined for module`);
|
|
236
|
-
}
|
|
182
|
+
type ModuleType = MountableModule<ModuleRunParams>;
|
|
237
183
|
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
}
|
|
184
|
+
const loader = createModuleLoader<ModuleType>(/*...*/);
|
|
185
|
+
export const MyAwesomeComponent = () => {
|
|
186
|
+
const { loadingState, targetElementRef } = useModuleMounter({
|
|
187
|
+
loader,
|
|
188
|
+
runParams: { theme: 'blue', width: 300 },
|
|
189
|
+
});
|
|
245
190
|
|
|
246
|
-
|
|
247
|
-
|
|
191
|
+
return (
|
|
192
|
+
<div>
|
|
193
|
+
{ loadingState === 'pending' && <div>pending...</div> }
|
|
194
|
+
{ loadingState === 'rejected' && <div>Error</div> }
|
|
195
|
+
<div ref={ targetElementRef }/>
|
|
196
|
+
</div>
|
|
197
|
+
);
|
|
198
|
+
}
|
|
248
199
|
```
|
|
249
200
|
|
|
250
|
-
|
|
201
|
+
:warning: **Внимание!** Модуль не будет обновляться при изменении runParams. Это осознанное решение, вы не должны относится к
|
|
202
|
+
параметрам тут с той же легкостью, что и к prop-ам react-компонентов. Очень легко провести параллели между эти двумя
|
|
203
|
+
концепциями, но использование runParams специально сделано менее удобным - чем меньше вы их используете, тем реже вы будете
|
|
204
|
+
их менять, и тем меньше вероятность привнести обратно несовместимые изменения. Старайтесь передавать в runParams только
|
|
205
|
+
примитивы, не пытаться передавать там объекты сущностей (например профиль пользователя).
|
|
206
|
+
В целом - минимизируйте их использование.
|
|
207
|
+
Если вам важно чтобы модуль перемонтировался каждый раз при изменении runParams - вы можете сделать это самостоятельно, например добавив
|
|
208
|
+
key в ваш компонент-обертку вокруг модуля.
|
|
251
209
|
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
import type { ModuleMountFunction, ModuleUnmountFunction, WindowWithMountableModule } from '@alfalab/scripts-modules';
|
|
257
|
-
import { ModuleCompat } from './ModuleCompat';
|
|
258
|
-
|
|
259
|
-
const mount: ModuleMountFunction<any, any> = (targetNode, runParams, serverState) => {
|
|
260
|
-
console.log('ModuleCompat: mount', { runParams, serverState });
|
|
261
|
-
ReactDOM.render(<ModuleCompat />, targetNode);
|
|
262
|
-
};
|
|
263
|
-
const unmount: ModuleUnmountFunction = (targetNode) => {
|
|
264
|
-
console.log('ModuleCompat: unmount');
|
|
210
|
+
# Возможность управления модулями с сервера
|
|
211
|
+
Сами модули используются только на клиентской части приложения. Но, в некоторых случаях, может быть полезно иметь возможность
|
|
212
|
+
управлять тем, какой модуль должен быть подключен на странице с сервера, или же иметь модуль, который будет при загрузке
|
|
213
|
+
иметь доступ к данным, доступным только на сервере (аналогично тому, как мы передаем серверный стейт в приложения при SSR).
|
|
265
214
|
|
|
266
|
-
|
|
267
|
-
|
|
215
|
+
По умолчанию, даже в клиентские модули в mount-функцию будет передан параметр `serverState`, содержащий:
|
|
216
|
+
- `baseUrl` - тот адрес, который использовался при загрузке модуля с помощью `createModuleFetcher`. Вы можете использовать
|
|
217
|
+
этот адрес например для определения того, где искать API вашего модуля
|
|
218
|
+
- `hostAppId` - идентификатор приложения, которое загружает модуль. Может быть полезен если вы хотите менять поведение
|
|
219
|
+
модуля в зависимости от того, кто его потребляет.
|
|
268
220
|
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
unmount: unmountModule,
|
|
272
|
-
};
|
|
273
|
-
```
|
|
221
|
+
Так же `arui-scripts` предоставляет возможность создать специальный эндпоинт на вашем сервере, из которого вы сможете управлять
|
|
222
|
+
состоянием модуля.
|
|
274
223
|
|
|
224
|
+
Модули с такой возможностью мы называем _модулями с серверным состоянием_ (_server state_).
|
|
275
225
|
|
|
276
|
-
|
|
277
|
-
Если вы хотите, чтобы ваш модуль имел серверную часть, которая сможет подготовить данные для модуля, то вам необходимо
|
|
278
|
-
определить серверный эндпоинт для модуля. Для этого вам нужно определить объект, описывающий ваши модули:
|
|
226
|
+
Для удобного создания таких эндпоинтов сделаны хелперы в пакете `@alfalab/scripts-server`. Например, для `hapi@20`:
|
|
279
227
|
|
|
280
228
|
```ts
|
|
281
|
-
import
|
|
282
|
-
|
|
283
|
-
const
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
contextRoot: 'http://localhost:8081',
|
|
303
|
-
}),
|
|
304
|
-
},
|
|
305
|
-
};
|
|
306
|
-
```
|
|
307
|
-
|
|
308
|
-
Подробнее о `getResourcesRequest` и `getRunParams` рассказано в разделе [Подключение модулей](#Подключение-модулей).
|
|
229
|
+
import { createGetModulesHapi20Plugin } from '@alfalab/scripts-server/build/hapi-20';
|
|
230
|
+
|
|
231
|
+
const plugin = createGetModulesHapi20Plugin(
|
|
232
|
+
{
|
|
233
|
+
'SomeModule': {
|
|
234
|
+
mountMode: 'default',
|
|
235
|
+
version: '1.0.0',
|
|
236
|
+
getModuleState: async (getResourcesRequest, request) => {
|
|
237
|
+
console.log(getResourcesRequest.moduleId); // "SomeModule"
|
|
238
|
+
console.log(getResourcesRequest.hostAppId); // В зависимости от того, что за приложение запросило модуль
|
|
239
|
+
console.log(getResourcesRequest.params); // параметры запроса за модулем, подробнее ниже
|
|
240
|
+
console.log(request); // Для каждого фреймворка это будет специфичный для него объект запроса
|
|
241
|
+
const answerFromAwesomeService = await getSomethingFromBackend();
|
|
242
|
+
return {
|
|
243
|
+
baseUrl: '/awesome-app',
|
|
244
|
+
answerFromAwesomeService, // эти данные попадут в параметр serverState mount-функции модуля
|
|
245
|
+
};
|
|
246
|
+
},
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
);
|
|
309
250
|
|
|
310
|
-
|
|
311
|
-
|
|
251
|
+
server.register(plugin);
|
|
252
|
+
```
|
|
312
253
|
|
|
254
|
+
Для других фреймворков это будет выглядеть аналогично, для `express`
|
|
313
255
|
```ts
|
|
314
256
|
import { createGetModulesExpress } from '@alfalab/scripts-server/build/express';
|
|
315
257
|
|
|
316
|
-
const modulesRouter = createGetModulesExpress(
|
|
258
|
+
const modulesRouter = createGetModulesExpress({/*...*/});
|
|
317
259
|
|
|
318
260
|
app.use(modulesRouter);
|
|
319
261
|
```
|
|
@@ -323,17 +265,7 @@ app.use(modulesRouter);
|
|
|
323
265
|
```ts
|
|
324
266
|
import { createGetModulesHapi16Plugin } from '@alfalab/scripts-server/build/hapi16';
|
|
325
267
|
|
|
326
|
-
const modulesPlugin = createGetModulesHapi16Plugin(
|
|
327
|
-
|
|
328
|
-
server.register(modulesPlugin);
|
|
329
|
-
```
|
|
330
|
-
|
|
331
|
-
Для `hapi@20`:
|
|
332
|
-
|
|
333
|
-
```ts
|
|
334
|
-
import { createGetModulesHapi20Plugin } from '@alfalab/scripts-server/build/hapi20';
|
|
335
|
-
|
|
336
|
-
const modulesPlugin = createGetModulesHapi20Plugin(modules);
|
|
268
|
+
const modulesPlugin = createGetModulesHapi16Plugin({/*...*/});
|
|
337
269
|
|
|
338
270
|
server.register(modulesPlugin);
|
|
339
271
|
```
|
|
@@ -343,7 +275,7 @@ server.register(modulesPlugin);
|
|
|
343
275
|
```ts
|
|
344
276
|
import { createGetModulesMethod } from '@alfalab/scripts-server';
|
|
345
277
|
|
|
346
|
-
const getModules = createGetModulesMethod(
|
|
278
|
+
const getModules = createGetModulesMethod({/*...*/});
|
|
347
279
|
|
|
348
280
|
// getModules будет иметь следующую сигнатуру:
|
|
349
281
|
type ModulesMethod = {
|
|
@@ -357,191 +289,330 @@ type ModulesMethod = {
|
|
|
357
289
|
// вы можете посмотреть примеры реализации тких методов для express, hapi@16 и hapi@20.
|
|
358
290
|
```
|
|
359
291
|
|
|
360
|
-
|
|
292
|
+
## Подключение модулей с серверным состоянием в приложении потребителе
|
|
293
|
+
Приложение-потребитель должно знать, что модуль имеет серверное состояние, поскольку это требует
|
|
294
|
+
небольшого изменения механизма подключения модуля:
|
|
361
295
|
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
296
|
+
```tsx
|
|
297
|
+
import {
|
|
298
|
+
createModuleLoader,
|
|
299
|
+
createServerStateModuleFetcher,
|
|
300
|
+
useModuleMounter,
|
|
301
|
+
MountableModule,
|
|
302
|
+
} from '@alfalab/scripts-modules';
|
|
303
|
+
|
|
304
|
+
type RequestParams = { // Опционально, вы можете определить параметры которые попадут на серверную часть модуля, в getResourcesRequest.params
|
|
305
|
+
name: string;
|
|
306
|
+
}
|
|
365
307
|
|
|
366
|
-
|
|
308
|
+
const loader = createModuleLoader<MountableModule, RequestParams>({
|
|
309
|
+
hostAppId: 'bar-app',
|
|
310
|
+
moduleId: 'test',
|
|
311
|
+
getModuleResources: createServerStateModuleFetcher({ // !!! другой метод подключения
|
|
312
|
+
baseUrl: 'http://localhost:8082',
|
|
313
|
+
headers: { 'X-Auth': 'bla-bla' } // опционально вы можете передать дополнительные заголовки для запроса
|
|
314
|
+
}),
|
|
315
|
+
});
|
|
367
316
|
|
|
368
|
-
|
|
317
|
+
export const MyAwesomeComponent = () => {
|
|
318
|
+
const { loadingState, targetElementRef } = useModuleMounter({
|
|
319
|
+
loader,
|
|
320
|
+
loaderParams: { name: 'Ivan' }, // Если модуль не принимает кастомных параметров на сервере - этого можно не делать!
|
|
321
|
+
});
|
|
369
322
|
|
|
370
|
-
|
|
323
|
+
return (
|
|
324
|
+
<div>
|
|
325
|
+
{loadingState === 'pending' && <div>pending...</div>}
|
|
326
|
+
{loadingState === 'rejected' && <div>Error</div>}
|
|
327
|
+
<div ref={targetElementRef} /> {/* сюда будет монтироваться модуль */}
|
|
328
|
+
</div>
|
|
329
|
+
);
|
|
330
|
+
}
|
|
331
|
+
```
|
|
371
332
|
|
|
372
|
-
|
|
373
|
-
|
|
333
|
+
# Изоляция стилей
|
|
334
|
+
Если ваши приложения активно используют глобальные стили (то есть не с css-modules или css-in-js), вы вполне
|
|
335
|
+
можете столкнуться с проблемой конфликтов стилей между модулями и приложением-потребителем.
|
|
374
336
|
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
то конфликтов возникнуть не должно. Если же это не так - вы можете попробовать решить эту проблему используя shadow-dom.
|
|
337
|
+
Для решения конфликтов стилей вы можете попробовать перевести проект на css-modules, но это может быть довольно
|
|
338
|
+
трудоемкой задачей, особенно если у вас уже есть большая кодовая база.
|
|
378
339
|
|
|
379
|
-
|
|
380
|
-
|
|
340
|
+
Простое решение, которое предлагает arui-scripts - это использование другого типа модулей, основанного не
|
|
341
|
+
на module-federation. Эти модули мы называем _compat_ модулями.
|
|
381
342
|
|
|
382
|
-
|
|
383
|
-
|
|
343
|
+
Суть метода заключается в том, что ко всем стилям модуля будет добавляться префикс, который позволит изолировать
|
|
344
|
+
стили модуля от стилей приложения-потребителя.
|
|
384
345
|
|
|
346
|
+
:warning: **Внимание!** - изоляция стилей работает только в одном направлении - стили модуля не будут применены к элементам
|
|
347
|
+
хост-приложения. Но стили хост-приложения могут быть применены к элементам модуля.
|
|
385
348
|
|
|
386
|
-
|
|
349
|
+
Для того чтобы использовать этот метод, вам нужно:
|
|
387
350
|
|
|
388
|
-
|
|
389
|
-
Базовый способ подключение модулей - это использование `createModuleLoader` из `@alfalab/scripts-modules`. Этот метод
|
|
390
|
-
вернет вам функцию, которая позволит подключить модуль в ваше приложение.
|
|
351
|
+
1. Изменить конфигурацию модуля в `arui-scripts.config.ts`:
|
|
391
352
|
|
|
392
353
|
```ts
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
appName: 'moduleSourceAppName', // имя приложения, которое является источником модуля
|
|
404
|
-
mountMode: 'compat', // режим монтирования модуля
|
|
405
|
-
moduleRunParams: { // параметры, которые будут доступны при инициализации модуля
|
|
406
|
-
baseUrl: 'http://localhost:8081',
|
|
354
|
+
// ./arui-scripts.config.ts
|
|
355
|
+
import type { PackageSettings } from 'arui-scripts';
|
|
356
|
+
|
|
357
|
+
const aruiScriptsConfig: PackageSettings = {
|
|
358
|
+
compatModules: {
|
|
359
|
+
// Тут ключ - название библиотеки, значение - имя переменной в window, которая будет использоваться для получения библиотеки
|
|
360
|
+
// Это те библиотеки, которые этот проект будет предоставлять модулям, подключаемым в него
|
|
361
|
+
shared: {
|
|
362
|
+
'react': 'react',
|
|
363
|
+
'react-dom': 'reactDOM',
|
|
407
364
|
},
|
|
408
|
-
|
|
409
|
-
|
|
365
|
+
exposes: {
|
|
366
|
+
'SomeModule': {
|
|
367
|
+
entry: './src/modules/some-module/index',
|
|
368
|
+
// Это те библиотеки, которые модуль будет пытаться получить из window
|
|
369
|
+
externals: {
|
|
370
|
+
react: 'react',
|
|
371
|
+
'react-dom': 'reactDOM',
|
|
372
|
+
},
|
|
373
|
+
},
|
|
374
|
+
'AnotherModule': {
|
|
375
|
+
entry: './src/modules/another-module/index',
|
|
376
|
+
},
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
export default aruiScriptsConfig;
|
|
410
382
|
```
|
|
411
383
|
|
|
412
|
-
|
|
413
|
-
использовать один из готовых хелперов:
|
|
384
|
+
2. Изменить входную точку модуля:
|
|
414
385
|
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
import {
|
|
386
|
+
```tsx
|
|
387
|
+
// ./src/modules/some-module/index
|
|
388
|
+
import type {
|
|
389
|
+
ModuleMountFunction,
|
|
390
|
+
ModuleUnmountFunction,
|
|
391
|
+
WindowWithMountableModule,
|
|
392
|
+
} from '@alfalab/scripts-modules';
|
|
418
393
|
|
|
419
|
-
const
|
|
420
|
-
hostAppId: 'my-app',
|
|
421
|
-
moduleId: 'test',
|
|
422
|
-
getModuleResources: createModuleFetcher({
|
|
423
|
-
baseUrl: 'http://localhost:8081',
|
|
424
|
-
}),
|
|
425
|
-
});
|
|
426
|
-
```
|
|
394
|
+
const CSS_PREFIX = 'module-SomeModule'; // префикс, который будет добавлен к классам модуля. По умолчанию - module-<moduleId>
|
|
427
395
|
|
|
428
|
-
|
|
396
|
+
// Если ваше react-приложение использует порталы, вам так же надо не забыть добавить префикс к элементу-порталу.
|
|
397
|
+
export const mount: ModuleMountFunction = (targetNode, runParams, serverState) => {
|
|
398
|
+
ReactDOM.render(
|
|
399
|
+
<div className={ CSS_PREFIX }>Hello from module!</div>,
|
|
400
|
+
targetNode,
|
|
401
|
+
);
|
|
402
|
+
}
|
|
429
403
|
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
404
|
+
export const unmount: ModuleUnmountFunction = (targetNode) => {
|
|
405
|
+
ReactDOM.unmountComponentAtNode(targetNode);
|
|
406
|
+
};
|
|
433
407
|
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
baseUrl: 'http://localhost:8081',
|
|
439
|
-
headers: { 'X-Auth': 'bla-bla' } // опционально вы можете передать дополнительные заголовки для запроса
|
|
440
|
-
}),
|
|
441
|
-
});
|
|
408
|
+
(window as WindowWithMountableModule).SomeModule = { // имя переменной в window должно соответствовать имени модуля в exposes
|
|
409
|
+
mount: mountModule,
|
|
410
|
+
unmount: unmountModule,
|
|
411
|
+
};
|
|
442
412
|
```
|
|
443
413
|
|
|
444
|
-
|
|
414
|
+
Как видите, изменения в сравнении с обычными модулями минимальны. На стороне потребителя при
|
|
415
|
+
этом не меняется ничего - `@alfalab/script-modules` сам поймет как загружать такой модуль и будет подключать его
|
|
416
|
+
нужным способом.
|
|
445
417
|
|
|
446
|
-
|
|
418
|
+
<details>
|
|
419
|
+
<summary>Писать в window? Вы что, с дуба рухнулись?</summary>
|
|
420
|
+
Да, конечно, это может создать определенные проблемы (конфликты имен модулей, определенные ограничения на используемые названия),
|
|
421
|
+
но по сути это единственный способ передать код модуля в хост-приложение.
|
|
447
422
|
|
|
448
|
-
|
|
449
|
-
|
|
423
|
+
Webpack module federation делает абсолютно то же самое, просто прячет работу с глобальными переменными за собой.
|
|
424
|
+
</details>
|
|
450
425
|
|
|
451
|
-
|
|
452
|
-
const { module, unmount, moduleResources } = await loader({
|
|
453
|
-
getResourcesParams: { foo: 'bar' }, // параметры, которые будут переданы в getModuleResources
|
|
454
|
-
});
|
|
426
|
+
### Сравнение способов подключения модулей
|
|
455
427
|
|
|
456
|
-
|
|
457
|
-
|
|
428
|
+
`default` модули:
|
|
429
|
+
- **+++** Простой способ для переиспользования библиотек между модулем и приложением-хостом.
|
|
430
|
+
- **+++** Возможность использовать разные версии общих библиотек в разных модулях/хостах (речь про те библиотеки, которые будут шарится).
|
|
431
|
+
- **---** Нет встроенной изоляции стилей. Стили модуля будут применены к хост-приложению.
|
|
432
|
+
- **---** Нет возможности использовать модуль в приложении, которое не использует webpack.
|
|
458
433
|
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
434
|
+
Проблема изоляции стилей может быть решена с помощью [shadow dom](https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_shadow_DOM),
|
|
435
|
+
или с помощью css modules. Но это накладывает некоторые ограничения либо на поддерживаемые браузеры (shadow dom), либо на
|
|
436
|
+
существующую кодовую базу (css modules должны использоваться везде, если у вас будет две версии arui-feather на странице - будет не очень приятно).
|
|
437
|
+
|
|
438
|
+
`compat` модули:
|
|
439
|
+
- **+++** Встроенная изоляция стилей. Стили модуля не будут применены к хост-приложению, если только вы не захотите этого.
|
|
440
|
+
- **---** Нет возможности использовать разные версии общих библиотек в разных модулях/хостах, если вы хотите их шарить.
|
|
463
441
|
|
|
464
|
-
|
|
465
|
-
если
|
|
442
|
+
*Как понять какой режим использовать?*
|
|
443
|
+
В целом, если ваше приложение и модули используют только css-modules, то можно использовать `default` режим. Конфликты в стилях
|
|
444
|
+
вам в таком случае не грозят. Если же вы используете обычный css, или ваши библиотеки используют обычный css, то лучше
|
|
445
|
+
использовать `compat` режим.
|
|
466
446
|
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
447
|
+
# Другие типы модулей
|
|
448
|
+
|
|
449
|
+
Помимо создания монтируемых модулей, есть возможность создавать и другие типы модулей, более подходящие для некоторых вариантов использования.
|
|
450
|
+
|
|
451
|
+
|
|
452
|
+
## Модули-фабрики
|
|
453
|
+
Модули-фабрики - это модули, которые поставляют фабрики, которые в свою очередь вызываются в рантайме со стейтом (клиентским или серверным, в зависимости от типа поставляемого модуля).
|
|
454
|
+
|
|
455
|
+
Такие модули должны экспортировать фабрику:
|
|
456
|
+
|
|
457
|
+
Для default модулей:
|
|
458
|
+
|
|
459
|
+
```tsx
|
|
460
|
+
import type { FactoryModule } from '@alfalab/scripts-modules';
|
|
461
|
+
|
|
462
|
+
const factory: FactoryModule = function (runParams, serverState) {
|
|
463
|
+
// serverState - это состояние, которое подготовлено на сервере модуля
|
|
464
|
+
// runParams - это параметры, которые были переданы при запуске модуля клиентом
|
|
465
|
+
// в фабрике можно на основе стейта вернуть готовый модуль
|
|
466
|
+
return {
|
|
467
|
+
serverState,
|
|
468
|
+
doSomething: () => {
|
|
469
|
+
fetch(serverState.baseUrl + '/api/getData')
|
|
470
|
+
}
|
|
471
|
+
};
|
|
473
472
|
}
|
|
473
|
+
|
|
474
|
+
export default factory;
|
|
475
|
+
// или export { factory };
|
|
474
476
|
```
|
|
475
477
|
|
|
476
|
-
|
|
478
|
+
для compat модулей:
|
|
479
|
+
```ts
|
|
480
|
+
import type { FactoryModule } from '@alfalab/scripts-modules';
|
|
477
481
|
|
|
478
|
-
|
|
479
|
-
|
|
482
|
+
const factory: FactoryModule = function (runParams, serverState) {
|
|
483
|
+
// в фабрике можно на основе стейта вернуть готовый модуль
|
|
484
|
+
return {
|
|
485
|
+
serverState,
|
|
486
|
+
doSomething: () => {
|
|
487
|
+
fetch(serverState.baseUrl + '/api/getData')
|
|
488
|
+
}
|
|
489
|
+
};
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
window.ModuleCompat = factory;
|
|
493
|
+
```
|
|
480
494
|
|
|
481
|
-
|
|
495
|
+
Серверный эндпоинт для таких модулей создается абсолютно так же, как и для монтируемых модулей.
|
|
482
496
|
|
|
483
|
-
|
|
497
|
+
### Подключение модулей-фабрик в приложении потребителе
|
|
484
498
|
|
|
485
|
-
|
|
486
|
-
import { createModuleLoader, useModuleLoader, createModuleFetcher } from '@alfalab/scripts-modules';
|
|
499
|
+
Для подключения модулей-фабрик можно использовать хук `useModuleFactory`:
|
|
487
500
|
|
|
488
|
-
|
|
501
|
+
```tsx
|
|
502
|
+
import {
|
|
503
|
+
createModuleLoader,
|
|
504
|
+
createModuleFetcher,
|
|
505
|
+
FactoryModule,
|
|
506
|
+
useModuleFactory,
|
|
507
|
+
} from '@alfalab/scripts-modules';
|
|
508
|
+
|
|
509
|
+
const loader = createModuleLoader<FactoryModule>({
|
|
510
|
+
hostAppId: 'bar-app',
|
|
489
511
|
moduleId: 'test',
|
|
490
|
-
getModuleResources: createModuleFetcher({
|
|
491
|
-
baseUrl: '
|
|
512
|
+
getModuleResources: createModuleFetcher({ // или createServerStateModuleFetcher для модулей с серверным состоянием
|
|
513
|
+
baseUrl: 'https://examle.com/foo-app',
|
|
492
514
|
}),
|
|
493
515
|
});
|
|
494
516
|
|
|
495
|
-
const
|
|
496
|
-
const { loadingState, module
|
|
517
|
+
export const MyAwesomeComponent = () => {
|
|
518
|
+
const { loadingState, module } = useModuleFactory({ loader });
|
|
497
519
|
|
|
498
520
|
return (
|
|
499
521
|
<div>
|
|
500
|
-
{loadingState === '
|
|
501
|
-
{loadingState === '
|
|
502
|
-
{
|
|
503
|
-
<div>
|
|
504
|
-
<div>Module loaded</div>
|
|
505
|
-
<div>{module}</div> {/* модуль, который вы загрузили. Тут будет доступно всё, что было экспортировано из модуля */}
|
|
506
|
-
<div>{resources}</div>
|
|
507
|
-
</div>
|
|
508
|
-
)}
|
|
522
|
+
{ loadingState === 'pending' && <div>pending...</div> }
|
|
523
|
+
{ loadingState === 'rejected' && <div>Error</div> }
|
|
524
|
+
<pre>{ JSON.stringify(module) }</pre> {/* модуль будет содержать то, что вернула фабрика */}
|
|
509
525
|
</div>
|
|
510
526
|
);
|
|
511
|
-
}
|
|
527
|
+
}
|
|
528
|
+
```
|
|
529
|
+
|
|
530
|
+
Если вы хотите использовать модуль-фабрику вне реакт-компонента, вы можете использовать другой метод:
|
|
531
|
+
|
|
532
|
+
```ts
|
|
533
|
+
import {
|
|
534
|
+
createModuleLoader,
|
|
535
|
+
createModuleFetcher,
|
|
536
|
+
FactoryModule,
|
|
537
|
+
executeModuleFactory,
|
|
538
|
+
} from '@alfalab/scripts-modules';
|
|
539
|
+
|
|
540
|
+
const loader = createModuleLoader<FactoryModule>(/*...*/);
|
|
541
|
+
|
|
542
|
+
(async () => {
|
|
543
|
+
const loaderResult = await loader();
|
|
544
|
+
|
|
545
|
+
const executionResult = executeModuleFactory(
|
|
546
|
+
result.module,
|
|
547
|
+
result.moduleResources.moduleState,
|
|
548
|
+
{}, // опциональные run-параметры модуля
|
|
549
|
+
);
|
|
550
|
+
|
|
551
|
+
console.log(executionResult); // Тут будет то, что возвращает модуль-фабрика
|
|
552
|
+
})();
|
|
512
553
|
```
|
|
513
554
|
|
|
514
|
-
|
|
555
|
+
## Абстрактные модули
|
|
556
|
+
|
|
557
|
+
Для совсем сложных кейсов, когда вам нужен полный контроль над всем тем, что делает код из модуля, вы можете использовать
|
|
558
|
+
абстрактные модули. Они могут иметь абсолютно любую структуру, экспортировать из себя любые методы и константы.
|
|
559
|
+
|
|
560
|
+
```tsx
|
|
561
|
+
// src/modules/abstract-module/index.ts
|
|
562
|
+
export const doSomething = () => {
|
|
563
|
+
console.log('Hello from abstract module!');
|
|
564
|
+
};
|
|
565
|
+
|
|
566
|
+
export const publicConstant = 3.14;
|
|
567
|
+
|
|
568
|
+
window.AbstractModule = { // для compat модулей
|
|
569
|
+
doSomething,
|
|
570
|
+
publicConstant,
|
|
571
|
+
};
|
|
572
|
+
```
|
|
515
573
|
|
|
516
|
-
Для
|
|
574
|
+
Для подключения вы можете использовать хук `useModuleLoader`:
|
|
517
575
|
|
|
518
576
|
```tsx
|
|
519
|
-
import {
|
|
577
|
+
import {
|
|
578
|
+
createModuleLoader,
|
|
579
|
+
createModuleFetcher,
|
|
580
|
+
useModuleLoader,
|
|
581
|
+
} from '@alfalab/scripts-modules';
|
|
582
|
+
|
|
583
|
+
type CustomModule = {
|
|
584
|
+
doSomething: () => void;
|
|
585
|
+
publicConstant: number;
|
|
586
|
+
}
|
|
520
587
|
|
|
521
|
-
const loader = createModuleLoader({
|
|
588
|
+
const loader = createModuleLoader<CustomModule>({
|
|
589
|
+
hostAppId: 'bar-app',
|
|
522
590
|
moduleId: 'test',
|
|
523
591
|
getModuleResources: createModuleFetcher({
|
|
524
|
-
baseUrl: '
|
|
592
|
+
baseUrl: 'https://examle.com/foo-app',
|
|
525
593
|
}),
|
|
526
594
|
});
|
|
527
595
|
|
|
528
|
-
const
|
|
529
|
-
const { loadingState,
|
|
530
|
-
loader,
|
|
531
|
-
loaderParams: {}, // параметры, которые будут переданы в getModuleResources, опционально
|
|
532
|
-
runParams: {}, // параметры, которые будут переданы в mount функцию модуля, опционально
|
|
533
|
-
});
|
|
596
|
+
export const MyAwesomeComponent = () => {
|
|
597
|
+
const { loadingState, module } = useModuleLoader({ loader });
|
|
534
598
|
|
|
535
599
|
return (
|
|
536
600
|
<div>
|
|
537
|
-
{loadingState === '
|
|
538
|
-
{loadingState === '
|
|
539
|
-
<
|
|
601
|
+
{ loadingState === 'pending' && <div>pending...</div> }
|
|
602
|
+
{ loadingState === 'rejected' && <div>Error</div> }
|
|
603
|
+
<pre>{ JSON.stringify(module) }</pre> {/* модуль будет содержать то, что экспортирует модуль, то есть doSomething и publicConstant */}
|
|
540
604
|
</div>
|
|
541
605
|
);
|
|
542
|
-
}
|
|
606
|
+
}
|
|
543
607
|
```
|
|
544
608
|
|
|
609
|
+
# Тестирование модулей
|
|
610
|
+
Поскольку в общем случае модули представляют собой простой js код - для тестирования вы можете пользоваться любыми привычными вам инструментами.
|
|
611
|
+
|
|
612
|
+
Для тестирования модулей в cypress или playwright вы можете создать отдельный эндпоинт в вашем приложении, который будет
|
|
613
|
+
подключать модуль в ваше же приложение.
|
|
614
|
+
|
|
615
|
+
|
|
545
616
|
# Документация API
|
|
546
617
|
|
|
547
618
|
## Конфигурация модулей
|