@tramvai/module-child-app 2.26.2 → 2.28.0
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 +15 -0
- package/lib/server.browser.js +28 -4
- package/lib/server.es.js +28 -4
- package/lib/server.js +27 -3
- package/lib/shared/react/component.d.ts +1 -1
- package/package.json +9 -9
package/README.md
CHANGED
|
@@ -323,3 +323,18 @@ You may specify a full config to debug to a specific child-app:
|
|
|
323
323
|
});
|
|
324
324
|
```
|
|
325
325
|
2. Run root-app with `CHILD_APP_DEBUG` environment variable with value of child-app names needed to debug
|
|
326
|
+
|
|
327
|
+
## Known issues
|
|
328
|
+
|
|
329
|
+
### This Suspense boundary received an update before it finished hydrating
|
|
330
|
+
|
|
331
|
+
When `React` >= `18` version is used, child-app will be wrapped in `Suspense` boundary for [Selective Hydration](https://github.com/reactwg/react-18/discussions/130).
|
|
332
|
+
This optimization can significantly decrease Total Blocking Time metric of the page.
|
|
333
|
+
|
|
334
|
+
There is one drawback of this optimization - if you will try rerender child-app during selective hydration, `React` will switch to deopt mode and made full client-rendering of the child-app component.
|
|
335
|
+
Potential ways to fix this problem [described here](https://github.com/facebook/react/issues/24476#issuecomment-1127800350).
|
|
336
|
+
`ChildApp` component already wrapped in `React.memo`.
|
|
337
|
+
|
|
338
|
+
Few advices to avoid this problem:
|
|
339
|
+
- Memoize object, passed to child-app `props` property
|
|
340
|
+
- Prevent pass to child-app properties, which can be changed during hydration, for example at cliend-side in page actions
|
package/lib/server.browser.js
CHANGED
|
@@ -11,7 +11,7 @@ import flatten from '@tinkoff/utils/array/flatten';
|
|
|
11
11
|
import noop from '@tinkoff/utils/function/noop';
|
|
12
12
|
import { Subscription, ChildDispatcherContext, createEvent, createReducer } from '@tramvai/state';
|
|
13
13
|
import { jsx } from 'react/jsx-runtime';
|
|
14
|
-
import { createContext, useContext, useMemo, useState, useEffect, createElement } from 'react';
|
|
14
|
+
import { createContext, memo, useContext, useMemo, useState, useEffect, createElement, Suspense } from 'react';
|
|
15
15
|
import applyOrReturn from '@tinkoff/utils/function/applyOrReturn';
|
|
16
16
|
import { loadModule } from '@tinkoff/module-loader-client';
|
|
17
17
|
|
|
@@ -936,7 +936,27 @@ const browserProviders = [
|
|
|
936
936
|
}),
|
|
937
937
|
];
|
|
938
938
|
|
|
939
|
-
const
|
|
939
|
+
const FailedChildAppFallback = ({ name, version, tag, logger, fallback: Fallback, }) => {
|
|
940
|
+
// On client-side hydration errors will be handled in `hydrateRoot` `onRecoverableError` property,
|
|
941
|
+
// and update errors will be handled in Error Boundaries.
|
|
942
|
+
//
|
|
943
|
+
// Also, this component never be rendered at client-side, and we check environment only for safety
|
|
944
|
+
// (server errors logic described here https://github.com/reactjs/rfcs/blob/main/text/0215-server-errors-in-react-18.md).
|
|
945
|
+
//
|
|
946
|
+
// On server-side, we still use `renderToString`,
|
|
947
|
+
// and need to manually log render errors for components, wrapped in Suspense Boundaries.
|
|
948
|
+
if (typeof window === 'undefined') {
|
|
949
|
+
logger.error({
|
|
950
|
+
event: 'failed-render',
|
|
951
|
+
message: 'child-app failed to render, will try to recover during hydration',
|
|
952
|
+
name,
|
|
953
|
+
version,
|
|
954
|
+
tag,
|
|
955
|
+
});
|
|
956
|
+
}
|
|
957
|
+
return Fallback ? jsx(Fallback, {}) : null;
|
|
958
|
+
};
|
|
959
|
+
const ChildApp = memo(({ name, version, tag, props, fallback }) => {
|
|
940
960
|
const renderManager = useContext(RenderContext);
|
|
941
961
|
const resolveExternalConfig = useDi(CHILD_APP_RESOLVE_CONFIG_TOKEN);
|
|
942
962
|
const logger = useDi(LOGGER_TOKEN);
|
|
@@ -973,10 +993,14 @@ const ChildApp = ({ name, version, tag, props }) => {
|
|
|
973
993
|
});
|
|
974
994
|
return null;
|
|
975
995
|
}
|
|
976
|
-
|
|
996
|
+
const result = createElement(Cmp, {
|
|
977
997
|
di,
|
|
978
998
|
props,
|
|
979
999
|
});
|
|
1000
|
+
if (process.env.__TRAMVAI_CONCURRENT_FEATURES) {
|
|
1001
|
+
return (jsx(Suspense, { fallback: jsx(FailedChildAppFallback, { name: name, version: version, tag: tag, logger: log, fallback: fallback }), children: result }));
|
|
1002
|
+
}
|
|
1003
|
+
return result;
|
|
980
1004
|
}
|
|
981
1005
|
catch (error) {
|
|
982
1006
|
log.error({
|
|
@@ -989,7 +1013,7 @@ const ChildApp = ({ name, version, tag, props }) => {
|
|
|
989
1013
|
});
|
|
990
1014
|
return null;
|
|
991
1015
|
}
|
|
992
|
-
};
|
|
1016
|
+
});
|
|
993
1017
|
|
|
994
1018
|
let ChildAppModule = class ChildAppModule {
|
|
995
1019
|
};
|
package/lib/server.es.js
CHANGED
|
@@ -10,7 +10,7 @@ import { resolveLazyComponent, useDi } from '@tramvai/react';
|
|
|
10
10
|
import flatten from '@tinkoff/utils/array/flatten';
|
|
11
11
|
import { ChildDispatcherContext, createEvent, createReducer } from '@tramvai/state';
|
|
12
12
|
import { jsx } from 'react/jsx-runtime';
|
|
13
|
-
import { createContext, useContext, useMemo, useState, useEffect, createElement } from 'react';
|
|
13
|
+
import { createContext, memo, useContext, useMemo, useState, useEffect, createElement, Suspense } from 'react';
|
|
14
14
|
import applyOrReturn from '@tinkoff/utils/function/applyOrReturn';
|
|
15
15
|
import { combineValidators, isUrl, endsWith } from '@tinkoff/env-validators';
|
|
16
16
|
import { safeStringify } from '@tramvai/safe-strings';
|
|
@@ -955,7 +955,27 @@ const serverProviders = [
|
|
|
955
955
|
}),
|
|
956
956
|
];
|
|
957
957
|
|
|
958
|
-
const
|
|
958
|
+
const FailedChildAppFallback = ({ name, version, tag, logger, fallback: Fallback, }) => {
|
|
959
|
+
// On client-side hydration errors will be handled in `hydrateRoot` `onRecoverableError` property,
|
|
960
|
+
// and update errors will be handled in Error Boundaries.
|
|
961
|
+
//
|
|
962
|
+
// Also, this component never be rendered at client-side, and we check environment only for safety
|
|
963
|
+
// (server errors logic described here https://github.com/reactjs/rfcs/blob/main/text/0215-server-errors-in-react-18.md).
|
|
964
|
+
//
|
|
965
|
+
// On server-side, we still use `renderToString`,
|
|
966
|
+
// and need to manually log render errors for components, wrapped in Suspense Boundaries.
|
|
967
|
+
if (typeof window === 'undefined') {
|
|
968
|
+
logger.error({
|
|
969
|
+
event: 'failed-render',
|
|
970
|
+
message: 'child-app failed to render, will try to recover during hydration',
|
|
971
|
+
name,
|
|
972
|
+
version,
|
|
973
|
+
tag,
|
|
974
|
+
});
|
|
975
|
+
}
|
|
976
|
+
return Fallback ? jsx(Fallback, {}) : null;
|
|
977
|
+
};
|
|
978
|
+
const ChildApp = memo(({ name, version, tag, props, fallback }) => {
|
|
959
979
|
const renderManager = useContext(RenderContext);
|
|
960
980
|
const resolveExternalConfig = useDi(CHILD_APP_RESOLVE_CONFIG_TOKEN);
|
|
961
981
|
const logger = useDi(LOGGER_TOKEN);
|
|
@@ -992,10 +1012,14 @@ const ChildApp = ({ name, version, tag, props }) => {
|
|
|
992
1012
|
});
|
|
993
1013
|
return null;
|
|
994
1014
|
}
|
|
995
|
-
|
|
1015
|
+
const result = createElement(Cmp, {
|
|
996
1016
|
di,
|
|
997
1017
|
props,
|
|
998
1018
|
});
|
|
1019
|
+
if (process.env.__TRAMVAI_CONCURRENT_FEATURES) {
|
|
1020
|
+
return (jsx(Suspense, { fallback: jsx(FailedChildAppFallback, { name: name, version: version, tag: tag, logger: log, fallback: fallback }), children: result }));
|
|
1021
|
+
}
|
|
1022
|
+
return result;
|
|
999
1023
|
}
|
|
1000
1024
|
catch (error) {
|
|
1001
1025
|
log.error({
|
|
@@ -1008,7 +1032,7 @@ const ChildApp = ({ name, version, tag, props }) => {
|
|
|
1008
1032
|
});
|
|
1009
1033
|
return null;
|
|
1010
1034
|
}
|
|
1011
|
-
};
|
|
1035
|
+
});
|
|
1012
1036
|
|
|
1013
1037
|
let ChildAppModule = class ChildAppModule {
|
|
1014
1038
|
};
|
package/lib/server.js
CHANGED
|
@@ -964,7 +964,27 @@ const serverProviders = [
|
|
|
964
964
|
}),
|
|
965
965
|
];
|
|
966
966
|
|
|
967
|
-
const
|
|
967
|
+
const FailedChildAppFallback = ({ name, version, tag, logger, fallback: Fallback, }) => {
|
|
968
|
+
// On client-side hydration errors will be handled in `hydrateRoot` `onRecoverableError` property,
|
|
969
|
+
// and update errors will be handled in Error Boundaries.
|
|
970
|
+
//
|
|
971
|
+
// Also, this component never be rendered at client-side, and we check environment only for safety
|
|
972
|
+
// (server errors logic described here https://github.com/reactjs/rfcs/blob/main/text/0215-server-errors-in-react-18.md).
|
|
973
|
+
//
|
|
974
|
+
// On server-side, we still use `renderToString`,
|
|
975
|
+
// and need to manually log render errors for components, wrapped in Suspense Boundaries.
|
|
976
|
+
if (typeof window === 'undefined') {
|
|
977
|
+
logger.error({
|
|
978
|
+
event: 'failed-render',
|
|
979
|
+
message: 'child-app failed to render, will try to recover during hydration',
|
|
980
|
+
name,
|
|
981
|
+
version,
|
|
982
|
+
tag,
|
|
983
|
+
});
|
|
984
|
+
}
|
|
985
|
+
return Fallback ? jsxRuntime.jsx(Fallback, {}) : null;
|
|
986
|
+
};
|
|
987
|
+
const ChildApp = react.memo(({ name, version, tag, props, fallback }) => {
|
|
968
988
|
const renderManager = react.useContext(RenderContext);
|
|
969
989
|
const resolveExternalConfig = react$1.useDi(tokensChildApp.CHILD_APP_RESOLVE_CONFIG_TOKEN);
|
|
970
990
|
const logger = react$1.useDi(tokensCommon.LOGGER_TOKEN);
|
|
@@ -1001,10 +1021,14 @@ const ChildApp = ({ name, version, tag, props }) => {
|
|
|
1001
1021
|
});
|
|
1002
1022
|
return null;
|
|
1003
1023
|
}
|
|
1004
|
-
|
|
1024
|
+
const result = react.createElement(Cmp, {
|
|
1005
1025
|
di,
|
|
1006
1026
|
props,
|
|
1007
1027
|
});
|
|
1028
|
+
if (process.env.__TRAMVAI_CONCURRENT_FEATURES) {
|
|
1029
|
+
return (jsxRuntime.jsx(react.Suspense, { fallback: jsxRuntime.jsx(FailedChildAppFallback, { name: name, version: version, tag: tag, logger: log, fallback: fallback }), children: result }));
|
|
1030
|
+
}
|
|
1031
|
+
return result;
|
|
1008
1032
|
}
|
|
1009
1033
|
catch (error) {
|
|
1010
1034
|
log.error({
|
|
@@ -1017,7 +1041,7 @@ const ChildApp = ({ name, version, tag, props }) => {
|
|
|
1017
1041
|
});
|
|
1018
1042
|
return null;
|
|
1019
1043
|
}
|
|
1020
|
-
};
|
|
1044
|
+
});
|
|
1021
1045
|
|
|
1022
1046
|
exports.ChildAppModule = class ChildAppModule {
|
|
1023
1047
|
};
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
import type { ChildAppReactConfig } from '@tramvai/tokens-child-app';
|
|
2
|
-
export declare const ChildApp: ({ name, version, tag, props }: ChildAppReactConfig) =>
|
|
2
|
+
export declare const ChildApp: import("react").MemoExoticComponent<({ name, version, tag, props, fallback }: ChildAppReactConfig) => JSX.Element>;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tramvai/module-child-app",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.28.0",
|
|
4
4
|
"description": "Module for child apps",
|
|
5
5
|
"browser": {
|
|
6
6
|
"./lib/server.js": "./lib/browser.js",
|
|
@@ -31,20 +31,20 @@
|
|
|
31
31
|
"@tinkoff/env-validators": "0.1.3",
|
|
32
32
|
"@tinkoff/module-loader-client": "0.4.3",
|
|
33
33
|
"@tinkoff/module-loader-server": "0.5.3",
|
|
34
|
-
"@tramvai/child-app-core": "2.
|
|
34
|
+
"@tramvai/child-app-core": "2.28.0",
|
|
35
35
|
"@tramvai/safe-strings": "0.5.4",
|
|
36
|
-
"@tramvai/tokens-child-app": "2.
|
|
36
|
+
"@tramvai/tokens-child-app": "2.28.0"
|
|
37
37
|
},
|
|
38
38
|
"devDependencies": {},
|
|
39
39
|
"peerDependencies": {
|
|
40
40
|
"@tinkoff/dippy": "0.8.6",
|
|
41
41
|
"@tinkoff/utils": "^2.1.2",
|
|
42
|
-
"@tramvai/core": "2.
|
|
43
|
-
"@tramvai/state": "2.
|
|
44
|
-
"@tramvai/react": "2.
|
|
45
|
-
"@tramvai/tokens-common": "2.
|
|
46
|
-
"@tramvai/tokens-render": "2.
|
|
47
|
-
"@tramvai/tokens-router": "2.
|
|
42
|
+
"@tramvai/core": "2.28.0",
|
|
43
|
+
"@tramvai/state": "2.28.0",
|
|
44
|
+
"@tramvai/react": "2.28.0",
|
|
45
|
+
"@tramvai/tokens-common": "2.28.0",
|
|
46
|
+
"@tramvai/tokens-render": "2.28.0",
|
|
47
|
+
"@tramvai/tokens-router": "2.28.0",
|
|
48
48
|
"react": ">=16.14.0",
|
|
49
49
|
"react-dom": ">=16.14.0",
|
|
50
50
|
"object-assign": "^4.1.1",
|