Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(scripts-module): add runParams for factory modules #98

Merged
merged 2 commits into from
Aug 28, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/cuddly-sloths-fry.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@alfalab/scripts-server": patch
---

Удалены неиспользуемые типы.
12 changes: 12 additions & 0 deletions .changeset/good-buses-hug.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
"@alfalab/scripts-modules": minor
---

- Добавлено описание типов для Factory-модулей, доступно как `FactoryModule`.

- Factory-модули теперь могут принимать `runParams` - отдельный параметр, который не будет передаваться на сервер, а
сразу будет попадать из клиента в клиент.
**Важно:** `runParams` теперь является первым аргументом для `run` функции.

- Mountable модули теперь могут использовать запись вида `export default mountableModule;` вместо отдельных экспортов
`mount` и `unmount` функций.
Original file line number Diff line number Diff line change
@@ -1,20 +1,31 @@
import { BaseModuleState, Loader } from '../types';
import { useEffect, useState } from 'react';
import { BaseModuleState, Loader } from '../types';
import { FactoryModule } from '../module-types';
import { LoadingState } from './types';

export type UseModuleFactoryParams<LoaderParams, FactoryParams extends BaseModuleState> = {
import { unwrapDefaultExport } from '../utils/unwrap-default-export';

export type UseModuleFactoryParams<
LoaderParams,
ServerState extends BaseModuleState,
ModuleExportType = any,
RunParams = void,
> = {
/**
* Загрузчик модуля
*/
loader: Loader<LoaderParams, any>;
loader: Loader<LoaderParams, FactoryModule<ModuleExportType, RunParams, ServerState>>;
/**
* Параметры, которые будут переданы в loader
* Параметры, которые будут переданы в загрузчик (и будут переданы на сервер модуля)
*/
loaderParams?: LoaderParams;
/**
* Функция, который позволяет дополнить/изменить параметры для фабрики
* Параметры, которые будут переданы в run-функцию модуля
*/
runParams?: RunParams;
/**
* Функция, который позволяет дополнить/изменить серверный стейт модуля перед вызовом фабрики
*/
getFactoryParams?: (params: FactoryParams) => FactoryParams;
getFactoryParams?: (params: ServerState) => ServerState;
}

export type UseModuleFactoryResult<ModuleExportType> = {
Expand All @@ -28,11 +39,17 @@ export type UseModuleFactoryResult<ModuleExportType> = {
module: ModuleExportType | undefined;
}

export function useModuleFactory<LoaderParams, FactoryParams extends BaseModuleState, ModuleExportType = any>({
export function useModuleFactory<
LoaderParams,
ServerState extends BaseModuleState,
ModuleExportType = any,
RunParams = void,
>({
loader,
loaderParams,
runParams,
getFactoryParams,
}: UseModuleFactoryParams<LoaderParams, FactoryParams>): UseModuleFactoryResult<ModuleExportType> {
}: UseModuleFactoryParams<LoaderParams, ServerState, ModuleExportType, RunParams>): UseModuleFactoryResult<ModuleExportType> {
const [loadingState, setLoadingState] = useState<LoadingState>('unknown');
const [module, setModule] = useState<ModuleExportType | undefined>();

Expand All @@ -48,9 +65,9 @@ export function useModuleFactory<LoaderParams, FactoryParams extends BaseModuleS

unmountFn = result.unmount;

const factoryParams = getFactoryParams ?
await getFactoryParams(result.moduleResources.moduleState as FactoryParams) :
result.moduleResources.moduleState
const serverState = (getFactoryParams
? await getFactoryParams(result.moduleResources.moduleState as ServerState)
: result.moduleResources.moduleState) as ServerState;

let moduleResult: ModuleExportType;

Expand All @@ -59,21 +76,15 @@ export function useModuleFactory<LoaderParams, FactoryParams extends BaseModuleS
* Для compat модулей фабрику можно записать прямо в window
* Для compat и для mf модулей делаем также возможным записи в поля factory и default
*/
if (typeof result.module === 'function') {
const unwrappedModule = unwrapDefaultExport(result.module);

moduleResult = await result.module(factoryParams);

} else if (result.module.default && typeof result.module.default === 'function') {

moduleResult = await result.module.default(factoryParams);

} else if (result.module.factory && typeof result.module.factory === 'function') {

moduleResult = await result.module.factory(factoryParams);
if (typeof unwrappedModule === 'function') {
moduleResult = await unwrappedModule(runParams as RunParams, serverState);
} else if (unwrappedModule.factory && typeof unwrappedModule.factory === 'function') {
moduleResult = await unwrappedModule.factory(runParams as RunParams, serverState);
} else {

throw new Error(
`Module ${factoryParams.hostAppId} does not present a factory function,
`Module ${serverState.hostAppId} does not present a factory function,
try usign another hook, e.g. 'useModuleLoader' or 'useModuleMounter'`
)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
import { BaseModuleState, Loader, MountableModule } from '../types';
import { BaseModuleState, Loader } from '../types';
import { useCallback, useEffect, useState } from 'react';
import { LoadingState } from './types';
import { MountableModule } from '../module-types';
import { unwrapDefaultExport } from '../utils/unwrap-default-export';

export type UseModuleLoaderParams<LoaderParams, RunParams, ServerState extends BaseModuleState> = {
/**
* Загрузчик модуля
*/
loader: Loader<LoaderParams, MountableModule<RunParams, ServerState>>;
/**
* Параметры, которые будут переданы в loader
* Параметры, которые будут переданы в загрузчик (и будут переданы на сервер модуля)
*/
loaderParams?: LoaderParams;
/**
Expand Down Expand Up @@ -69,11 +71,12 @@ export function useModuleMounter<LoaderParams, RunParams, ServerState extends Ba
getResourcesParams: loaderParams as LoaderParams,
});

result.module.mount(targetNode, runParams as RunParams, result.moduleResources.moduleState as ServerState);
const module = unwrapDefaultExport(result.module);
module.mount(targetNode, runParams as RunParams, result.moduleResources.moduleState as ServerState);

unmountFn = () => {
result.unmount();
result.module.unmount(targetNode);
module.unmount(targetNode);
};
} catch (error) {
setLoadingState('rejected');
Expand Down
1 change: 1 addition & 0 deletions packages/arui-scripts-modules/src/module-loader/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export * from './types';
export * from './module-types';

export { createModuleLoader } from './create-module-loader';
export { createModuleFetcher } from './create-module-fetcher';
Expand Down
65 changes: 65 additions & 0 deletions packages/arui-scripts-modules/src/module-loader/module-types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { BaseModuleState } from './types';

// Типы для модулей "особого типа", то есть тех, чья форма определена заранее

/**
* Хелпер для удобного описания типов compat модулей
* @example (window as WindowWithModule).myAwesomeModule = { ... };
*/
export type WindowWithModule<ModuleType = unknown> = typeof window & {
[key: string]: ModuleType;
}

// --- Монтируемые модули ---

export type ModuleMountFunction<RunParams = void, ServerState extends BaseModuleState = BaseModuleState> =
(targetNode: HTMLElement, runParams: RunParams, serverState: ServerState) => void;

export type ModuleUnmountFunction = (targetNode: HTMLElement) => void;

/**
* Специальный тип модуля, который можно монтировать на страницу
* @template RunParams параметры, которые передаются в модуль с клиента
* @template ServerState состояние модуля, которое пришло с сервера
*/
export type MountableModule<RunParams = void, ServerState extends BaseModuleState = BaseModuleState> = {
/**
* Метод, с помощью которого модуль может прикрепиться к DOM
* @param targetNode DOM-нода, к которой нужно прикрепить модуль
* @param runParams параметры, которые передаются в модуль с клиента
* @param serverState состояние модуля, которое пришло с сервера
*/
mount: ModuleMountFunction<RunParams, ServerState>;
/**
* Метод, с помощью которого модуль должен открепиться от DOM
* @param targetNode DOM-нода, от которой нужно открепить модуль
*/
unmount: ModuleUnmountFunction;
};

/**
* Хелпер для удобного описания mountable compat модулей
*/
export type WindowWithMountableModule = WindowWithModule<MountableModule>;

// --- Модули-фабрики ---

/**
* Специальный тип модуля, который является фабрикой
* @template ServerState состояние модуля, которое пришло с сервера
* @template RunParams параметры, которые передаются в модуль с клиента
* @template ReturnType тип, который возвращает фабрика
*/
export type FactoryModuleFunction<
ReturnType = any,
RunParams = void,
ServerState extends BaseModuleState = BaseModuleState,
> = {
(runParams: RunParams, serverState: ServerState): ReturnType;
};

export type FactoryModule<ReturnType = any, RunParams = void, ServerState extends BaseModuleState = BaseModuleState> =
| FactoryModuleFunction<ReturnType, RunParams, ServerState>
| { factory: FactoryModuleFunction<ReturnType, RunParams, ServerState> };

export type WindowWithFactoryModule = WindowWithModule<FactoryModule>;
34 changes: 0 additions & 34 deletions packages/arui-scripts-modules/src/module-loader/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,40 +87,6 @@ export type Loader<GetResourcesParams, ModuleExportType = unknown> =

// Описание типов модулей

export type ModuleMountFunction<RunParams = void, ServerState extends BaseModuleState = BaseModuleState> =
(targetNode: HTMLElement, runParams: RunParams, serverState: ServerState) => void;

export type ModuleUnmountFunction = (targetNode: HTMLElement) => void;

// Специальный тип модуля, который можно монтировать на страницу
export type MountableModule<RunParams = void, ServerState extends BaseModuleState = BaseModuleState> = {
/**
* Метод, с помощью которого модуль может прикрепиться к DOM
* @param targetNode DOM-нода, к которой нужно прикрепить модуль
* @param runParams параметры, которые передаются в модуль с клиента
* @param serverState состояние модуля, которое пришло с сервера
*/
mount: ModuleMountFunction<RunParams, ServerState>;
/**
* Метод, с помощью которого модуль должен открепиться от DOM
* @param targetNode DOM-нода, от которой нужно открепить модуль
*/
unmount: ModuleUnmountFunction;
};

/**
* Хелпер для удобного описания типов compat модулей
* @example (window as WindowWithModule).myAwesomeModule = { ... };
*/
export type WindowWithModule<ModuleType = unknown> = typeof window & {
[key: string]: ModuleType;
}

/**
* Хелпер для удобного описания compat модулей
*/
export type WindowWithMountableModule = WindowWithModule<MountableModule>

export type ModuleFederationContainer = {
init: (...args: unknown[]) => Promise<void>;
get<T>(id: string): Promise<() => T>;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export function unwrapDefaultExport<ModuleExportType>(module: ModuleExportType): ModuleExportType {
return (module as any).default ?? module;
}
37 changes: 0 additions & 37 deletions packages/arui-scripts-server/src/modules/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,40 +9,3 @@ export type ModuleDescriptor<FrameworkParams extends unknown[] = []> = {
export type ModulesConfig<FrameworkParams extends unknown[] = []> = {
[moduleId: string]: ModuleDescriptor<FrameworkParams>;
};

/**
* Базовый тип конфигурации getResources плагина
*/
export type GetResourcesPluginConfig<
Req extends GetResourcesRequest,
Res extends ModuleResources,
> = {
/**
* Функция, которая должна возвращать имена чанков, относящихся к модулю.
* Последовательность, в которой возращаются имена чанков может быть важна, например vendor файл как правило должен идти перед main
* При использовании default режима можно не использовать эту функцию.
* @param moduleId
*/
getChunkNames?: (moduleId: string) => string[];
/**
* Функция, которая должна возвращать режим подключения модуля
* @param moduleId
*/
getMountMode: (moduleId: string) => MountMode;
/**
* Опциональная функция, которая должна возвращать строку с версией модуля. Если не передана - будет использоваться 'unknown'
* @param moduleId
*/
getModuleVersion?: (moduleId: string) => string;
/**
* Функция, которая должна возвращать состояние модуля
* @param moduleId
* @param request
* @param params
*/
getModuleState: (
moduleId: string,
request: Request,
params: Req['params'],
) => Promise<Res['moduleState']>;
};
50 changes: 24 additions & 26 deletions packages/arui-scripts/docs/modules.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,8 @@
Такие модули должны экспортировать две функции:

```tsx
export function mount(targetNode, runParams, serverState): void {
import { ModuleMountFunction, ModuleUnmountFunction } from '@alfalab/scripts-modules';
export const mount: ModuleMountFunction = (targetNode, runParams, serverState) => {
// здесь происходит монтирование модуля в хост-приложение
// targetNode - это DOM-нода, в которую нужно отрендерить модуль
// runParams - это параметры, которые были переданы при запуске модуля
Expand All @@ -80,7 +81,7 @@ export function mount(targetNode, runParams, serverState): void {
ReactDOM.render(<App preparedState={serverState} runParams={runParams} />, targetNode);
}

export function unmount(targetNode): void {
export const unmount: ModuleUnmountFunction = (targetNode) => {
// здесь происходит демонтирование модуля из хост-приложения
// Скорее всего это будет что-то вроде:
ReactDOM.unmountComponentAtNode(targetNode);
Expand All @@ -90,47 +91,44 @@ export function unmount(targetNode): void {
### Модули-фабрики
Модули-фабрики - это модули, которые поставляют фабрики, которые в свою очередь вызываются в рантайме со стейтом (клиентским или серверным, в зависимости от типа поставляемого модуля).

Такие модули должны экспортировать фабрику тремя возможными вариантами:

Такие модули должны экспортировать фабрику:

Для mf(default) модулей:

```tsx
export default function (moduleState) {
// в фабрике можно на основе стейта вернуть готовый модуль
import type { FactoryModule } from '@alfalab/scripts-modules';

const factory: FactoryModule = function (runParams, serverState) {
// serverState - это состояние, которое подготовлено на сервере модуля
// runParams - это параметры, которые были переданы при запуске модуля клиентом
// в фабрике можно на основе стейта вернуть готовый модуль
return {
moduleState,
serverState,
doSomething: () => {
fetch(moduleState.baseUrl + '/api/getData')
fetch(serverState.baseUrl + '/api/getData')
}
};
}

export default factory;
// или export { factory };
```
или:

```tsx
export const factory = function (moduleState) {
// в фабрике можно на основе стейта вернуть готовый модуль
для compat модулей:
```ts
import type { FactoryModule } from '@alfalab/scripts-modules';

const factory: FactoryModule = function (runParams, serverState) {
// в фабрике можно на основе стейта вернуть готовый модуль
return {
moduleState,
serverState,
doSomething: () => {
fetch(moduleState.baseUrl + '/api/getData')
fetch(serverState.baseUrl + '/api/getData')
}
};
}
```

для compat модулей:
```ts
// src/modules/module-compat/index.ts

window.ModuleCompat = (moduleState) => {
doSomething: () => {
fetch(moduleState.apiUrl);
},
publicConstant: 3.14,
// ...
};
window.ModuleCompat = factory;
```

## Как создать модуль
Expand Down
Loading