@tramvai/module-child-app 4.41.98 → 4.41.100
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/lib/server/module-federation/best-loaded-shared-modules.d.ts +15 -0
- package/lib/server/module-federation/best-loaded-shared-modules.es.js +26 -0
- package/lib/server/module-federation/best-loaded-shared-modules.js +30 -0
- package/lib/server/module-federation/best-shared-modules.d.ts +15 -0
- package/lib/server/module-federation/utils.d.ts +18 -0
- package/lib/server/module-federation/utils.es.js +38 -0
- package/lib/server/module-federation/utils.js +43 -0
- package/lib/server/render-slots.es.js +64 -48
- package/lib/server/render-slots.js +64 -48
- package/lib/shared/webpack/moduleFederation.browser.js +43 -0
- package/lib/shared/webpack/moduleFederation.d.ts +27 -18
- package/lib/shared/webpack/moduleFederation.es.js +43 -0
- package/lib/shared/webpack/moduleFederation.js +43 -0
- package/package.json +11 -11
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { SharedModule, SharedScopeItem } from './utils';
|
|
2
|
+
export type SharedModuleWithChunks = {
|
|
3
|
+
shareKey: string;
|
|
4
|
+
from: string;
|
|
5
|
+
version: string;
|
|
6
|
+
eager: boolean;
|
|
7
|
+
childAppName?: string;
|
|
8
|
+
childAppVersion?: string;
|
|
9
|
+
chunks?: string[];
|
|
10
|
+
};
|
|
11
|
+
export declare function resolveBestLoadedSharedModules({ sharedModules, sharedScopeItems, }: {
|
|
12
|
+
sharedModules: SharedModule[];
|
|
13
|
+
sharedScopeItems: SharedScopeItem[];
|
|
14
|
+
}): SharedModuleWithChunks[];
|
|
15
|
+
//# sourceMappingURL=best-loaded-shared-modules.d.ts.map
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
function resolveBestLoadedSharedModules({ sharedModules, sharedScopeItems, }) {
|
|
2
|
+
const result = [];
|
|
3
|
+
for (const sharedScopeItem of sharedScopeItems) {
|
|
4
|
+
const { shareKey, from, eager, loaded } = sharedScopeItem;
|
|
5
|
+
// we need only Child Apps deps, host app shared modules is always loaded on the client
|
|
6
|
+
// and we need to get only used shared modules, `loaded` flag means that module was required by MF runtime
|
|
7
|
+
// https://github.com/webpack/webpack/blob/97d4961cd1de9c69dba0f050a63f3b56bb64fab2/lib/sharing/ConsumeSharedRuntimeModule.js#L104
|
|
8
|
+
if (from.startsWith('child-app') && !!loaded) {
|
|
9
|
+
const sharedModule = sharedModules.find((m) => { var _a, _b; return m.from === from && ((_b = (_a = m.provides) === null || _a === void 0 ? void 0 : _a[0]) === null || _b === void 0 ? void 0 : _b.shareKey) === shareKey; });
|
|
10
|
+
if (sharedModule) {
|
|
11
|
+
result.push({
|
|
12
|
+
shareKey,
|
|
13
|
+
from: sharedScopeItem.from,
|
|
14
|
+
eager,
|
|
15
|
+
version: sharedScopeItem.version,
|
|
16
|
+
childAppName: sharedModule === null || sharedModule === void 0 ? void 0 : sharedModule.childAppName,
|
|
17
|
+
childAppVersion: sharedModule === null || sharedModule === void 0 ? void 0 : sharedModule.childAppVersion,
|
|
18
|
+
chunks: sharedModule === null || sharedModule === void 0 ? void 0 : sharedModule.chunks,
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
return result;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export { resolveBestLoadedSharedModules };
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, '__esModule', { value: true });
|
|
4
|
+
|
|
5
|
+
function resolveBestLoadedSharedModules({ sharedModules, sharedScopeItems, }) {
|
|
6
|
+
const result = [];
|
|
7
|
+
for (const sharedScopeItem of sharedScopeItems) {
|
|
8
|
+
const { shareKey, from, eager, loaded } = sharedScopeItem;
|
|
9
|
+
// we need only Child Apps deps, host app shared modules is always loaded on the client
|
|
10
|
+
// and we need to get only used shared modules, `loaded` flag means that module was required by MF runtime
|
|
11
|
+
// https://github.com/webpack/webpack/blob/97d4961cd1de9c69dba0f050a63f3b56bb64fab2/lib/sharing/ConsumeSharedRuntimeModule.js#L104
|
|
12
|
+
if (from.startsWith('child-app') && !!loaded) {
|
|
13
|
+
const sharedModule = sharedModules.find((m) => { var _a, _b; return m.from === from && ((_b = (_a = m.provides) === null || _a === void 0 ? void 0 : _a[0]) === null || _b === void 0 ? void 0 : _b.shareKey) === shareKey; });
|
|
14
|
+
if (sharedModule) {
|
|
15
|
+
result.push({
|
|
16
|
+
shareKey,
|
|
17
|
+
from: sharedScopeItem.from,
|
|
18
|
+
eager,
|
|
19
|
+
version: sharedScopeItem.version,
|
|
20
|
+
childAppName: sharedModule === null || sharedModule === void 0 ? void 0 : sharedModule.childAppName,
|
|
21
|
+
childAppVersion: sharedModule === null || sharedModule === void 0 ? void 0 : sharedModule.childAppVersion,
|
|
22
|
+
chunks: sharedModule === null || sharedModule === void 0 ? void 0 : sharedModule.chunks,
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
return result;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
exports.resolveBestLoadedSharedModules = resolveBestLoadedSharedModules;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { SharedModule, SharedScopeItem } from './utils';
|
|
2
|
+
export type SharedModuleWithChunks = {
|
|
3
|
+
shareKey: string;
|
|
4
|
+
from: string;
|
|
5
|
+
version: string;
|
|
6
|
+
eager: boolean;
|
|
7
|
+
childAppName?: string;
|
|
8
|
+
childAppVersion?: string;
|
|
9
|
+
chunks?: string[];
|
|
10
|
+
};
|
|
11
|
+
export declare function resolveBestSharedModules({ sharedModules, sharedScopeItems, }: {
|
|
12
|
+
sharedModules: SharedModule[];
|
|
13
|
+
sharedScopeItems: SharedScopeItem[];
|
|
14
|
+
}): SharedModuleWithChunks[];
|
|
15
|
+
//# sourceMappingURL=best-shared-modules.d.ts.map
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { ChildAppFinalConfig } from '@tramvai/tokens-child-app';
|
|
2
|
+
import type { ModuleFederationSharedModule, ModuleFederationSharedScope, ModuleFederationSharedScopeItem } from '../../shared/webpack/moduleFederation';
|
|
3
|
+
import type { ServerLoader } from '../loader';
|
|
4
|
+
export type SharedScopeItem = ModuleFederationSharedScopeItem & {
|
|
5
|
+
shareKey: string;
|
|
6
|
+
version: string;
|
|
7
|
+
};
|
|
8
|
+
export type SharedModule = ModuleFederationSharedModule & {
|
|
9
|
+
from: string;
|
|
10
|
+
childAppName: string;
|
|
11
|
+
childAppVersion: string;
|
|
12
|
+
};
|
|
13
|
+
export declare function getFlatSharedScopeItemsList(sharedScope: ModuleFederationSharedScope): SharedScopeItem[];
|
|
14
|
+
export declare function getFlatSharedModulesList({ preloadedConfigs, loader, }: {
|
|
15
|
+
preloadedConfigs: ChildAppFinalConfig[];
|
|
16
|
+
loader: ServerLoader;
|
|
17
|
+
}): SharedModule[];
|
|
18
|
+
//# sourceMappingURL=utils.d.ts.map
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
function getFlatSharedScopeItemsList(sharedScope) {
|
|
2
|
+
// flat list of all shared items initialized in shared scope
|
|
3
|
+
const sharedScopeItems = [];
|
|
4
|
+
for (const shareKey in sharedScope) {
|
|
5
|
+
const sharedDependency = sharedScope[shareKey];
|
|
6
|
+
for (const version in sharedDependency) {
|
|
7
|
+
const sharedScopeItem = sharedDependency[version];
|
|
8
|
+
sharedScopeItems.push({
|
|
9
|
+
...sharedScopeItem,
|
|
10
|
+
shareKey,
|
|
11
|
+
version,
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
return sharedScopeItems;
|
|
16
|
+
}
|
|
17
|
+
function getFlatSharedModulesList({ preloadedConfigs, loader, }) {
|
|
18
|
+
const sharedModules = [];
|
|
19
|
+
for (const config of preloadedConfigs) {
|
|
20
|
+
const stats = 'getStats' in loader ? loader.getStats(config) : undefined;
|
|
21
|
+
const from = `child-app:${config.name}:${config.version}`;
|
|
22
|
+
if (stats && stats.federatedModules) {
|
|
23
|
+
for (const federatedModule of stats.federatedModules) {
|
|
24
|
+
for (const sharedModule of federatedModule.sharedModules) {
|
|
25
|
+
sharedModules.push({
|
|
26
|
+
...sharedModule,
|
|
27
|
+
from,
|
|
28
|
+
childAppName: config.name,
|
|
29
|
+
childAppVersion: config.version,
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return sharedModules;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export { getFlatSharedModulesList, getFlatSharedScopeItemsList };
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, '__esModule', { value: true });
|
|
4
|
+
|
|
5
|
+
function getFlatSharedScopeItemsList(sharedScope) {
|
|
6
|
+
// flat list of all shared items initialized in shared scope
|
|
7
|
+
const sharedScopeItems = [];
|
|
8
|
+
for (const shareKey in sharedScope) {
|
|
9
|
+
const sharedDependency = sharedScope[shareKey];
|
|
10
|
+
for (const version in sharedDependency) {
|
|
11
|
+
const sharedScopeItem = sharedDependency[version];
|
|
12
|
+
sharedScopeItems.push({
|
|
13
|
+
...sharedScopeItem,
|
|
14
|
+
shareKey,
|
|
15
|
+
version,
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
return sharedScopeItems;
|
|
20
|
+
}
|
|
21
|
+
function getFlatSharedModulesList({ preloadedConfigs, loader, }) {
|
|
22
|
+
const sharedModules = [];
|
|
23
|
+
for (const config of preloadedConfigs) {
|
|
24
|
+
const stats = 'getStats' in loader ? loader.getStats(config) : undefined;
|
|
25
|
+
const from = `child-app:${config.name}:${config.version}`;
|
|
26
|
+
if (stats && stats.federatedModules) {
|
|
27
|
+
for (const federatedModule of stats.federatedModules) {
|
|
28
|
+
for (const sharedModule of federatedModule.sharedModules) {
|
|
29
|
+
sharedModules.push({
|
|
30
|
+
...sharedModule,
|
|
31
|
+
from,
|
|
32
|
+
childAppName: config.name,
|
|
33
|
+
childAppVersion: config.version,
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return sharedModules;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
exports.getFlatSharedModulesList = getFlatSharedModulesList;
|
|
43
|
+
exports.getFlatSharedScopeItemsList = getFlatSharedScopeItemsList;
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { extname } from 'path';
|
|
2
|
-
import { gt, eq } from 'semver';
|
|
3
2
|
import flatten from '@tinkoff/utils/array/flatten';
|
|
4
3
|
import { resolve } from '@tinkoff/url';
|
|
5
4
|
import { CHILD_APP_INTERNAL_CHUNK_EXTRACTOR } from '@tramvai/tokens-child-app';
|
|
6
5
|
import { RENDER_SLOTS, ResourceType, ResourceSlot } from '@tramvai/tokens-render';
|
|
7
6
|
import { getSharedScope } from '../shared/webpack/moduleFederation.es.js';
|
|
7
|
+
import { getFlatSharedScopeItemsList, getFlatSharedModulesList } from './module-federation/utils.es.js';
|
|
8
|
+
import { resolveBestLoadedSharedModules } from './module-federation/best-loaded-shared-modules.es.js';
|
|
8
9
|
|
|
9
10
|
const asyncScriptAttrs = {
|
|
10
11
|
defer: null,
|
|
@@ -25,7 +26,9 @@ const entryAttrs = {
|
|
|
25
26
|
const chunkAttrs = {
|
|
26
27
|
onerror: `this.remove()`,
|
|
27
28
|
};
|
|
28
|
-
const registerChildAppRenderSlots = ({ logger, diManager, resolveFullConfig, preloadManager, loader, renderMode, resourcesRegistry, }) =>
|
|
29
|
+
const registerChildAppRenderSlots = ({ logger, diManager, resolveFullConfig, preloadManager, loader, renderMode, resourcesRegistry, }) =>
|
|
30
|
+
// eslint-disable-next-line max-statements
|
|
31
|
+
async () => {
|
|
29
32
|
const log = logger('child-app:render:slots');
|
|
30
33
|
const result = [];
|
|
31
34
|
// defer scripts is not suitable for React streaming, we need to ability to run them as early as possible
|
|
@@ -64,37 +67,26 @@ const registerChildAppRenderSlots = ({ logger, diManager, resolveFullConfig, pre
|
|
|
64
67
|
};
|
|
65
68
|
const preloadedList = new Set(preloadManager.getPreloadedList());
|
|
66
69
|
const sharedScope = getSharedScope();
|
|
67
|
-
const
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
//
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
// if versions are equal then module federation will pick
|
|
85
|
-
// the dep with eager prop (it's set in root-app) of with the child-app with highest name in alphabetical order
|
|
86
|
-
(eq(version, last.version) && (eager !== last.eager ? eager : name > last.name))) {
|
|
87
|
-
mapSharedToChildApp.set(shareKey, { version, type, name, eager });
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
}
|
|
70
|
+
const preloadedConfigs = Array.from(preloadedList)
|
|
71
|
+
.map((requestConfig) => {
|
|
72
|
+
return resolveFullConfig(requestConfig);
|
|
73
|
+
})
|
|
74
|
+
.filter(Boolean);
|
|
75
|
+
// flat list of all shared items initialized in shared scope
|
|
76
|
+
const sharedScopeItems = getFlatSharedScopeItemsList(sharedScope);
|
|
77
|
+
// flat list of all preloaded Child Apps shared modules
|
|
78
|
+
const sharedModules = getFlatSharedModulesList({
|
|
79
|
+
preloadedConfigs,
|
|
80
|
+
loader: loader,
|
|
81
|
+
});
|
|
82
|
+
// optimal shared dependencies for preloaded Child Apps
|
|
83
|
+
const bestSharedModules = resolveBestLoadedSharedModules({
|
|
84
|
+
sharedModules,
|
|
85
|
+
sharedScopeItems,
|
|
86
|
+
});
|
|
91
87
|
// eslint-disable-next-line max-statements
|
|
92
|
-
|
|
93
|
-
var _a, _b, _c
|
|
94
|
-
const config = resolveFullConfig(requestConfig);
|
|
95
|
-
if (!config) {
|
|
96
|
-
return;
|
|
97
|
-
}
|
|
88
|
+
preloadedConfigs.forEach((config) => {
|
|
89
|
+
var _a, _b, _c;
|
|
98
90
|
const stats = 'getStats' in loader ? loader.getStats(config) : undefined;
|
|
99
91
|
const di = diManager.getChildDi(config);
|
|
100
92
|
const loadableAssets = (_a = di === null || di === void 0 ? void 0 : di.get(CHILD_APP_INTERNAL_CHUNK_EXTRACTOR)) === null || _a === void 0 ? void 0 : _a.getMainAssets();
|
|
@@ -122,21 +114,6 @@ const registerChildAppRenderSlots = ({ logger, diManager, resolveFullConfig, pre
|
|
|
122
114
|
for (const file of files) {
|
|
123
115
|
addChunk(resolve(config.client.baseUrl, file));
|
|
124
116
|
}
|
|
125
|
-
for (const sharedModule of federatedModule.sharedModules) {
|
|
126
|
-
const { shareKey } = (_d = sharedModule.provides) === null || _d === void 0 ? void 0 : _d[0];
|
|
127
|
-
const { chunks } = sharedModule;
|
|
128
|
-
const bestShared = mapSharedToChildApp.get(shareKey);
|
|
129
|
-
if (!(bestShared === null || bestShared === void 0 ? void 0 : bestShared.eager) && (bestShared === null || bestShared === void 0 ? void 0 : bestShared.name) === config.name) {
|
|
130
|
-
for (const chunk of chunks) {
|
|
131
|
-
addChunk(resolve(config.client.baseUrl, chunk));
|
|
132
|
-
}
|
|
133
|
-
// in stats.json federated stats could contain 2 sets of chunks for shared modules
|
|
134
|
-
// there usual one and fallback. For shared module there could be used any of this
|
|
135
|
-
// and the other one will be useless. So delete entry from map after its usage in order
|
|
136
|
-
// to add only single set of chunks for the same shared dep
|
|
137
|
-
mapSharedToChildApp.delete(shareKey);
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
117
|
}
|
|
141
118
|
}
|
|
142
119
|
if (!di) {
|
|
@@ -151,10 +128,49 @@ const registerChildAppRenderSlots = ({ logger, diManager, resolveFullConfig, pre
|
|
|
151
128
|
catch (error) {
|
|
152
129
|
log.error({
|
|
153
130
|
event: 'get-slots-failed',
|
|
154
|
-
|
|
131
|
+
childApp: {
|
|
132
|
+
name: config.name,
|
|
133
|
+
version: config.version,
|
|
134
|
+
tag: config.tag,
|
|
135
|
+
},
|
|
155
136
|
});
|
|
156
137
|
}
|
|
157
138
|
});
|
|
139
|
+
// shared chunks deduplication
|
|
140
|
+
const addedSharedChunks = new Set();
|
|
141
|
+
// preloaded chunks we need to pass to client
|
|
142
|
+
const serializableSharedModules = [];
|
|
143
|
+
bestSharedModules.forEach(({ shareKey, version, chunks, childAppName, childAppVersion }) => {
|
|
144
|
+
const childAppConfig = preloadedConfigs.find((config) => {
|
|
145
|
+
return config.name === childAppName && config.version === childAppVersion;
|
|
146
|
+
});
|
|
147
|
+
// if it is application shared dependency, we don't have any extra chunks
|
|
148
|
+
if (childAppConfig && chunks) {
|
|
149
|
+
for (const chunk of chunks) {
|
|
150
|
+
if (!addedSharedChunks.has(chunk)) {
|
|
151
|
+
addedSharedChunks.add(chunk);
|
|
152
|
+
addChunk(resolve(childAppConfig.client.baseUrl, chunk));
|
|
153
|
+
const containerName = `child-app__${childAppConfig.client.entry}`;
|
|
154
|
+
serializableSharedModules.push({
|
|
155
|
+
containerName,
|
|
156
|
+
shareKey,
|
|
157
|
+
version,
|
|
158
|
+
childAppName: childAppName,
|
|
159
|
+
childAppVersion: childAppVersion,
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
});
|
|
165
|
+
result.push({
|
|
166
|
+
type: ResourceType.inlineScript,
|
|
167
|
+
slot: ResourceSlot.HEAD_POLYFILLS,
|
|
168
|
+
payload: `window.__webpack_share_preloaded__ = [
|
|
169
|
+
${serializableSharedModules.map((m) => {
|
|
170
|
+
return `${JSON.stringify(m)}`;
|
|
171
|
+
})}
|
|
172
|
+
];`,
|
|
173
|
+
});
|
|
158
174
|
result.map((item) => resourcesRegistry.register(item));
|
|
159
175
|
};
|
|
160
176
|
|
|
@@ -3,12 +3,13 @@
|
|
|
3
3
|
Object.defineProperty(exports, '__esModule', { value: true });
|
|
4
4
|
|
|
5
5
|
var path = require('path');
|
|
6
|
-
var semver = require('semver');
|
|
7
6
|
var flatten = require('@tinkoff/utils/array/flatten');
|
|
8
7
|
var url = require('@tinkoff/url');
|
|
9
8
|
var tokensChildApp = require('@tramvai/tokens-child-app');
|
|
10
9
|
var tokensRender = require('@tramvai/tokens-render');
|
|
11
10
|
var moduleFederation = require('../shared/webpack/moduleFederation.js');
|
|
11
|
+
var utils = require('./module-federation/utils.js');
|
|
12
|
+
var bestLoadedSharedModules = require('./module-federation/best-loaded-shared-modules.js');
|
|
12
13
|
|
|
13
14
|
function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
|
|
14
15
|
|
|
@@ -33,7 +34,9 @@ const entryAttrs = {
|
|
|
33
34
|
const chunkAttrs = {
|
|
34
35
|
onerror: `this.remove()`,
|
|
35
36
|
};
|
|
36
|
-
const registerChildAppRenderSlots = ({ logger, diManager, resolveFullConfig, preloadManager, loader, renderMode, resourcesRegistry, }) =>
|
|
37
|
+
const registerChildAppRenderSlots = ({ logger, diManager, resolveFullConfig, preloadManager, loader, renderMode, resourcesRegistry, }) =>
|
|
38
|
+
// eslint-disable-next-line max-statements
|
|
39
|
+
async () => {
|
|
37
40
|
const log = logger('child-app:render:slots');
|
|
38
41
|
const result = [];
|
|
39
42
|
// defer scripts is not suitable for React streaming, we need to ability to run them as early as possible
|
|
@@ -72,37 +75,26 @@ const registerChildAppRenderSlots = ({ logger, diManager, resolveFullConfig, pre
|
|
|
72
75
|
};
|
|
73
76
|
const preloadedList = new Set(preloadManager.getPreloadedList());
|
|
74
77
|
const sharedScope = moduleFederation.getSharedScope();
|
|
75
|
-
const
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
//
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
// if versions are equal then module federation will pick
|
|
93
|
-
// the dep with eager prop (it's set in root-app) of with the child-app with highest name in alphabetical order
|
|
94
|
-
(semver.eq(version, last.version) && (eager !== last.eager ? eager : name > last.name))) {
|
|
95
|
-
mapSharedToChildApp.set(shareKey, { version, type, name, eager });
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
}
|
|
78
|
+
const preloadedConfigs = Array.from(preloadedList)
|
|
79
|
+
.map((requestConfig) => {
|
|
80
|
+
return resolveFullConfig(requestConfig);
|
|
81
|
+
})
|
|
82
|
+
.filter(Boolean);
|
|
83
|
+
// flat list of all shared items initialized in shared scope
|
|
84
|
+
const sharedScopeItems = utils.getFlatSharedScopeItemsList(sharedScope);
|
|
85
|
+
// flat list of all preloaded Child Apps shared modules
|
|
86
|
+
const sharedModules = utils.getFlatSharedModulesList({
|
|
87
|
+
preloadedConfigs,
|
|
88
|
+
loader: loader,
|
|
89
|
+
});
|
|
90
|
+
// optimal shared dependencies for preloaded Child Apps
|
|
91
|
+
const bestSharedModules = bestLoadedSharedModules.resolveBestLoadedSharedModules({
|
|
92
|
+
sharedModules,
|
|
93
|
+
sharedScopeItems,
|
|
94
|
+
});
|
|
99
95
|
// eslint-disable-next-line max-statements
|
|
100
|
-
|
|
101
|
-
var _a, _b, _c
|
|
102
|
-
const config = resolveFullConfig(requestConfig);
|
|
103
|
-
if (!config) {
|
|
104
|
-
return;
|
|
105
|
-
}
|
|
96
|
+
preloadedConfigs.forEach((config) => {
|
|
97
|
+
var _a, _b, _c;
|
|
106
98
|
const stats = 'getStats' in loader ? loader.getStats(config) : undefined;
|
|
107
99
|
const di = diManager.getChildDi(config);
|
|
108
100
|
const loadableAssets = (_a = di === null || di === void 0 ? void 0 : di.get(tokensChildApp.CHILD_APP_INTERNAL_CHUNK_EXTRACTOR)) === null || _a === void 0 ? void 0 : _a.getMainAssets();
|
|
@@ -130,21 +122,6 @@ const registerChildAppRenderSlots = ({ logger, diManager, resolveFullConfig, pre
|
|
|
130
122
|
for (const file of files) {
|
|
131
123
|
addChunk(url.resolve(config.client.baseUrl, file));
|
|
132
124
|
}
|
|
133
|
-
for (const sharedModule of federatedModule.sharedModules) {
|
|
134
|
-
const { shareKey } = (_d = sharedModule.provides) === null || _d === void 0 ? void 0 : _d[0];
|
|
135
|
-
const { chunks } = sharedModule;
|
|
136
|
-
const bestShared = mapSharedToChildApp.get(shareKey);
|
|
137
|
-
if (!(bestShared === null || bestShared === void 0 ? void 0 : bestShared.eager) && (bestShared === null || bestShared === void 0 ? void 0 : bestShared.name) === config.name) {
|
|
138
|
-
for (const chunk of chunks) {
|
|
139
|
-
addChunk(url.resolve(config.client.baseUrl, chunk));
|
|
140
|
-
}
|
|
141
|
-
// in stats.json federated stats could contain 2 sets of chunks for shared modules
|
|
142
|
-
// there usual one and fallback. For shared module there could be used any of this
|
|
143
|
-
// and the other one will be useless. So delete entry from map after its usage in order
|
|
144
|
-
// to add only single set of chunks for the same shared dep
|
|
145
|
-
mapSharedToChildApp.delete(shareKey);
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
125
|
}
|
|
149
126
|
}
|
|
150
127
|
if (!di) {
|
|
@@ -159,10 +136,49 @@ const registerChildAppRenderSlots = ({ logger, diManager, resolveFullConfig, pre
|
|
|
159
136
|
catch (error) {
|
|
160
137
|
log.error({
|
|
161
138
|
event: 'get-slots-failed',
|
|
162
|
-
|
|
139
|
+
childApp: {
|
|
140
|
+
name: config.name,
|
|
141
|
+
version: config.version,
|
|
142
|
+
tag: config.tag,
|
|
143
|
+
},
|
|
163
144
|
});
|
|
164
145
|
}
|
|
165
146
|
});
|
|
147
|
+
// shared chunks deduplication
|
|
148
|
+
const addedSharedChunks = new Set();
|
|
149
|
+
// preloaded chunks we need to pass to client
|
|
150
|
+
const serializableSharedModules = [];
|
|
151
|
+
bestSharedModules.forEach(({ shareKey, version, chunks, childAppName, childAppVersion }) => {
|
|
152
|
+
const childAppConfig = preloadedConfigs.find((config) => {
|
|
153
|
+
return config.name === childAppName && config.version === childAppVersion;
|
|
154
|
+
});
|
|
155
|
+
// if it is application shared dependency, we don't have any extra chunks
|
|
156
|
+
if (childAppConfig && chunks) {
|
|
157
|
+
for (const chunk of chunks) {
|
|
158
|
+
if (!addedSharedChunks.has(chunk)) {
|
|
159
|
+
addedSharedChunks.add(chunk);
|
|
160
|
+
addChunk(url.resolve(childAppConfig.client.baseUrl, chunk));
|
|
161
|
+
const containerName = `child-app__${childAppConfig.client.entry}`;
|
|
162
|
+
serializableSharedModules.push({
|
|
163
|
+
containerName,
|
|
164
|
+
shareKey,
|
|
165
|
+
version,
|
|
166
|
+
childAppName: childAppName,
|
|
167
|
+
childAppVersion: childAppVersion,
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
});
|
|
173
|
+
result.push({
|
|
174
|
+
type: tokensRender.ResourceType.inlineScript,
|
|
175
|
+
slot: tokensRender.ResourceSlot.HEAD_POLYFILLS,
|
|
176
|
+
payload: `window.__webpack_share_preloaded__ = [
|
|
177
|
+
${serializableSharedModules.map((m) => {
|
|
178
|
+
return `${JSON.stringify(m)}`;
|
|
179
|
+
})}
|
|
180
|
+
];`,
|
|
181
|
+
});
|
|
166
182
|
result.map((item) => resourcesRegistry.register(item));
|
|
167
183
|
};
|
|
168
184
|
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
const initModuleFederation = async (container, scope = 'default') => {
|
|
2
|
+
var _a;
|
|
2
3
|
if (container) {
|
|
3
4
|
await container.init(__webpack_share_scopes__[scope]);
|
|
4
5
|
return;
|
|
@@ -8,6 +9,48 @@ const initModuleFederation = async (container, scope = 'default') => {
|
|
|
8
9
|
// to implement the same logic for loading child-app as UniversalModuleFederation
|
|
9
10
|
global.__remote_scope__ = global.__remote_scope__ || { _config: {} };
|
|
10
11
|
}
|
|
12
|
+
else {
|
|
13
|
+
// for easy debugging
|
|
14
|
+
if (process.env.NODE_ENV === 'development') {
|
|
15
|
+
window.__webpack_share_scopes__ = __webpack_share_scopes__;
|
|
16
|
+
}
|
|
17
|
+
// iterate over server-side preloaded shared modules
|
|
18
|
+
(_a = window.__webpack_share_preloaded__) === null || _a === void 0 ? void 0 : _a.forEach((preloaded) => {
|
|
19
|
+
// Child Apps entries will register itself in the global scope with unique name after loading
|
|
20
|
+
// @ts-expect-error
|
|
21
|
+
const containerForPreloaded = window[preloaded.containerName];
|
|
22
|
+
if (containerForPreloaded) {
|
|
23
|
+
// early init to register all container shared modules in share scope
|
|
24
|
+
containerForPreloaded.init(__webpack_share_scopes__[scope]);
|
|
25
|
+
//
|
|
26
|
+
// For example, at server-side we preload two Child Apps - "base" and "router",
|
|
27
|
+
// with the same shared dependency "@tramvai/core@3.4.5", and host has uncompatible "@tramvai/core@4.0.0".
|
|
28
|
+
//
|
|
29
|
+
// It means that MF can share any of shared "@tramvai/core@3.4.5" instance between this two Child Apps.
|
|
30
|
+
//
|
|
31
|
+
// But MF share scope is a singleton at server, and we can't predict which Child App will be loaded first,
|
|
32
|
+
// or "base" can be resolved first at different page, and which dependency will be registered at share scope.
|
|
33
|
+
//
|
|
34
|
+
// When we preload used at server-side shared dependency script to prevent client-side waterfalls,
|
|
35
|
+
// it can be "base/@tramvai/core@3.4.5.js" or "router/@tramvai/core@3.4.5.js"
|
|
36
|
+
//
|
|
37
|
+
// And here is the problem - client-side runtime logic will don't know which shared dependency was preloaded,
|
|
38
|
+
// because "loaded = 1" flag is set to 1 only when the module is required from Child App entry.
|
|
39
|
+
//
|
|
40
|
+
// Shared dependencies registration logic described here:
|
|
41
|
+
// https://github.com/webpack/webpack/blob/97d4961cd1de9c69dba0f050a63f3b56bb64fab2/lib/sharing/ShareRuntimeModule.js#L100
|
|
42
|
+
// For our example it means that "@tramvai/core" from "router" will have priority, because of names comparsion - "router" > "base",
|
|
43
|
+
// and "loaded" flag is absent.
|
|
44
|
+
//
|
|
45
|
+
// So, even if the "base/@tramvai/core@3.4.5.js" was preloaded on the server, an additional request for the "router/@tramvai/core@3.4.5.js" will occur on the client.
|
|
46
|
+
// To prevent this, manually mark "base/@tramvai/core@3.4.5.js" as loaded in share scope immediately after registration.
|
|
47
|
+
//
|
|
48
|
+
__webpack_share_scopes__[scope][preloaded.shareKey][preloaded.version].loaded = 1;
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
// cleanup
|
|
52
|
+
window.__webpack_share_preloaded__ = [];
|
|
53
|
+
}
|
|
11
54
|
await __webpack_init_sharing__('default');
|
|
12
55
|
// currently module federation has problems with external modules (they are marked as externals in the dev build)
|
|
13
56
|
// and unfortunately react and react-dom are marked as externals in defaults
|
|
@@ -1,18 +1,26 @@
|
|
|
1
1
|
declare global {
|
|
2
2
|
var __webpack_init_sharing__: (name: string) => Promise<void>;
|
|
3
3
|
var __webpack_share_scopes__: ModuleFederationSharedScopes;
|
|
4
|
+
var __webpack_share_preloaded__: {
|
|
5
|
+
containerName: string;
|
|
6
|
+
shareKey: string;
|
|
7
|
+
version: string;
|
|
8
|
+
childAppName: string;
|
|
9
|
+
childAppVersion: string;
|
|
10
|
+
}[];
|
|
4
11
|
var __remote_scope__: {
|
|
5
12
|
_config: Record<string, string>;
|
|
6
13
|
};
|
|
7
14
|
}
|
|
8
|
-
interface
|
|
15
|
+
export interface ModuleFederationSharedScopeItem {
|
|
16
|
+
get: Function;
|
|
17
|
+
from: string;
|
|
18
|
+
eager: boolean;
|
|
19
|
+
loaded: 0 | 1 | boolean;
|
|
20
|
+
}
|
|
21
|
+
export interface ModuleFederationSharedScope {
|
|
9
22
|
[packageName: string]: {
|
|
10
|
-
[version: string]:
|
|
11
|
-
get: Function;
|
|
12
|
-
from: string;
|
|
13
|
-
eager: boolean;
|
|
14
|
-
loaded: 0 | 1 | boolean;
|
|
15
|
-
};
|
|
23
|
+
[version: string]: ModuleFederationSharedScopeItem;
|
|
16
24
|
};
|
|
17
25
|
}
|
|
18
26
|
interface ModuleFederationSharedScopes {
|
|
@@ -23,22 +31,23 @@ export interface ModuleFederationContainer {
|
|
|
23
31
|
init(scope: ModuleFederationSharedScopes[string]): Promise<void>;
|
|
24
32
|
get<T = unknown>(name: string): Promise<T>;
|
|
25
33
|
}
|
|
34
|
+
export interface ModuleFederationSharedModule {
|
|
35
|
+
chunks: string[];
|
|
36
|
+
provides: Array<{
|
|
37
|
+
shareScope: string;
|
|
38
|
+
shareKey: string;
|
|
39
|
+
requiredVersion: string;
|
|
40
|
+
strictVersion: boolean;
|
|
41
|
+
singleton: boolean;
|
|
42
|
+
eager: boolean;
|
|
43
|
+
}>;
|
|
44
|
+
}
|
|
26
45
|
export interface ModuleFederationStats {
|
|
27
46
|
sharedModules: any[];
|
|
28
47
|
federatedModules: Array<{
|
|
29
48
|
remote: string;
|
|
30
49
|
entry: string;
|
|
31
|
-
sharedModules: Array<
|
|
32
|
-
chunks: string[];
|
|
33
|
-
provides: Array<{
|
|
34
|
-
shareScope: string;
|
|
35
|
-
shareKey: string;
|
|
36
|
-
requiredVersion: string;
|
|
37
|
-
strictVersion: boolean;
|
|
38
|
-
singleton: boolean;
|
|
39
|
-
eager: boolean;
|
|
40
|
-
}>;
|
|
41
|
-
}>;
|
|
50
|
+
sharedModules: Array<ModuleFederationSharedModule>;
|
|
42
51
|
exposes: Record<string, Array<Record<string, string[]>>>;
|
|
43
52
|
}>;
|
|
44
53
|
}
|
|
@@ -2,6 +2,7 @@ const getSharedScope = (scope = 'default') => {
|
|
|
2
2
|
return __webpack_share_scopes__[scope];
|
|
3
3
|
};
|
|
4
4
|
const initModuleFederation = async (container, scope = 'default') => {
|
|
5
|
+
var _a;
|
|
5
6
|
if (container) {
|
|
6
7
|
await container.init(__webpack_share_scopes__[scope]);
|
|
7
8
|
return;
|
|
@@ -11,6 +12,48 @@ const initModuleFederation = async (container, scope = 'default') => {
|
|
|
11
12
|
// to implement the same logic for loading child-app as UniversalModuleFederation
|
|
12
13
|
global.__remote_scope__ = global.__remote_scope__ || { _config: {} };
|
|
13
14
|
}
|
|
15
|
+
else {
|
|
16
|
+
// for easy debugging
|
|
17
|
+
if (process.env.NODE_ENV === 'development') {
|
|
18
|
+
window.__webpack_share_scopes__ = __webpack_share_scopes__;
|
|
19
|
+
}
|
|
20
|
+
// iterate over server-side preloaded shared modules
|
|
21
|
+
(_a = window.__webpack_share_preloaded__) === null || _a === void 0 ? void 0 : _a.forEach((preloaded) => {
|
|
22
|
+
// Child Apps entries will register itself in the global scope with unique name after loading
|
|
23
|
+
// @ts-expect-error
|
|
24
|
+
const containerForPreloaded = window[preloaded.containerName];
|
|
25
|
+
if (containerForPreloaded) {
|
|
26
|
+
// early init to register all container shared modules in share scope
|
|
27
|
+
containerForPreloaded.init(__webpack_share_scopes__[scope]);
|
|
28
|
+
//
|
|
29
|
+
// For example, at server-side we preload two Child Apps - "base" and "router",
|
|
30
|
+
// with the same shared dependency "@tramvai/core@3.4.5", and host has uncompatible "@tramvai/core@4.0.0".
|
|
31
|
+
//
|
|
32
|
+
// It means that MF can share any of shared "@tramvai/core@3.4.5" instance between this two Child Apps.
|
|
33
|
+
//
|
|
34
|
+
// But MF share scope is a singleton at server, and we can't predict which Child App will be loaded first,
|
|
35
|
+
// or "base" can be resolved first at different page, and which dependency will be registered at share scope.
|
|
36
|
+
//
|
|
37
|
+
// When we preload used at server-side shared dependency script to prevent client-side waterfalls,
|
|
38
|
+
// it can be "base/@tramvai/core@3.4.5.js" or "router/@tramvai/core@3.4.5.js"
|
|
39
|
+
//
|
|
40
|
+
// And here is the problem - client-side runtime logic will don't know which shared dependency was preloaded,
|
|
41
|
+
// because "loaded = 1" flag is set to 1 only when the module is required from Child App entry.
|
|
42
|
+
//
|
|
43
|
+
// Shared dependencies registration logic described here:
|
|
44
|
+
// https://github.com/webpack/webpack/blob/97d4961cd1de9c69dba0f050a63f3b56bb64fab2/lib/sharing/ShareRuntimeModule.js#L100
|
|
45
|
+
// For our example it means that "@tramvai/core" from "router" will have priority, because of names comparsion - "router" > "base",
|
|
46
|
+
// and "loaded" flag is absent.
|
|
47
|
+
//
|
|
48
|
+
// So, even if the "base/@tramvai/core@3.4.5.js" was preloaded on the server, an additional request for the "router/@tramvai/core@3.4.5.js" will occur on the client.
|
|
49
|
+
// To prevent this, manually mark "base/@tramvai/core@3.4.5.js" as loaded in share scope immediately after registration.
|
|
50
|
+
//
|
|
51
|
+
__webpack_share_scopes__[scope][preloaded.shareKey][preloaded.version].loaded = 1;
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
// cleanup
|
|
55
|
+
window.__webpack_share_preloaded__ = [];
|
|
56
|
+
}
|
|
14
57
|
await __webpack_init_sharing__('default');
|
|
15
58
|
// currently module federation has problems with external modules (they are marked as externals in the dev build)
|
|
16
59
|
// and unfortunately react and react-dom are marked as externals in defaults
|
|
@@ -6,6 +6,7 @@ const getSharedScope = (scope = 'default') => {
|
|
|
6
6
|
return __webpack_share_scopes__[scope];
|
|
7
7
|
};
|
|
8
8
|
const initModuleFederation = async (container, scope = 'default') => {
|
|
9
|
+
var _a;
|
|
9
10
|
if (container) {
|
|
10
11
|
await container.init(__webpack_share_scopes__[scope]);
|
|
11
12
|
return;
|
|
@@ -15,6 +16,48 @@ const initModuleFederation = async (container, scope = 'default') => {
|
|
|
15
16
|
// to implement the same logic for loading child-app as UniversalModuleFederation
|
|
16
17
|
global.__remote_scope__ = global.__remote_scope__ || { _config: {} };
|
|
17
18
|
}
|
|
19
|
+
else {
|
|
20
|
+
// for easy debugging
|
|
21
|
+
if (process.env.NODE_ENV === 'development') {
|
|
22
|
+
window.__webpack_share_scopes__ = __webpack_share_scopes__;
|
|
23
|
+
}
|
|
24
|
+
// iterate over server-side preloaded shared modules
|
|
25
|
+
(_a = window.__webpack_share_preloaded__) === null || _a === void 0 ? void 0 : _a.forEach((preloaded) => {
|
|
26
|
+
// Child Apps entries will register itself in the global scope with unique name after loading
|
|
27
|
+
// @ts-expect-error
|
|
28
|
+
const containerForPreloaded = window[preloaded.containerName];
|
|
29
|
+
if (containerForPreloaded) {
|
|
30
|
+
// early init to register all container shared modules in share scope
|
|
31
|
+
containerForPreloaded.init(__webpack_share_scopes__[scope]);
|
|
32
|
+
//
|
|
33
|
+
// For example, at server-side we preload two Child Apps - "base" and "router",
|
|
34
|
+
// with the same shared dependency "@tramvai/core@3.4.5", and host has uncompatible "@tramvai/core@4.0.0".
|
|
35
|
+
//
|
|
36
|
+
// It means that MF can share any of shared "@tramvai/core@3.4.5" instance between this two Child Apps.
|
|
37
|
+
//
|
|
38
|
+
// But MF share scope is a singleton at server, and we can't predict which Child App will be loaded first,
|
|
39
|
+
// or "base" can be resolved first at different page, and which dependency will be registered at share scope.
|
|
40
|
+
//
|
|
41
|
+
// When we preload used at server-side shared dependency script to prevent client-side waterfalls,
|
|
42
|
+
// it can be "base/@tramvai/core@3.4.5.js" or "router/@tramvai/core@3.4.5.js"
|
|
43
|
+
//
|
|
44
|
+
// And here is the problem - client-side runtime logic will don't know which shared dependency was preloaded,
|
|
45
|
+
// because "loaded = 1" flag is set to 1 only when the module is required from Child App entry.
|
|
46
|
+
//
|
|
47
|
+
// Shared dependencies registration logic described here:
|
|
48
|
+
// https://github.com/webpack/webpack/blob/97d4961cd1de9c69dba0f050a63f3b56bb64fab2/lib/sharing/ShareRuntimeModule.js#L100
|
|
49
|
+
// For our example it means that "@tramvai/core" from "router" will have priority, because of names comparsion - "router" > "base",
|
|
50
|
+
// and "loaded" flag is absent.
|
|
51
|
+
//
|
|
52
|
+
// So, even if the "base/@tramvai/core@3.4.5.js" was preloaded on the server, an additional request for the "router/@tramvai/core@3.4.5.js" will occur on the client.
|
|
53
|
+
// To prevent this, manually mark "base/@tramvai/core@3.4.5.js" as loaded in share scope immediately after registration.
|
|
54
|
+
//
|
|
55
|
+
__webpack_share_scopes__[scope][preloaded.shareKey][preloaded.version].loaded = 1;
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
// cleanup
|
|
59
|
+
window.__webpack_share_preloaded__ = [];
|
|
60
|
+
}
|
|
18
61
|
await __webpack_init_sharing__('default');
|
|
19
62
|
// currently module federation has problems with external modules (they are marked as externals in the dev build)
|
|
20
63
|
// and unfortunately react and react-dom are marked as externals in defaults
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tramvai/module-child-app",
|
|
3
|
-
"version": "4.41.
|
|
3
|
+
"version": "4.41.100",
|
|
4
4
|
"description": "Module for child apps",
|
|
5
5
|
"browser": {
|
|
6
6
|
"./lib/server.js": "./lib/browser.js",
|
|
@@ -33,24 +33,24 @@
|
|
|
33
33
|
"@tinkoff/env-validators": "0.3.3",
|
|
34
34
|
"@tinkoff/module-loader-client": "0.6.7",
|
|
35
35
|
"@tinkoff/module-loader-server": "0.7.7",
|
|
36
|
-
"@tramvai/module-common": "4.41.
|
|
36
|
+
"@tramvai/module-common": "4.41.100",
|
|
37
37
|
"@tinkoff/url": "0.10.3",
|
|
38
38
|
"@tinkoff/errors": "0.5.3",
|
|
39
|
-
"@tramvai/child-app-core": "4.41.
|
|
39
|
+
"@tramvai/child-app-core": "4.41.100",
|
|
40
40
|
"@tramvai/safe-strings": "0.7.9",
|
|
41
|
-
"@tramvai/tokens-child-app": "4.41.
|
|
41
|
+
"@tramvai/tokens-child-app": "4.41.100"
|
|
42
42
|
},
|
|
43
43
|
"devDependencies": {},
|
|
44
44
|
"peerDependencies": {
|
|
45
45
|
"@tinkoff/dippy": "0.10.11",
|
|
46
|
-
"@tinkoff/router": "0.4.
|
|
46
|
+
"@tinkoff/router": "0.4.235",
|
|
47
47
|
"@tinkoff/utils": "^2.1.2",
|
|
48
|
-
"@tramvai/core": "4.41.
|
|
49
|
-
"@tramvai/state": "4.41.
|
|
50
|
-
"@tramvai/react": "4.41.
|
|
51
|
-
"@tramvai/tokens-common": "4.41.
|
|
52
|
-
"@tramvai/tokens-render": "4.41.
|
|
53
|
-
"@tramvai/tokens-router": "4.41.
|
|
48
|
+
"@tramvai/core": "4.41.100",
|
|
49
|
+
"@tramvai/state": "4.41.100",
|
|
50
|
+
"@tramvai/react": "4.41.100",
|
|
51
|
+
"@tramvai/tokens-common": "4.41.100",
|
|
52
|
+
"@tramvai/tokens-render": "4.41.100",
|
|
53
|
+
"@tramvai/tokens-router": "4.41.100",
|
|
54
54
|
"react": ">=16.14.0",
|
|
55
55
|
"react-dom": ">=16.14.0",
|
|
56
56
|
"object-assign": "^4.1.1",
|