From 954e8501b1c0baa258671bc845a6c0d13061f17b Mon Sep 17 00:00:00 2001 From: Aleksandr Kitov Date: Wed, 4 Oct 2023 14:35:57 +0200 Subject: [PATCH 1/2] docs(modules): cleanup modules documentation --- packages/arui-scripts/docs/modules.md | 787 ++++++++++++++------------ 1 file changed, 429 insertions(+), 358 deletions(-) diff --git a/packages/arui-scripts/docs/modules.md b/packages/arui-scripts/docs/modules.md index 4db6d95d..3e3b423d 100644 --- a/packages/arui-scripts/docs/modules.md +++ b/packages/arui-scripts/docs/modules.md @@ -14,306 +14,248 @@ то модули приложений - это его реализация в рамках arui-scripts, с дополнительным уровнем абстракции, который, в том числе, позволяет использовать модули без самого module-federation. -## Общие принципы работы модулей -С точки зрения кода модуль представляет собой простой js-объект, который может быть _каким то образом_ подключен в другое приложение. +# Как использовать -`arui-scripts` предоставляет решение для сборки таких модулей, а также отдельную библиотеку для упрощения их подключения в другие приложения. +Предположим у вас есть два приложения, `foo-app` и `bar-app`. Вы хотите предоставлять модуль из `foo-app` и использовать +его в `bar-app`. -## Режимы подключения модулей +## 1. Добавляем конфигурацию в arui-scripts.config.ts (в foo-app) -В `arui-scripts` есть два способа сборки модулей: -- `default` - Это стандартные модули, которые подключаются с помощью [webpack module federation](https://webpack.js.org/concepts/module-federation/). -- `compat` - Это модули, которые подключаются просто добавлением нужных скриптов на страницу. - -Основная проблема, которую решает ModuleFederation - это возможность не загружать на хост-приложение код библиотек уже подключенных в него. -Например, хост-приложение уже использует `react`, модуль так же написан на `react`. ModuleFederation дает нам легко "переиспользовать" -уже загруженный в браузер код `react` в модуле, не загружая его еще раз. - -### Сравнение - -`default` модули: -- **+++** Простой способ для переиспользования библиотек между модулем и приложением-хостом. -- **+++** Возможность использовать разные версии общих библиотек в разных модулях/хостах (речь про те библиотеки, которые будут шарится). -- **---** Нет встроенной изоляции стилей. Стили модуля будут применены к хост-приложению. -- **---** Нет возможности использовать модуль в приложении, которое не использует webpack. - -Проблема изоляции стилей может быть решена с помощью [shadow dom](https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_shadow_DOM), -или с помощью css modules. Но это накладывает некоторые ограничения либо на поддерживаемые браузеры (shadow dom), либо на -существующую кодовую базу (css modules должны использоваться везде, если у вас будет две версии arui-feather на странице - будет не очень приятно). - -`compat` модули: -- **+++** Встроенная изоляция стилей. Стили модуля не будут применены к хост-приложению, если только вы не захотите этого. -- **---** Нет возможности использовать разные версии общих библиотек в разных модулях/хостах, если вы хотите их шарить. - -*Как понять какой режим использовать?* -В целом, если ваше приложение и модули используют только css-modules, то можно использовать `default` режим. Конфликты в стилях -вам в таком случае не грозят. Если же вы используете обычный css, или ваши библиотеки используют обычный css, то лучше -использовать `compat` режим. - -## Возможность управления модулями с сервера -Сами модули используются только на клиентской части приложения. Но, в некоторых случаях, может быть полезно иметь возможность -управлять тем, какой модуль должен быть подключен на странице с сервера, или же иметь модуль, который будет при загрузке -иметь доступ к данным, доступным только на сервере (аналогично тому, как мы передаем серверный стейт в приложения при SSR). - -Поэтому `arui-scripts` предоставляет возможность создать специальный эндпоинт на вашем сервере, из которого вы сможете управлять -состоянием модуля. +```ts +// ./arui-scripts.config.ts +import type { PackageSettings } from 'arui-scripts'; -Модули с такой возможностью мы называем _модулями с серверным состоянием_ (_server state_). +const aruiScriptsConfig: PackageSettings = { + modules: { + // если хост приложение шарит эти библиотеки, и они совпадают по версии + shared: { + 'react': '^17.0.0', // так же поддерживаются более сложные версии например { requiredVersion: '^17.0.0', singleton: true } + 'react-dom': '^17.0.0', + }, + exposes: { + 'SomeModule': './src/modules/some-module/index', + 'AnotherModule': './src/modules/another-module/index', + } + } +} -## Особые типы модулей -Несмотря на то, что сами по себе модули представляют собой простой js-объект, мы определяем один особый тип модулей - _монтируемые модули_. +export default aruiScriptsConfig; +``` -### Монтируемые модули -Монтируемые модули - это модули, основное предназначение которых - отрендерить какой-то компонент внутри хост-приложения. -Монтируемые модули могут быть как клиентскими, так и серверными. +Эта конфигурация объявляет два модуля, `SomeModule` и `AnotherModule`. Эти модули смогут получать react и react-dom из +подключившего их приложения, если оно содержит конфигурацию для `modules.shared`, и версии библиотек подходят по semver. -Такие модули должны экспортировать две функции: +## 2. Создаем входную точку модуля (в foo-app) ```tsx -import { ModuleMountFunction, ModuleUnmountFunction } from '@alfalab/scripts-modules'; +// src/modules/some-module/index +import type { ModuleMountFunction, ModuleUnmountFunction } from '@alfalab/scripts-modules'; export const mount: ModuleMountFunction = (targetNode, runParams, serverState) => { - // здесь происходит монтирование модуля в хост-приложение - // targetNode - это DOM-нода, в которую нужно отрендерить модуль - // runParams - это параметры, которые были переданы при запуске модуля - // serverState - это состояние, которое было передано с сервера + console.log( // Вы можете передать эти переменные в ваши компоненты, подробнее ниже + runParams, // undefined + serverState // { baseUrl: "https://example.com/foo-app", hostAppId: "bar-app" } // Эти данные определяются тем, что было передано в загрузчик в приложении-хосте + ); - // Скорее всего это будет что-то вроде: - ReactDOM.render(, targetNode); + ReactDOM.render( +
Hello from module!
, + targetNode, + ); } export const unmount: ModuleUnmountFunction = (targetNode) => { - // здесь происходит демонтирование модуля из хост-приложения - // Скорее всего это будет что-то вроде: ReactDOM.unmountComponentAtNode(targetNode); } ``` -### Модули-фабрики -Модули-фабрики - это модули, которые поставляют фабрики, которые в свою очередь вызываются в рантайме со стейтом (клиентским или серверным, в зависимости от типа поставляемого модуля). +Пример для React@18 -Такие модули должны экспортировать фабрику: +```tsx +import type { ModuleMountFunction, ModuleUnmountFunction } from '@alfalab/scripts-modules'; +import ReactDOM from 'react-dom'; -Для mf(default) модулей: +let root: ReturnType -```tsx -import type { FactoryModule } from '@alfalab/scripts-modules'; +export const mount: ModuleMountFunction = (targetNide, runParams, serverState) => { + root = ReactDOM.createRoot(targetNode); -const factory: FactoryModule = function (runParams, serverState) { - // serverState - это состояние, которое подготовлено на сервере модуля - // runParams - это параметры, которые были переданы при запуске модуля клиентом - // в фабрике можно на основе стейта вернуть готовый модуль - return { - serverState, - doSomething: () => { - fetch(serverState.baseUrl + '/api/getData') - } - }; + root.render(); } -export default factory; -// или export { factory }; +export const unmount: ModuleUnmountFunction = () => { + root?.unmount(); +} ``` -для compat модулей: -```ts -import type { FactoryModule } from '@alfalab/scripts-modules'; +## 3. Подключите модуль в другом приложении (в bar-app) -const factory: FactoryModule = function (runParams, serverState) { - // в фабрике можно на основе стейта вернуть готовый модуль - return { - serverState, - doSomething: () => { - fetch(serverState.baseUrl + '/api/getData') - } - }; -} +Если предположить что приложение с модулем уже развернуто по адресу https://examle.com/foo-app +```tsx +import { + createModuleLoader, + createModuleFetcher, + useModuleMounter, + MountableModule, +} from '@alfalab/scripts-modules'; + +const loader = createModuleLoader({ + hostAppId: 'bar-app', + moduleId: 'test', + getModuleResources: createModuleFetcher({ + baseUrl: 'https://examle.com/foo-app', + }), +}); -window.ModuleCompat = factory; +export const MyAwesomeComponent = () => { + const { loadingState, targetElementRef } = useModuleMounter({ loader }); + + return ( +
+ {loadingState === 'pending' &&
pending...
} + {loadingState === 'rejected' &&
Error
} +
{/* сюда будет монтироваться модуль */} +
+ ); +} ``` -## Как создать модуль +На этом этапе вы получите подгружающееся в bar-app модуль. -### Описать модуль в настройках arui-scripts -Для того чтобы ваше приложение начало предоставлять модули, вам необходимо добавить настройки в `arui-scripts.config.ts`: +## 4. (Опционально). Определить shared-библиотеки в приложении потребителе (в bar-app) ```ts -import { PackageSettings } from 'arui-scripts'; +// ./arui-scripts.config.ts +import type { PackageSettings } from 'arui-scripts'; const aruiScriptsConfig: PackageSettings = { - compatModules: { - exposes: { - 'ClientModuleCompat': { // имя модуля, будет использоваться приложениями-потребителями - entry: './src/modules/module-compat/index', // точка входа модуля - }, - 'ServerStateModuleCompat': { - entry: './src/modules/server-state-module-compat/index', - // этот модуль будет ожидать на странице глобальные переменные react и reactDOM, - // он будет использовать их вместо библиотек из своего node_modules - compatConfig: { - react: 'react', - 'react-dom': 'reactDOM', - } - } - } - }, modules: { - // модули тут смогут переиспользовать react и react-dom из хост-приложения, - // если хост приложение шарит эти библиотеки, и они совпадают по версии shared: { - 'react': '^17.0.0', // так же поддерживаются более сложные версии например { requiredVersion: '^17.0.0', singleton: true } + 'react': '^17.0.0', 'react-dom': '^17.0.0', }, - exposes: { - 'module': './src/modules/module/index', - 'ServerStateModule': './src/modules/server-state-module/index', - } } } export default aruiScriptsConfig; ``` +Эта конфигурация даст возможность использовать react и react-dom библиотеки из bar-app, не загружая их из foo-app. -Все параметры конфигурации описаны [ниже](#Конфигурация-модулей). +## Готово! -### Создать модуль -Модуль является простым js/ts файлом. Он может использовать любой код вашего проекта, и любые библиотеки из node_modules. +Эта минимальная конфигурация, которая нужна для работы с модулями. Далее идет информация об advanced настройках работы с модулями. +Рекомендуется с ней ознакомиться хотя бы верхнеуровнево, для того, чтобы понимать какие возможности есть у модулей. -В зависимости от режима подключения модуля, входная точка будет выглядеть по-разному. +# Передача параметров в модуль из приложения-потребителя +Зачастую модули должны получать какую-то информацию из приложения потребителя при своей инициализации. -#### Default модуль -Входная точка модуля должна экспортировать все поля модуля через `export`. +На стороне модуля эти параметры приходят в параметр `runParams` функции mount. Предположим ваш модуль должен получать из +приложения-потребителя тему (`theme`) и ширину (`width`). Тогда входная точка будет выглядеть примерно так: -```ts -// src/modules/module/index.ts +```tsx +import type { ModuleMountFunction, ModuleUnmountFunction } from '@alfalab/scripts-modules'; -export const doSomething = () => { - console.log('Hello from module!'); +type ModuleRunParams = { + theme: stirng; + width: number; }; -export const publicConstant = 3.14; -``` - -#### Compat модуль -Входная точка compat модуля должна писать в глобальную переменную `window` объект с ключом `{НазваниеМодуля}`. -Все поля этого объекта по сути и будут являться модулем, ваши потребители смогут использовать их. - -```ts -// src/modules/module-compat/index.ts - -window.ModuleCompat = { - doSomething: () => { - console.log('Hello from compat module!'); - }, - publicConstant: 3.14, - // ... -}; +export const mount: ModuleMountFunction = (targetNode, runParams) => { + ReactDOM.render( + , + targetNode, + ); +} +// далее unmount как и раньше ``` -
-Писать в window? Вы что, с дуба рухнулись? -Да, конечно, это может создать определенные проблемы (конфликты имен модулей, определенные ограничения на используемые названия), -но по сути это единственный способ передать код модуля в хост-приложение. - -Webpack module federation делает абсолютно то же самое, просто прячет работу с глобальными переменными за собой. -
- -#### Создание модулей предопределенного типа - -**Монтируемый модуль, default** +На стороне потребителя: ```tsx -// src/modules/module/index.ts +import { createModuleLoader, MountableModule, useModuleMounter } from '@alfalab/scripts-modules'; -import React from 'react'; -import ReactDOM from 'react-dom'; -import type { ModuleMountFunction, ModuleUnmountFunction } from '@alfalab/scripts-modules'; -import { Module } from './Module'; +// У нас возможности передать типы из приложения-модуля в приложение-хост +// При желании вы можете вынести эти типы в общую библиотеку и использовать оттуда +type ModuleRunParams = { + theme: string; + width: number; +}; -export const mount: ModuleMountFunction = (targetNode, runParams, serverState) => { - console.log('Module: mount', { runParams, serverState }); - if (!targetNode) { - throw new Error(`Target node is not defined for module`); - } +type ModuleType = MountableModule; - ReactDOM.render(, targetNode); -}; -export const unmount: ModuleUnmountFunction = (targetNode) => { - console.log('Module: unmount'); - if (!targetNode) { - return; - } +const loader = createModuleLoader(/*...*/); +export const MyAwesomeComponent = () => { + const { loadingState, targetElementRef } = useModuleMounter({ + loader, + runParams: { theme: 'blue', width: 300 }, + }); - ReactDOM.unmountComponentAtNode(targetNode); -}; + return ( +
+ { loadingState === 'pending' &&
pending...
} + { loadingState === 'rejected' &&
Error
} +
+
+ ); +} ``` -**Монтируемый модуль, compat** +:warning: **Внимание!** Модуль не будет обновляться при изменении runParams. Это осознанное решение, вы не должны относится к +параметрам тут с той же легкостью, что и к prop-ам react-компонентов. Очень легко провести параллели между эти двумя +концепциями, но использование runParams специально сделано менее удобным - чем меньше вы их используете, тем реже вы будете +их менять, и тем меньше вероятность привнести обратно несовместимые изменения. Старайтесь передавать в runParams только +примитивы, не пытаться передавать там объекты сущностей (например профиль пользователя). +В целом - минимизируйте их использование. +Если вам важно чтобы модуль перемонтировался каждый раз при изменении runParams - вы можете сделать это самостоятельно, например добавив +key в ваш компонент-обертку вокруг модуля. -```tsx -// src/modules/module-compat/index.ts -import React from 'react'; -import ReactDOM from 'react-dom'; -import type { ModuleMountFunction, ModuleUnmountFunction, WindowWithMountableModule } from '@alfalab/scripts-modules'; -import { ModuleCompat } from './ModuleCompat'; - -const mount: ModuleMountFunction = (targetNode, runParams, serverState) => { - console.log('ModuleCompat: mount', { runParams, serverState }); - ReactDOM.render(, targetNode); -}; -const unmount: ModuleUnmountFunction = (targetNode) => { - console.log('ModuleCompat: unmount'); +# Возможность управления модулями с сервера +Сами модули используются только на клиентской части приложения. Но, в некоторых случаях, может быть полезно иметь возможность +управлять тем, какой модуль должен быть подключен на странице с сервера, или же иметь модуль, который будет при загрузке +иметь доступ к данным, доступным только на сервере (аналогично тому, как мы передаем серверный стейт в приложения при SSR). - ReactDOM.unmountComponentAtNode(targetNode); -}; +По умолчанию, даже в клиентские модули в mount-функцию будет передан параметр `serverState`, содержащий: +- `baseUrl` - тот адрес, который использовался при загрузке модуля с помощью `createModuleFetcher`. Вы можете использовать +этот адрес например для определения того, где искать API вашего модуля +- `hostAppId` - идентификатор приложения, которое загружает модуль. Может быть полезен если вы хотите менять поведение +модуля в зависимости от того, кто его потребляет. -(window as WindowWithMountableModule).ModuleCompat = { - mount: mountModule, - unmount: unmountModule, -}; -``` +Так же `arui-scripts` предоставляет возможность создать специальный эндпоинт на вашем сервере, из которого вы сможете управлять +состоянием модуля. +Модули с такой возможностью мы называем _модулями с серверным состоянием_ (_server state_). -### (Опционально) Определить серверный эндпоинт для модуля -Если вы хотите, чтобы ваш модуль имел серверную часть, которая сможет подготовить данные для модуля, то вам необходимо -определить серверный эндпоинт для модуля. Для этого вам нужно определить объект, описывающий ваши модули: +Для удобного создания таких эндпоинтов сделаны хелперы в пакете `@alfalab/scripts-server`. Например, для `hapi@20`: ```ts -import type { ModulesConfig } from '@alfalab/scripts-server'; - -const modules: ModulesConfig = { - 'ServerStateModuleCompat': { - mountMode: 'compat', - version: '1.0.0', - getRunParams: async (getResourcesRequest) => ({ - // getResouresRequest - это объект, который будет передан из хост-приложения - - // данные, которые вернет эта будут доступны при инициализации модуля - paramFromServer: 'This can be any data from server', - asyncData: 'It can be constructed from async data, so you may perform some service calls here', - contextRoot: 'http://localhost:8081', - }), - }, - 'ServerModule': { - mountMode: 'default', - version: '1.0.0', - getRunParams: async () => ({ - paramFromServer: 'This can be any data from server', - asyncData: 'It can be constructed from async data, so you may perform some service calls here', - contextRoot: 'http://localhost:8081', - }), - }, -}; -``` - -Подробнее о `getResourcesRequest` и `getRunParams` рассказано в разделе [Подключение модулей](#Подключение-модулей). +import { createGetModulesHapi20Plugin } from '@alfalab/scripts-server/build/hapi-20'; + +const plugin = createGetModulesHapi20Plugin( + { + 'SomeModule': { + mountMode: 'default', + version: '1.0.0', + getModuleState: async (getResourcesRequest, request) => { + console.log(getResourcesRequest.moduleId); // "SomeModule" + console.log(getResourcesRequest.hostAppId); // В зависимости от того, что за приложение запросило модуль + console.log(getResourcesRequest.params); // параметры запроса за модулем, подробнее ниже + console.log(request); // Для каждого фреймворка это будет специфичный для него объект запроса + const answerFromAwesomeService = await getSomethingFromBackend(); + return { + baseUrl: '/awesome-app', + answerFromAwesomeService, // эти данные попадут в параметр serverState mount-функции модуля + }; + }, + } + } +); -Далее, в зависимости от того, какой серверный фреймворк вы используете, вам нужно будет подключить ваши модули в -соответствующий хендлер. Например, для express это будет выглядеть так: +server.register(plugin); +``` +Для других фреймворков это будет выглядеть аналогично, для `express` ```ts import { createGetModulesExpress } from '@alfalab/scripts-server/build/express'; -const modulesRouter = createGetModulesExpress(modules); +const modulesRouter = createGetModulesExpress({/*...*/}); app.use(modulesRouter); ``` @@ -323,17 +265,7 @@ app.use(modulesRouter); ```ts import { createGetModulesHapi16Plugin } from '@alfalab/scripts-server/build/hapi16'; -const modulesPlugin = createGetModulesHapi16Plugin(modules); - -server.register(modulesPlugin); -``` - -Для `hapi@20`: - -```ts -import { createGetModulesHapi20Plugin } from '@alfalab/scripts-server/build/hapi20'; - -const modulesPlugin = createGetModulesHapi20Plugin(modules); +const modulesPlugin = createGetModulesHapi16Plugin({/*...*/}); server.register(modulesPlugin); ``` @@ -343,7 +275,7 @@ server.register(modulesPlugin); ```ts import { createGetModulesMethod } from '@alfalab/scripts-server'; -const getModules = createGetModulesMethod(modules); +const getModules = createGetModulesMethod({/*...*/}); // getModules будет иметь следующую сигнатуру: type ModulesMethod = { @@ -357,191 +289,330 @@ type ModulesMethod = { // вы можете посмотреть примеры реализации тких методов для express, hapi@16 и hapi@20. ``` -### (Опционально) Разобраться с изоляцией стилей +## Подключение модулей с серверным состоянием в приложении потребителе +Приложение-потребитель должно знать, что модуль имеет серверное состояние, поскольку это требует +небольшого изменения механизма подключения модуля: -#### Compat модули -В случае с compat модулями, стили модуля будут применены только к элементам, которые находятся внутри элемента -с классом `module-{имя модуля}`. Это позволяет изолировать стили модуля от стилей хост-приложения. +```tsx +import { + createModuleLoader, + createServerStateModuleFetcher, + useModuleMounter, + MountableModule, +} from '@alfalab/scripts-modules'; + +type RequestParams = { // Опционально, вы можете определить параметры которые попадут на серверную часть модуля, в getResourcesRequest.params + name: string; +} -Вашей ответственностью будет добавить к рут-элементу модуля класс .module-nameOfModule. Вы должны сделать это в самом верхнем компоненте/элементе вашего модуля. +const loader = createModuleLoader({ + hostAppId: 'bar-app', + moduleId: 'test', + getModuleResources: createServerStateModuleFetcher({ // !!! другой метод подключения + baseUrl: 'http://localhost:8082', + headers: { 'X-Auth': 'bla-bla' } // опционально вы можете передать дополнительные заголовки для запроса + }), +}); -Вы можете переопределить префикс для css классов модуля, в `arui-scripts.config.ts`, подробнее в [конфигурации модулей](#Конфигурация-модулей). +export const MyAwesomeComponent = () => { + const { loadingState, targetElementRef } = useModuleMounter({ + loader, + loaderParams: { name: 'Ivan' }, // Если модуль не принимает кастомных параметров на сервере - этого можно не делать! + }); -Если ваше react-приложение использует порталы, вам так же надо не забыть добавить префикс к элементу-порталу. + return ( +
+ {loadingState === 'pending' &&
pending...
} + {loadingState === 'rejected' &&
Error
} +
{/* сюда будет монтироваться модуль */} +
+ ); +} +``` -**Важно** - изоляция стилей работает только в одном направлении - стили модуля не будут применены к элементам -хост-приложения. Но стили хост-приложения могут быть применены к элементам модуля. +# Изоляция стилей +Если ваши приложения активно используют глобальные стили (то есть не с css-modules или css-in-js), вы вполне +можете столкнуться с проблемой конфликтов стилей между модулями и приложением-потребителем. -#### Стандартные модули -Никакого встроенного механизма изоляции стилей для стандартных модулей нет. Если хост-приложение и модуль используют css-modules, -то конфликтов возникнуть не должно. Если же это не так - вы можете попробовать решить эту проблему используя shadow-dom. +Для решения конфликтов стилей вы можете попробовать перевести проект на css-modules, но это может быть довольно +трудоемкой задачей, особенно если у вас уже есть большая кодовая база. -### Тестирование модулей -Поскольку в общем случае модули представляют собой простой js код - для тестирования вы можете пользоваться любыми привычными вам инструментами. +Простое решение, которое предлагает arui-scripts - это использование другого типа модулей, основанного не +на module-federation. Эти модули мы называем _compat_ модулями. -Для тестирования модулей в cypress или playwright вы можете создать отдельный эндпоинт в вашем приложении, который будет -подключать модуль в ваше же приложение. +Суть метода заключается в том, что ко всем стилям модуля будет добавляться префикс, который позволит изолировать +стили модуля от стилей приложения-потребителя. +:warning: **Внимание!** - изоляция стилей работает только в одном направлении - стили модуля не будут применены к элементам +хост-приложения. Но стили хост-приложения могут быть применены к элементам модуля. -# Подключение модулей +Для того чтобы использовать этот метод, вам нужно: -## Создание загрузчика -Базовый способ подключение модулей - это использование `createModuleLoader` из `@alfalab/scripts-modules`. Этот метод -вернет вам функцию, которая позволит подключить модуль в ваше приложение. +1. Изменить конфигурацию модуля в `arui-scripts.config.ts`: ```ts -import { createModuleLoader } from '@alfalab/scripts-modules'; - -const loader = createModuleLoader({ - hostAppId: 'my-app', // id вашего приложения, оно будет передаваться в серверную ручку модуля - moduleId: 'test', // id модуля, который вы хотите подключить - // функция, которая должна вернуть описание модуля. - getModuleResources: async ({ moduleId, hostAppId, params }) => ({ - scripts: ['http://localhost:8081/static/js/main.js'], // скрипты модуля - styles: ['http://localhost:8081/static/css/main.css'], // стили модуля - moduleVersion: '1.0.0', // версия модуля - appName: 'moduleSourceAppName', // имя приложения, которое является источником модуля - mountMode: 'compat', // режим монтирования модуля - moduleRunParams: { // параметры, которые будут доступны при инициализации модуля - baseUrl: 'http://localhost:8081', +// ./arui-scripts.config.ts +import type { PackageSettings } from 'arui-scripts'; + +const aruiScriptsConfig: PackageSettings = { + compatModules: { + // Тут ключ - название библиотеки, значение - имя переменной в window, которая будет использоваться для получения библиотеки + // Это те библиотеки, которые этот проект будет предоставлять модулям, подключаемым в него + shared: { + 'react': 'react', + 'react-dom': 'reactDOM', }, - }), -}); + exposes: { + 'SomeModule': { + entry: './src/modules/some-module/index', + // Это те библиотеки, которые модуль будет пытаться получить из window + externals: { + react: 'react', + 'react-dom': 'reactDOM', + }, + }, + 'AnotherModule': { + entry: './src/modules/another-module/index', + }, + } + } +} + +export default aruiScriptsConfig; ``` -Вам вовсе не обязательно руками описывать функцию `getModuleResources`. В зависимости от типа модуля, вы можете -использовать один из готовых хелперов: +2. Изменить входную точку модуля: -Для модулей без серверного стейта: -```ts -import { createModuleLoader, createModuleFetcher } from '@alfalab/scripts-modules'; +```tsx +// ./src/modules/some-module/index +import type { + ModuleMountFunction, + ModuleUnmountFunction, + WindowWithMountableModule, +} from '@alfalab/scripts-modules'; -const loader = createModuleLoader({ - hostAppId: 'my-app', - moduleId: 'test', - getModuleResources: createModuleFetcher({ - baseUrl: 'http://localhost:8081', - }), -}); -``` +const CSS_PREFIX = 'module-SomeModule'; // префикс, который будет добавлен к классам модуля. По умолчанию - module- -`createModuleFetcher` сам сделает запрос за манифестом приложения, и правильным образом сформирует описание модуля. +// Если ваше react-приложение использует порталы, вам так же надо не забыть добавить префикс к элементу-порталу. +export const mount: ModuleMountFunction = (targetNode, runParams, serverState) => { + ReactDOM.render( +
Hello from module!
, + targetNode, + ); +} -Для модулей с серверным стейтом: -```ts -import { createModuleLoader, createServerStateModuleFetcher } from '@alfalab/scripts-modules'; +export const unmount: ModuleUnmountFunction = (targetNode) => { + ReactDOM.unmountComponentAtNode(targetNode); +}; -const loader = createModuleLoader({ - hostAppId: 'my-app', - moduleId: 'test', - getModuleResources: createServerStateModuleFetcher({ - baseUrl: 'http://localhost:8081', - headers: { 'X-Auth': 'bla-bla' } // опционально вы можете передать дополнительные заголовки для запроса - }), -}); +(window as WindowWithMountableModule).SomeModule = { // имя переменной в window должно соответствовать имени модуля в exposes + mount: mountModule, + unmount: unmountModule, +}; ``` -`createServerStateModuleFetcher` сам сделает запрос к ручке, которая отдает описание модуля. +Как видите, изменения в сравнении с обычными модулями минимальны. На стороне потребителя при +этом не меняется ничего - `@alfalab/script-modules` сам поймет как загружать такой модуль и будет подключать его +нужным способом. -В случае же совсем кастомных требований, вы можете реализовать функцию `getModuleResources` самостоятельно. +
+Писать в window? Вы что, с дуба рухнулись? +Да, конечно, это может создать определенные проблемы (конфликты имен модулей, определенные ограничения на используемые названия), +но по сути это единственный способ передать код модуля в хост-приложение. -## Использование загрузчика -После того как вы создали `loader` - вы легко можете получить доступ к модулю: +Webpack module federation делает абсолютно то же самое, просто прячет работу с глобальными переменными за собой. +
-```ts -const { module, unmount, moduleResources } = await loader({ - getResourcesParams: { foo: 'bar' }, // параметры, которые будут переданы в getModuleResources -}); +### Сравнение способов подключения модулей -console.log(module); // модуль, который вы загрузили. Тут будут доступны всё, что было экспортировано из модуля -console.log(moduleResources); // полный ответ от getModuleResources +`default` модули: +- **+++** Простой способ для переиспользования библиотек между модулем и приложением-хостом. +- **+++** Возможность использовать разные версии общих библиотек в разных модулях/хостах (речь про те библиотеки, которые будут шарится). +- **---** Нет встроенной изоляции стилей. Стили модуля будут применены к хост-приложению. +- **---** Нет возможности использовать модуль в приложении, которое не использует webpack. -// вызов этой функции отмонтирует модуль из вашего приложения - удалит скрипты и стили модуля, а так же удалит -// все глобальные переменные, которые были определены в модуле. -unmount(); -``` +Проблема изоляции стилей может быть решена с помощью [shadow dom](https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_shadow_DOM), +или с помощью css modules. Но это накладывает некоторые ограничения либо на поддерживаемые браузеры (shadow dom), либо на +существующую кодовую базу (css modules должны использоваться везде, если у вас будет две версии arui-feather на странице - будет не очень приятно). + +`compat` модули: +- **+++** Встроенная изоляция стилей. Стили модуля не будут применены к хост-приложению, если только вы не захотите этого. +- **---** Нет возможности использовать разные версии общих библиотек в разных модулях/хостах, если вы хотите их шарить. -При вызове `loader` вы можете передать параметры, которые попадут в функцию `getModuleResources`. Это может быть полезно, -если вы хотите передать какие-то параметры на сервер модуля. +*Как понять какой режим использовать?* +В целом, если ваше приложение и модули используют только css-modules, то можно использовать `default` режим. Конфликты в стилях +вам в таком случае не грозят. Если же вы используете обычный css, или ваши библиотеки используют обычный css, то лучше +использовать `compat` режим. -`getModuleResources` будет вызвана со следующими параметрами: -```ts -const getModuleResourcesParams = { - moduleId: 'test', // id модуля, который вы хотите подключить - hostAppId: 'my-app', // id вашего приложения - params: { foo: 'bar' }, // параметры, которые вы передали в loader как `getResourcesParams` +# Другие типы модулей + +Помимо создания монтируемых модулей, есть возможность создавать и другие типы модулей, более подходящие для некоторых вариантов использования. + + +## Модули-фабрики +Модули-фабрики - это модули, которые поставляют фабрики, которые в свою очередь вызываются в рантайме со стейтом (клиентским или серверным, в зависимости от типа поставляемого модуля). + +Такие модули должны экспортировать фабрику: + +Для default модулей: + +```tsx +import type { FactoryModule } from '@alfalab/scripts-modules'; + +const factory: FactoryModule = function (runParams, serverState) { + // serverState - это состояние, которое подготовлено на сервере модуля + // runParams - это параметры, которые были переданы при запуске модуля клиентом + // в фабрике можно на основе стейта вернуть готовый модуль + return { + serverState, + doSomething: () => { + fetch(serverState.baseUrl + '/api/getData') + } + }; } + +export default factory; +// или export { factory }; ``` -При использовании `createServerStateModuleFetcher` именно эти данные будут отправлены на сервер и будут доступны в функции `getRunParams` модуля. +для compat модулей: +```ts +import type { FactoryModule } from '@alfalab/scripts-modules'; -При использовании `createModuleFetcher` вам не нужно беспокоиться о том, какие параметры вы передаете в `getModuleResources` - они -никак не используются в клиентских модулях. +const factory: FactoryModule = function (runParams, serverState) { + // в фабрике можно на основе стейта вернуть готовый модуль + return { + serverState, + doSomething: () => { + fetch(serverState.baseUrl + '/api/getData') + } + }; +} + +window.ModuleCompat = factory; +``` -## Использования загрузчика в реакт-приложении +Серверный эндпоинт для таких модулей создается абсолютно так же, как и для монтируемых модулей. -Для того чтобы упростить работу с загрузчиком в реакт-приложении, мы предоставляем хук `useModuleLoader`: +### Подключение модулей-фабрик в приложении потребителе -```tsx -import { createModuleLoader, useModuleLoader, createModuleFetcher } from '@alfalab/scripts-modules'; +Для подключения модулей-фабрик можно использовать хук `useModuleFactory`: -const loader = createModuleLoader({ +```tsx +import { + createModuleLoader, + createModuleFetcher, + FactoryModule, + useModuleFactory, +} from '@alfalab/scripts-modules'; + +const loader = createModuleLoader({ + hostAppId: 'bar-app', moduleId: 'test', - getModuleResources: createModuleFetcher({ - baseUrl: 'http://localhost:8081', + getModuleResources: createModuleFetcher({ // или createServerStateModuleFetcher для модулей с серверным состоянием + baseUrl: 'https://examle.com/foo-app', }), }); -const MyComponent = () => { - const { loadingState, module, resources } = useModuleLoader(loader); // вторым параметром можно передать параметры, которые будут переданы в getModuleResources +export const MyAwesomeComponent = () => { + const { loadingState, module } = useModuleFactory({ loader }); return (
- {loadingState === 'loading' &&
Loading...
} - {loadingState === 'error' &&
Error
} - {loadingState === 'success' && ( -
-
Module loaded
-
{module}
{/* модуль, который вы загрузили. Тут будет доступно всё, что было экспортировано из модуля */} -
{resources}
-
- )} + { loadingState === 'pending' &&
pending...
} + { loadingState === 'rejected' &&
Error
} +
{ JSON.stringify(module) }
{/* модуль будет содержать то, что вернула фабрика */}
); -}; +} +``` + +Если вы хотите использовать модуль-фабрику вне реакт-компонента, вы можете использовать другой метод: + +```ts +import { + createModuleLoader, + createModuleFetcher, + FactoryModule, + executeModuleFactory, +} from '@alfalab/scripts-modules'; + +const loader = createModuleLoader(/*...*/); + +(async () => { + const loaderResult = await loader(); + + const executionResult = executeModuleFactory( + result.module, + result.moduleResources.moduleState, + {}, // опциональные run-параметры модуля + ); + + console.log(executionResult); // Тут будет то, что возвращает модуль-фабрика +})(); ``` -### Использование монтируемых модулей +## Абстрактные модули + +Для совсем сложных кейсов, когда вам нужен полный контроль над всем тем, что делает код из модуля, вы можете использовать +абстрактные модули. Они могут иметь абсолютно любую структуру, экспортировать из себя любые методы и константы. + +```tsx +// src/modules/abstract-module/index.ts +export const doSomething = () => { + console.log('Hello from abstract module!'); +}; + +export const publicConstant = 3.14; + +window.AbstractModule = { // для compat модулей + doSomething, + publicConstant, +}; +``` -Для работы с монтируемыми модулями так же есть готовый хук `useModuleMounter`: +Для подключения вы можете использовать хук `useModuleLoader`: ```tsx -import { createModuleLoader, useModuleMounter, createModuleFetcher } from '@alfalab/scripts-modules'; +import { + createModuleLoader, + createModuleFetcher, + useModuleLoader, +} from '@alfalab/scripts-modules'; + +type CustomModule = { + doSomething: () => void; + publicConstant: number; +} -const loader = createModuleLoader({ +const loader = createModuleLoader({ + hostAppId: 'bar-app', moduleId: 'test', getModuleResources: createModuleFetcher({ - baseUrl: 'http://localhost:8081', + baseUrl: 'https://examle.com/foo-app', }), }); -const MyComponent = () => { - const { loadingState, targetElementRef } = useModuleMounter({ - loader, - loaderParams: {}, // параметры, которые будут переданы в getModuleResources, опционально - runParams: {}, // параметры, которые будут переданы в mount функцию модуля, опционально - }); +export const MyAwesomeComponent = () => { + const { loadingState, module } = useModuleLoader({ loader }); return (
- {loadingState === 'loading' &&
Loading...
} - {loadingState === 'error' &&
Error
} -
{/* сюда будет монтироваться модуль */} + { loadingState === 'pending' &&
pending...
} + { loadingState === 'rejected' &&
Error
} +
{ JSON.stringify(module) }
{/* модуль будет содержать то, что экспортирует модуль, то есть doSomething и publicConstant */}
); -}; +} ``` +# Тестирование модулей +Поскольку в общем случае модули представляют собой простой js код - для тестирования вы можете пользоваться любыми привычными вам инструментами. + +Для тестирования модулей в cypress или playwright вы можете создать отдельный эндпоинт в вашем приложении, который будет +подключать модуль в ваше же приложение. + + # Документация API ## Конфигурация модулей From 6e64c05045f9c09ab5974863a488854d7dc8b579 Mon Sep 17 00:00:00 2001 From: Aleksandr Kitov Date: Wed, 4 Oct 2023 15:08:54 +0200 Subject: [PATCH 2/2] chore(modules): add types in example module usages --- .../global-definitions.d.ts | 5 +++ .../module-loader/hooks/use-module-mounter.ts | 2 + .../src/module-loader/types.ts | 8 ---- packages/arui-scripts-modules/tsconfig.json | 1 + .../src/modules/module-abstract/index.ts | 4 +- .../src/modules/module-compat/index.tsx | 41 +++++++++---------- .../src/modules/module/index.tsx | 2 +- .../server-state-module-compat/index.tsx | 7 ++-- .../server-state-module-compat.tsx | 2 +- .../src/modules/server-state-module/index.tsx | 4 +- ...tateModule.tsx => server-state-module.tsx} | 4 +- .../src/module-mounters/abstract-module.tsx | 7 +++- .../module-mounters/compat-module-mounter.tsx | 3 +- .../server-state-compat-module-mounter.tsx | 7 +++- .../server-state-factory-module-mounter.tsx | 6 ++- 15 files changed, 55 insertions(+), 48 deletions(-) create mode 100644 packages/arui-scripts-modules/global-definitions.d.ts rename packages/example-modules/src/modules/server-state-module/{ServerStateModule.tsx => server-state-module.tsx} (69%) diff --git a/packages/arui-scripts-modules/global-definitions.d.ts b/packages/arui-scripts-modules/global-definitions.d.ts new file mode 100644 index 00000000..d7137fd6 --- /dev/null +++ b/packages/arui-scripts-modules/global-definitions.d.ts @@ -0,0 +1,5 @@ +/* eslint-disable @typescript-eslint/naming-convention,no-underscore-dangle */ +declare const __webpack_share_scopes__: { + default: unknown; +}; +/* eslint-enable @typescript-eslint/naming-convention,no-underscore-dangle */ diff --git a/packages/arui-scripts-modules/src/module-loader/hooks/use-module-mounter.ts b/packages/arui-scripts-modules/src/module-loader/hooks/use-module-mounter.ts index 8fa074ec..b854d284 100644 --- a/packages/arui-scripts-modules/src/module-loader/hooks/use-module-mounter.ts +++ b/packages/arui-scripts-modules/src/module-loader/hooks/use-module-mounter.ts @@ -100,6 +100,8 @@ export function useModuleMounter Promise; get(id: string): Promise<() => T>; }; - -declare global { - /* eslint-disable @typescript-eslint/naming-convention,no-underscore-dangle */ - const __webpack_share_scopes__: { - default: unknown; - }; - /* eslint-enable @typescript-eslint/naming-convention,no-underscore-dangle */ -} diff --git a/packages/arui-scripts-modules/tsconfig.json b/packages/arui-scripts-modules/tsconfig.json index dd215e0d..918d6571 100644 --- a/packages/arui-scripts-modules/tsconfig.json +++ b/packages/arui-scripts-modules/tsconfig.json @@ -9,6 +9,7 @@ }, "include": [ "src/**/*.ts", + "global-definitions.d.ts" ], "exclude": ["build"] } diff --git a/packages/example-modules/src/modules/module-abstract/index.ts b/packages/example-modules/src/modules/module-abstract/index.ts index 41e690d9..1bacd623 100644 --- a/packages/example-modules/src/modules/module-abstract/index.ts +++ b/packages/example-modules/src/modules/module-abstract/index.ts @@ -1,3 +1,5 @@ +import { WindowWithModule } from '@alfalab/scripts-modules'; + /** * Этот модуль является "абстрактным", то есть он не имеет какой-либо заданной структуры. Он может содержать любые * функции, переменные, классы, интерфейсы, типы и т.д. @@ -9,7 +11,7 @@ export function justSomeRandomFunctionThatWeWantToExport() { export const someRandomVariableThatWeWantToExport = 'hello'; -(window as any).ModuleAbstract = { +(window as WindowWithModule).ModuleAbstract = { justSomeRandomFunctionThatWeWantToExport, someRandomVariableThatWeWantToExport, }; diff --git a/packages/example-modules/src/modules/module-compat/index.tsx b/packages/example-modules/src/modules/module-compat/index.tsx index 2c4f4002..430c921e 100644 --- a/packages/example-modules/src/modules/module-compat/index.tsx +++ b/packages/example-modules/src/modules/module-compat/index.tsx @@ -3,21 +3,21 @@ import React from 'react'; import { render } from 'react-dom'; -import type { - ModuleMountFunction, - ModuleUnmountFunction, - WindowWithMountableModule, -} from '@alfalab/scripts-modules'; +import type { WindowWithModule } from '@alfalab/scripts-modules'; +import { MountableModule } from '@alfalab/scripts-modules/src/module-loader/module-types'; import { PostcssFeatures } from '#/shared/postcss-features'; import './styles.css'; -const mountModule: ModuleMountFunction = (targetNode, runParams) => { - console.log('ModuleCompat: mount', { runParams }); +type ModuleType = MountableModule>; - if (targetNode) { - targetNode.innerHTML = ` +const ModuleCompat: ModuleType = { + mount: (targetNode, runParams) => { + console.log('ModuleCompat: mount', { runParams }); + + if (targetNode) { + targetNode.innerHTML = `
Это модуль ModuleCompat, который был загружен в режиме compat. @@ -32,19 +32,16 @@ const mountModule: ModuleMountFunction = (targetNode, runParams) => {
`; - render(, document.getElementById('postcss-example')); - } -}; - -const unmountModule: ModuleUnmountFunction = (targetNode) => { - console.log('ModuleCompat: cleanup'); + render(, document.getElementById('postcss-example')); + } + }, + unmount: (targetNode) => { + console.log('ModuleCompat: cleanup'); - if (targetNode) { - targetNode.innerHTML = ''; - } + if (targetNode) { + targetNode.innerHTML = ''; + } + }, }; -(window as WindowWithMountableModule).ModuleCompat = { - mount: mountModule, - unmount: unmountModule, -}; +(window as WindowWithModule).ModuleCompat = ModuleCompat; diff --git a/packages/example-modules/src/modules/module/index.tsx b/packages/example-modules/src/modules/module/index.tsx index 2de77c32..d8b62532 100644 --- a/packages/example-modules/src/modules/module/index.tsx +++ b/packages/example-modules/src/modules/module/index.tsx @@ -5,7 +5,7 @@ import type { ModuleMountFunction, ModuleUnmountFunction } from '@alfalab/script import { Module } from './Module'; -export const mount: ModuleMountFunction = (targetNode, runParams, serverState) => { +export const mount: ModuleMountFunction = (targetNode, runParams, serverState) => { console.log('Module: mount', { runParams, serverState }); if (!targetNode) { throw new Error('Target node is not defined for module'); diff --git a/packages/example-modules/src/modules/server-state-module-compat/index.tsx b/packages/example-modules/src/modules/server-state-module-compat/index.tsx index e46d981d..881b9a2a 100644 --- a/packages/example-modules/src/modules/server-state-module-compat/index.tsx +++ b/packages/example-modules/src/modules/server-state-module-compat/index.tsx @@ -1,11 +1,11 @@ import React from 'react'; import ReactDOM from 'react-dom'; -import type { ModuleMountFunction, ModuleUnmountFunction } from '@alfalab/scripts-modules'; +import type { ModuleMountFunction, ModuleUnmountFunction, WindowWithModule } from '@alfalab/scripts-modules'; import { ServerStateModuleCompat } from '#/modules/server-state-module-compat/server-state-module-compat'; -const mountModule: ModuleMountFunction = (targetNode, runParams, serverState) => { +const mountModule: ModuleMountFunction> = (targetNode, runParams, serverState) => { console.log('ServerStateModuleCompat: mount', { runParams, serverState }); ReactDOM.render( @@ -22,8 +22,7 @@ const unmountModule: ModuleUnmountFunction = (targetNode) => { } }; -// TODO: типизировать -(window as any).ServerStateModuleCompat = { +(window as WindowWithModule).ServerStateModuleCompat = { mount: mountModule, unmount: unmountModule, }; diff --git a/packages/example-modules/src/modules/server-state-module-compat/server-state-module-compat.tsx b/packages/example-modules/src/modules/server-state-module-compat/server-state-module-compat.tsx index fb914154..234f052e 100644 --- a/packages/example-modules/src/modules/server-state-module-compat/server-state-module-compat.tsx +++ b/packages/example-modules/src/modules/server-state-module-compat/server-state-module-compat.tsx @@ -1,6 +1,6 @@ import React from 'react'; -export const ServerStateModuleCompat = (props: { runParams: any; serverState: any }) => ( +export const ServerStateModuleCompat = (props: { runParams: Record; serverState: Record }) => (
Это модуль ServerStateModuleCompat, который был загружен в режиме compat. Главное его отличие от ModuleCompat - это то, что при его загрузке на страницу, он сразу же получает diff --git a/packages/example-modules/src/modules/server-state-module/index.tsx b/packages/example-modules/src/modules/server-state-module/index.tsx index 211c293b..61535d44 100644 --- a/packages/example-modules/src/modules/server-state-module/index.tsx +++ b/packages/example-modules/src/modules/server-state-module/index.tsx @@ -5,9 +5,9 @@ import ReactDOM from 'react-dom'; import type { ModuleMountFunction, ModuleUnmountFunction } from '@alfalab/scripts-modules'; -import { ServerStateModule } from './ServerStateModule'; +import { ServerStateModule } from './server-state-module'; -const mount: ModuleMountFunction = (targetNode, runParams, serverState) => { +const mount: ModuleMountFunction> = (targetNode, runParams, serverState) => { console.log('ServerStateModule: mount', { runParams, serverState }); ReactDOM.render( , diff --git a/packages/example-modules/src/modules/server-state-module/ServerStateModule.tsx b/packages/example-modules/src/modules/server-state-module/server-state-module.tsx similarity index 69% rename from packages/example-modules/src/modules/server-state-module/ServerStateModule.tsx rename to packages/example-modules/src/modules/server-state-module/server-state-module.tsx index 095e37cd..1608e2d5 100644 --- a/packages/example-modules/src/modules/server-state-module/ServerStateModule.tsx +++ b/packages/example-modules/src/modules/server-state-module/server-state-module.tsx @@ -1,8 +1,6 @@ -/* eslint-disable unicorn/filename-case */ -// TODO: remove eslint-disable import React from 'react'; -export const ServerStateModule = (props: { runParams: any; serverState: any }) => ( +export const ServerStateModule = (props: { runParams: Record; serverState: Record }) => (

ServerStateModule

diff --git a/packages/example/src/module-mounters/abstract-module.tsx b/packages/example/src/module-mounters/abstract-module.tsx index 52d3bd39..795482c5 100644 --- a/packages/example/src/module-mounters/abstract-module.tsx +++ b/packages/example/src/module-mounters/abstract-module.tsx @@ -4,7 +4,12 @@ import { Spinner } from '@alfalab/core-components/spinner'; import { Underlay } from '@alfalab/core-components/underlay'; import { createModuleFetcher, createModuleLoader, useModuleLoader } from '@alfalab/scripts-modules'; -const loader = createModuleLoader({ +type ModuleType = { + justSomeRandomFunctionThatWeWantToExport: () => void; + someRandomVariableThatWeWantToExport: string; +} + +const loader = createModuleLoader({ hostAppId: 'example', moduleId: 'ModuleAbstract', getModuleResources: createModuleFetcher({ diff --git a/packages/example/src/module-mounters/compat-module-mounter.tsx b/packages/example/src/module-mounters/compat-module-mounter.tsx index bf4a93df..d4ce7af6 100644 --- a/packages/example/src/module-mounters/compat-module-mounter.tsx +++ b/packages/example/src/module-mounters/compat-module-mounter.tsx @@ -3,14 +3,13 @@ import React from 'react'; import { Spinner } from '@alfalab/core-components/spinner'; import { Underlay } from '@alfalab/core-components/underlay'; import { - BaseModuleState, createModuleFetcher, createModuleLoader, MountableModule, useModuleMounter, } from '@alfalab/scripts-modules'; -const loader = createModuleLoader>({ +const loader = createModuleLoader({ hostAppId: 'example', moduleId: 'ModuleCompat', getModuleResources: createModuleFetcher({ diff --git a/packages/example/src/module-mounters/server-state-compat-module-mounter.tsx b/packages/example/src/module-mounters/server-state-compat-module-mounter.tsx index 2a3a8d15..16bd11de 100644 --- a/packages/example/src/module-mounters/server-state-compat-module-mounter.tsx +++ b/packages/example/src/module-mounters/server-state-compat-module-mounter.tsx @@ -3,14 +3,17 @@ import React from 'react'; import { Spinner } from '@alfalab/core-components/spinner'; import { Underlay } from '@alfalab/core-components/underlay'; import { - BaseModuleState, createModuleLoader, createServerStateModuleFetcher, MountableModule, useModuleMounter, } from '@alfalab/scripts-modules'; -const loader = createModuleLoader, { something: string }>({ +type ModuleRunParams = { + test: string; +} + +const loader = createModuleLoader, { something: string }>({ hostAppId: 'example', moduleId: 'ServerStateModuleCompat', getModuleResources: createServerStateModuleFetcher({ baseUrl: 'http://localhost:8082' }), diff --git a/packages/example/src/module-mounters/server-state-factory-module-mounter.tsx b/packages/example/src/module-mounters/server-state-factory-module-mounter.tsx index e4aff876..bbcb3048 100644 --- a/packages/example/src/module-mounters/server-state-factory-module-mounter.tsx +++ b/packages/example/src/module-mounters/server-state-factory-module-mounter.tsx @@ -16,7 +16,11 @@ type FactoryModuleRunParams = { placementId: string; }; -type ModuleType = FactoryModule, FactoryModuleRunParams>; +type FactoryModuleType = { + reloadPage: () => void; +} + +type ModuleType = FactoryModule; const loader = createModuleLoader({ hostAppId: 'example',