@tramvai/module-child-app 2.27.0 → 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 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
@@ -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 ChildApp = ({ name, version, tag, props }) => {
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
- return createElement(Cmp, {
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 ChildApp = ({ name, version, tag, props }) => {
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
- return createElement(Cmp, {
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 ChildApp = ({ name, version, tag, props }) => {
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
- return react.createElement(Cmp, {
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) => import("react").ReactElement<import("@tramvai/tokens-child-app").WrapperProps<any>, string | import("react").JSXElementConstructor<any>>;
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.27.0",
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.27.0",
34
+ "@tramvai/child-app-core": "2.28.0",
35
35
  "@tramvai/safe-strings": "0.5.4",
36
- "@tramvai/tokens-child-app": "2.27.0"
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.27.0",
43
- "@tramvai/state": "2.27.0",
44
- "@tramvai/react": "2.27.0",
45
- "@tramvai/tokens-common": "2.27.0",
46
- "@tramvai/tokens-render": "2.27.0",
47
- "@tramvai/tokens-router": "2.27.0",
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",