@tramvai/module-child-app 4.41.97 → 4.41.99

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.
@@ -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, }) => async () => {
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 mapSharedToChildApp = new Map();
68
- // sharedScope will contain all of the shared chunks that were added
69
- // while server is running
70
- // but on the page we can use only shared chunks that either provided by the root-app
71
- // or one of loaded child-app
72
- // so gather all of the available shared modules, check the ones that are available in the currently
73
- // preloaded child-apps and figure out the best single version of the dep
74
- for (const shareKey in sharedScope) {
75
- for (const version in sharedScope[shareKey]) {
76
- const dep = sharedScope[shareKey][version];
77
- const last = mapSharedToChildApp.get(shareKey);
78
- const { eager, from } = dep;
79
- const [type, name] = from.split(':');
80
- if (!last ||
81
- // module federation will pick the highest available version
82
- // https://github.com/webpack/webpack/blob/b67626c7b4ffed8737d195b27c8cea1e68d58134/lib/sharing/ConsumeSharedRuntimeModule.js#L144
83
- gt(version, last.version) ||
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
- preloadedList.forEach((requestConfig) => {
93
- var _a, _b, _c, _d;
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
- config: requestConfig,
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, }) => async () => {
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 mapSharedToChildApp = new Map();
76
- // sharedScope will contain all of the shared chunks that were added
77
- // while server is running
78
- // but on the page we can use only shared chunks that either provided by the root-app
79
- // or one of loaded child-app
80
- // so gather all of the available shared modules, check the ones that are available in the currently
81
- // preloaded child-apps and figure out the best single version of the dep
82
- for (const shareKey in sharedScope) {
83
- for (const version in sharedScope[shareKey]) {
84
- const dep = sharedScope[shareKey][version];
85
- const last = mapSharedToChildApp.get(shareKey);
86
- const { eager, from } = dep;
87
- const [type, name] = from.split(':');
88
- if (!last ||
89
- // module federation will pick the highest available version
90
- // https://github.com/webpack/webpack/blob/b67626c7b4ffed8737d195b27c8cea1e68d58134/lib/sharing/ConsumeSharedRuntimeModule.js#L144
91
- semver.gt(version, last.version) ||
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
- preloadedList.forEach((requestConfig) => {
101
- var _a, _b, _c, _d;
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
- config: requestConfig,
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 ModuleFederationSharedScope {
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.97",
3
+ "version": "4.41.99",
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.97",
36
+ "@tramvai/module-common": "4.41.99",
37
37
  "@tinkoff/url": "0.10.3",
38
38
  "@tinkoff/errors": "0.5.3",
39
- "@tramvai/child-app-core": "4.41.97",
39
+ "@tramvai/child-app-core": "4.41.99",
40
40
  "@tramvai/safe-strings": "0.7.9",
41
- "@tramvai/tokens-child-app": "4.41.97"
41
+ "@tramvai/tokens-child-app": "4.41.99"
42
42
  },
43
43
  "devDependencies": {},
44
44
  "peerDependencies": {
45
45
  "@tinkoff/dippy": "0.10.11",
46
- "@tinkoff/router": "0.4.232",
46
+ "@tinkoff/router": "0.4.234",
47
47
  "@tinkoff/utils": "^2.1.2",
48
- "@tramvai/core": "4.41.97",
49
- "@tramvai/state": "4.41.97",
50
- "@tramvai/react": "4.41.97",
51
- "@tramvai/tokens-common": "4.41.97",
52
- "@tramvai/tokens-render": "4.41.97",
53
- "@tramvai/tokens-router": "4.41.97",
48
+ "@tramvai/core": "4.41.99",
49
+ "@tramvai/state": "4.41.99",
50
+ "@tramvai/react": "4.41.99",
51
+ "@tramvai/tokens-common": "4.41.99",
52
+ "@tramvai/tokens-render": "4.41.99",
53
+ "@tramvai/tokens-router": "4.41.99",
54
54
  "react": ">=16.14.0",
55
55
  "react-dom": ">=16.14.0",
56
56
  "object-assign": "^4.1.1",