@tramvai/module-child-app 1.46.5

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/README.md ADDED
@@ -0,0 +1,256 @@
1
+ # ChildApp
2
+
3
+ Module for child app
4
+
5
+ ## Installation
6
+
7
+ First, install `@tramvai/module-child-app`
8
+
9
+ ```bash
10
+ yarn add @tramvai/module-child-app
11
+ ```
12
+
13
+ And then add module to your app
14
+
15
+ ```tsx
16
+ import { createApp } from '@tramvai/core';
17
+ import { ChildAppModule } from '@tramvai/module-child-app';
18
+
19
+ createApp({
20
+ name: 'tincoin',
21
+ modules: [ChildAppModule],
22
+ });
23
+ ```
24
+
25
+ ## Explanation
26
+
27
+ ### Terms
28
+
29
+ - `root-app` - basic tramvai-app constructed with `createApp` from `@tramvai/core`. It can connect with many child-app
30
+ - `child-app` - external microfrontend constructed with `createChildApp` from `@tramvai/child-app-core`. It is loaded by root-app and provides some external functionality
31
+ - `SingletonDI` - DI-container which is exist in single instance for app and exists as long as app itself
32
+ - `RequestDI` - DI-Container which is created for every request and represents specific data for single client. RequestDI inherits providers from SingletonDI and it is independent from other RequestDIs
33
+ - `CommandLineRunner` - instance of [CommandModule](references/modules/common.md#commandmodule)
34
+
35
+ ### DI
36
+
37
+ Every child-app has its own DI-hierarchy which is isolated from other child app and partially from root-app. The only way communicate fpr DIs it's getting providers from root-app di inside child-app.
38
+
39
+ Next picture shows connection between DI-containers in `root-app` and `child-app`s
40
+
41
+ ![di](/img/child-app/di.drawio.svg)
42
+
43
+ How does it work when we trying to get provider from DI in `child-app`:
44
+
45
+ 1. First check that provider is exist in the current DI-container. If it is then return it.
46
+ 2. If current DI is `RequestDI` then go to `SingletonDI` of `child-app` and look for provider.
47
+ 1. If it exists in `SingletonDI` then return it
48
+ 2. Go to `RequestDI` of `root-app` and if provider exists in it return it
49
+ 3. Go to `SingletonDI` of `root-app` and if provider exists in it return it
50
+ 4. Throw error otherwise
51
+ 3. If current DI is `SingletonDI` then go to `SingletonDI` of `root-app` and check for provider there
52
+ 1. If it exists then return it
53
+ 2. Throw error otherwise
54
+
55
+ ### CommandLineRunner
56
+
57
+ Each `child-app` has its own CommandLineRunner instance which allows to `child-app` make some preparations before the actual page render. This CommandLineRunner has almost identical lines as `root-app` to simplicity, but it is actually completely other line which are independent from lines in `root-app`
58
+
59
+ ![command-line-runner](/img/child-app/command-line-runner.drawio.svg)
60
+
61
+ All of the accepted line tokens:
62
+
63
+ ```ts
64
+ const command = {
65
+ customer: [
66
+ commandLineListTokens.customerStart,
67
+ commandLineListTokens.resolveUserDeps,
68
+ commandLineListTokens.resolvePageDeps,
69
+ ],
70
+ clear: [commandLineListTokens.clear],
71
+ spa: [
72
+ commandLineListTokens.resolveUserDeps,
73
+ commandLineListTokens.resolvePageDeps,
74
+ commandLineListTokens.spaTransition,
75
+ ],
76
+ };
77
+ ```
78
+
79
+ Child-app must be preloaded first to allow to execute commandline runner. In case of late preloading CommandLineRunner will be executed anyway but it will be out of sync with root-app CommandLineRunner (it will be called as soon as child-app code was loaded).
80
+
81
+ #### Server
82
+
83
+ - If child-app was preloaded before root-app `resolvePageDeps` then `customer` line list is executed on root-app `resolvePageDeps` line
84
+ - If child-app was preloaded on root-app `resolvePageDeps` then `customer` line list is executed as soon as child-app was loaded. `preload` call must be awaited in order to prevent root-app CommandLineRunner to passing to next line. That still counts as executing on `resolvePageDeps` line.
85
+ - Child-app `clear` line list is executed on root-app `clear` line for every child-app that was preloaded on previous lines
86
+
87
+ #### Client
88
+
89
+ ##### First Page load
90
+
91
+ - If child-app was preloaded on server `customer` line list is executed on root-app `resolvePageDeps` line
92
+ - If child-app was not preloaded on server but was preloaded on client then `customer` line list is executed on root-app `clear` line
93
+ - Child-app `clear` line list is executed on root-app `clear` line for every child-app that was preloaded on previous lines
94
+
95
+ ##### Spa-transitions
96
+
97
+ - If child-app was not preloaded on any previous pages before but was preloaded on next page then `customer` line list is executed as soon as child-app is loaded
98
+ - If child-app was preloaded on next page then child-app `spa` line list is executed on root-app `spaTransition` line
99
+
100
+ ### Loading Child App
101
+
102
+ Loading of child-app is happens only after preloading child-app with `CHILD_APP_PRELOAD_MANAGER`. This preloading loads code for a child-app and marks it to execution using [CommandLineRunner](#commandlinerunner).
103
+
104
+ ![loading](/img/child-app/loading.drawio.svg)
105
+
106
+ #### Server
107
+
108
+ - Calling `PreloadManager.preload(...)` loads a child-app code, executes and marks it as executable to CommandLineRunner
109
+ - Result of `PreloadManager.preload(...)` must be awaited as it is important to synchronize child-app commands lines execution with a root-app `CommandLinerRunner`
110
+ - Preloads after root-app `resolvePageDeps` are useless as they wont change page render and wont be used by root-app.
111
+ - If child-app was not preloaded at all but still is used on render then the child-app is still preloaded automatically, but it will lead to additional React render and may significantly increase response latency.
112
+
113
+ #### Client
114
+
115
+ - Calling `PreloadManager.preload(...)` loads a child-app code, executes and marks it as executable to CommandLineRunner
116
+ - Result of `PreloadManager.preload(...)` must be awaited as it is important to synchronize child-app commands lines execution with a root-app `CommandLinerRunner`
117
+ - If child-app was preloaded on server then child-app `customer` line list is executed on `resolvePageDeps` on first page render
118
+ - If child-app was not preloaded on server then actual loading and command-line execution are happens on root-app `clear` line as executing child-app before page render may break React hydration and should be executed only after it.
119
+ - On spa transition when previously child-app is preloaded it will be reused
120
+ - On spa transition if preloaded child-app was not loaded before it will be loaded and executed as soon as possible.
121
+
122
+ ### State
123
+
124
+ State Management is almost completely isolated from root-app and other of child-apps. Every child-app can register own stores, actions.
125
+
126
+ State for child-apps will be dehydrated on server as separate variable in the result html and then will be automatically rehydrated on client for every child-app.
127
+
128
+ :::warning Usually child-app cannot read data from root-app stores, but the dangerous workaround that allows to subscribe on any root-app store exists.
129
+
130
+ It may be done using `CHILD_APP_ROOT_STATE_SUBSCRIPTION_TOKEN` token.
131
+
132
+ This token is considered dangerous as it leads to high coupling with stores from root-app and this way stores in root-app might not change their public interface. But, in most cases, changes in stores ignore breaking change tracking and often breaks backward-compatibility. So **do not use this token if you can**, and if you should - use as little as possible from root-app and provide some fallback in case of wrong data. :::
133
+
134
+ ## API
135
+
136
+ ## How to
137
+
138
+ ### Connect a child app
139
+
140
+ 1. Place a child-app React component somewhere in your page render
141
+
142
+ ```ts
143
+ import React from 'react';
144
+ import { ChildApp } from '@tramvai/module-child-app';
145
+
146
+ export const Page = () => {
147
+ return (
148
+ <div>
149
+ ...
150
+ <ChildApp name="[name]" />
151
+ ...
152
+ </div>
153
+ );
154
+ };
155
+ ```
156
+
157
+ 1. Add configuration for child-app loading
158
+
159
+ ```ts
160
+ providers: [
161
+ provide({
162
+ provide: CHILD_APP_RESOLVE_BASE_URL_TOKEN, // or use `CHILD_APP_EXTERNAL_URL` env
163
+ useValue: 'http://localhost:4040/',
164
+ }),
165
+ provide({
166
+ provide: CHILD_APP_RESOLUTION_CONFIGS_TOKEN,
167
+ useValue: [
168
+ {
169
+ name: '[name]', // name of the child-app
170
+ byTag: {
171
+ latest: {
172
+ version: '[version]', // current version for the child app for tag `latest`
173
+ },
174
+ },
175
+ },
176
+ ],
177
+ }),
178
+ ];
179
+ ```
180
+
181
+ 1. Preload child-app execution in order to improve performance and allow child-app execute its data preparations
182
+
183
+ ```ts
184
+ import { commandLineListTokens, Provider, provide } from '@tramvai/core';
185
+ import { CHILD_APP_PRELOAD_MANAGER_TOKEN } from '@tramvai/module-child-app';
186
+
187
+ const providers: Provider[] = [
188
+ provide({
189
+ provide: commandLineListTokens.customerStart,
190
+ multi: true,
191
+ useFactory: ({ preloadManager }) => {
192
+ return function preloadHeaderChildApp() {
193
+ return preloadManager.preload({ name: '[name]' }); // this call is important
194
+ };
195
+ },
196
+ deps: {
197
+ preloadManager: CHILD_APP_PRELOAD_MANAGER_TOKEN,
198
+ },
199
+ }),
200
+ ];
201
+ ```
202
+
203
+ ### Debug child-app
204
+
205
+ #### Single child-app
206
+
207
+ 1. Run child-app using cli
208
+
209
+ ```sh
210
+ yarn tramvai start child-app
211
+ ```
212
+
213
+ 2. Run root-app with `CHILD_APP_DEBUG` environment variable
214
+
215
+ ```sh
216
+ CHILD_APP_DEBUG=child-app yarn tramvai start root-app
217
+ ```
218
+
219
+ #### Multiple child-app
220
+
221
+ 1. Run somehow multiple child-apps. They should be started on different ports.
222
+ 2. And either pass `Base Url` showed from cli as url to debug every child-app
223
+
224
+ ```sh
225
+ CHILD_APP_DEBUG=child-app1=baseUrl1;child-app2=baseUrl2 yarn tramvai start root-app
226
+ ```
227
+
228
+ 3. Or implement proxy on default `http:://localhost:4040/` yourself which redirects to concrete server by url
229
+
230
+ ```sh
231
+ CHILD_APP_DEBUG=child-app1;child-app2 yarn tramvai start root-app
232
+ ```
233
+
234
+ #### More detailed debug setup
235
+
236
+ You may specify a full config to debug to a specific child-app:
237
+
238
+ 1. To token `CHILD_APP_RESOLUTION_CONFIGS_TOKEN` for needed child-apps add special tag `debug`:
239
+ ```ts
240
+ ({
241
+ name: 'child-app',
242
+ byTag: {
243
+ latest: {
244
+ version: 'latest',
245
+ },
246
+ debug: {
247
+ baseUrl: '...url',
248
+ version: '...version',
249
+ client: {},
250
+ server: {},
251
+ css: {},
252
+ },
253
+ },
254
+ });
255
+ ```
256
+ 2. Run root-app with `CHILD_APP_DEBUG` environment variable with value of child-app names needed to debug
@@ -0,0 +1,3 @@
1
+ import type { Container } from '@tinkoff/dippy';
2
+ import type { Provider } from '@tramvai/core';
3
+ export declare const getChildProviders: (appDi: Container) => Provider[];
@@ -0,0 +1,16 @@
1
+ import type { ChildApp } from '@tramvai/child-app-core';
2
+ import type { ChildAppFinalConfig } from '@tramvai/tokens-child-app';
3
+ import type { LOGGER_TOKEN } from '@tramvai/tokens-common';
4
+ import { Loader } from '../shared/loader';
5
+ import type { ModuleFederationContainer } from '../shared/webpack/moduleFederation';
6
+ export declare const getModuleFromGlobal: (entry: string) => ModuleFederationContainer | void;
7
+ export declare class BrowserLoader extends Loader {
8
+ private readonly log;
9
+ private readonly initializedMap;
10
+ constructor({ logger }: {
11
+ logger: typeof LOGGER_TOKEN;
12
+ });
13
+ load(config: ChildAppFinalConfig): Promise<ChildApp | void>;
14
+ init(config: ChildAppFinalConfig): Promise<void>;
15
+ get(config: ChildAppFinalConfig): ChildApp | void;
16
+ }
@@ -0,0 +1,23 @@
1
+ import type { ChildAppCommandLineRunner, ChildAppRequestConfig, ChildAppLoader, ChildAppPreloadManager, CHILD_APP_RESOLVE_CONFIG_TOKEN } from '@tramvai/tokens-child-app';
2
+ import type { STORE_TOKEN } from '@tramvai/tokens-common';
3
+ export declare class PreloadManager implements ChildAppPreloadManager {
4
+ private loader;
5
+ private runner;
6
+ private resolveExternalConfig;
7
+ private pageHasLoaded;
8
+ private map;
9
+ private serverPreloaded;
10
+ private preloadMap;
11
+ constructor({ loader, runner, resolveExternalConfig, store, }: {
12
+ loader: ChildAppLoader;
13
+ runner: ChildAppCommandLineRunner;
14
+ resolveExternalConfig: typeof CHILD_APP_RESOLVE_CONFIG_TOKEN;
15
+ store: typeof STORE_TOKEN;
16
+ });
17
+ preload(request: ChildAppRequestConfig): Promise<void>;
18
+ isPreloaded(request: ChildAppRequestConfig): boolean;
19
+ runPreloaded(): Promise<void>;
20
+ clearPreloaded(): Promise<void>;
21
+ getPreloadedList(): ChildAppRequestConfig[];
22
+ private run;
23
+ }
@@ -0,0 +1,7 @@
1
+ import type { Provider } from '@tinkoff/dippy';
2
+ declare global {
3
+ interface Window {
4
+ childAppInitialState: string;
5
+ }
6
+ }
7
+ export declare const browserProviders: Provider[];
@@ -0,0 +1,20 @@
1
+ import type { Container } from '@tinkoff/dippy';
2
+ import type { ChildAppDiManager, ChildAppPreloadManager, ChildAppRenderManager, ChildAppRequestConfig, CHILD_APP_RESOLVE_CONFIG_TOKEN } from '@tramvai/tokens-child-app';
3
+ import type { LOGGER_TOKEN } from '@tramvai/tokens-common';
4
+ export declare class RenderManager implements ChildAppRenderManager {
5
+ private readonly preloadManager;
6
+ private readonly diManager;
7
+ private readonly resolveExternalConfig;
8
+ private readonly log;
9
+ private readonly hasRenderedSet;
10
+ private readonly loadingInProgress;
11
+ constructor({ logger, preloadManager, diManager, resolveExternalConfig, }: {
12
+ logger: typeof LOGGER_TOKEN;
13
+ preloadManager: ChildAppPreloadManager;
14
+ diManager: ChildAppDiManager;
15
+ resolveExternalConfig: typeof CHILD_APP_RESOLVE_CONFIG_TOKEN;
16
+ });
17
+ getChildDi(request: ChildAppRequestConfig): [Container | null, null | Promise<Container | null>];
18
+ flush(): Promise<boolean>;
19
+ clear(): void;
20
+ }
@@ -0,0 +1,3 @@
1
+ export * from './export';
2
+ export declare class ChildAppModule {
3
+ }
@@ -0,0 +1,2 @@
1
+ export * from '@tramvai/tokens-child-app';
2
+ export { ChildApp } from './shared/react/component';
@@ -0,0 +1,3 @@
1
+ import type { Container } from '@tinkoff/dippy';
2
+ import type { Provider } from '@tramvai/core';
3
+ export declare const getChildProviders: (appDi: Container) => Provider[];
@@ -0,0 +1,16 @@
1
+ import type { ChildApp } from '@tramvai/child-app-core';
2
+ import type { ChildAppFinalConfig } from '@tramvai/tokens-child-app';
3
+ import type { CREATE_CACHE_TOKEN, LOGGER_TOKEN } from '@tramvai/tokens-common';
4
+ import { Loader } from '../shared/loader';
5
+ export declare class ServerLoader extends Loader {
6
+ private readonly loader;
7
+ private readonly initializedMap;
8
+ private internalLoadCache;
9
+ constructor({ logger, createCache, }: {
10
+ logger: typeof LOGGER_TOKEN;
11
+ createCache: typeof CREATE_CACHE_TOKEN;
12
+ });
13
+ load(config: ChildAppFinalConfig): Promise<ChildApp | void>;
14
+ init(config: ChildAppFinalConfig): Promise<void>;
15
+ get(config: ChildAppFinalConfig): ChildApp | void;
16
+ }
@@ -0,0 +1,25 @@
1
+ import type { ChildAppCommandLineRunner, ChildAppRequestConfig, ChildAppLoader, ChildAppPreloadManager, ChildAppStateManager, CHILD_APP_RESOLVE_CONFIG_TOKEN, CHILD_APP_PRELOAD_EXTERNAL_CONFIG_TOKEN } from '@tramvai/tokens-child-app';
2
+ export declare class PreloadManager implements ChildAppPreloadManager {
3
+ private loader;
4
+ private runner;
5
+ private stateManager;
6
+ private preloadExternalConfig;
7
+ private readonly resolveFullConfig;
8
+ private shouldRunImmediately;
9
+ private map;
10
+ private preloadMap;
11
+ private configHasBeenPreloaded;
12
+ constructor({ loader, runner, stateManager, preloadExternalConfig, resolveFullConfig, }: {
13
+ loader: ChildAppLoader;
14
+ runner: ChildAppCommandLineRunner;
15
+ stateManager: ChildAppStateManager;
16
+ resolveFullConfig: typeof CHILD_APP_RESOLVE_CONFIG_TOKEN;
17
+ preloadExternalConfig: typeof CHILD_APP_PRELOAD_EXTERNAL_CONFIG_TOKEN | null;
18
+ });
19
+ preload(request: ChildAppRequestConfig): Promise<void>;
20
+ isPreloaded(request: ChildAppRequestConfig): boolean;
21
+ runPreloaded(): Promise<void>;
22
+ clearPreloaded(): Promise<void>;
23
+ getPreloadedList(): ChildAppRequestConfig[];
24
+ private run;
25
+ }
@@ -0,0 +1,2 @@
1
+ import type { Provider } from '@tinkoff/dippy';
2
+ export declare const serverProviders: Provider[];
@@ -0,0 +1,8 @@
1
+ import type { ChildAppDiManager, ChildAppPreloadManager, CHILD_APP_RESOLVE_CONFIG_TOKEN } from '@tramvai/tokens-child-app';
2
+ import type { LOGGER_TOKEN } from '@tramvai/tokens-common';
3
+ export declare const registerChildAppRenderSlots: ({ logger, diManager, resolveFullConfig, preloadManager, }: {
4
+ logger: typeof LOGGER_TOKEN;
5
+ diManager: ChildAppDiManager;
6
+ resolveFullConfig: typeof CHILD_APP_RESOLVE_CONFIG_TOKEN;
7
+ preloadManager: ChildAppPreloadManager;
8
+ }) => (import("@tramvai/tokens-render").PageResource | import("@tramvai/tokens-render").PageResource[])[][];
@@ -0,0 +1,24 @@
1
+ import type { CUSTOM_RENDER } from '@tramvai/tokens-render';
2
+ import type { Container } from '@tinkoff/dippy';
3
+ import type { ChildAppDiManager, ChildAppPreloadManager, ChildAppRenderManager, ChildAppRequestConfig, CHILD_APP_RESOLVE_CONFIG_TOKEN } from '@tramvai/tokens-child-app';
4
+ import type { LOGGER_TOKEN } from '@tramvai/tokens-common';
5
+ export declare const customRender: ({ renderManager, }: {
6
+ renderManager: ChildAppRenderManager;
7
+ }) => typeof CUSTOM_RENDER;
8
+ export declare class RenderManager implements ChildAppRenderManager {
9
+ private readonly preloadManager;
10
+ private readonly diManager;
11
+ private readonly resolveFullConfig;
12
+ private readonly log;
13
+ private readonly hasRenderedSet;
14
+ private readonly loadingInProgress;
15
+ constructor({ logger, preloadManager, diManager, resolveFullConfig, }: {
16
+ logger: typeof LOGGER_TOKEN;
17
+ preloadManager: ChildAppPreloadManager;
18
+ diManager: ChildAppDiManager;
19
+ resolveFullConfig: typeof CHILD_APP_RESOLVE_CONFIG_TOKEN;
20
+ });
21
+ getChildDi(request: ChildAppRequestConfig): [Container | null, null | Promise<Container | null>];
22
+ flush(): Promise<boolean>;
23
+ clear(): void;
24
+ }
@@ -0,0 +1,13 @@
1
+ import type { ChildAppDiManager, ChildAppStateManager, ChildAppFinalConfig } from '@tramvai/tokens-child-app';
2
+ import type { LOGGER_TOKEN } from '@tramvai/tokens-common';
3
+ export declare class StateManager implements ChildAppStateManager {
4
+ private readonly log;
5
+ private readonly diManager;
6
+ state: any;
7
+ constructor({ logger, diManager, }: {
8
+ logger: typeof LOGGER_TOKEN;
9
+ diManager: ChildAppDiManager;
10
+ });
11
+ registerChildApp(config: ChildAppFinalConfig): void;
12
+ getState(): void;
13
+ }