@dr.pogodin/react-utils 1.41.17 → 1.42.1

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.
Files changed (230) hide show
  1. package/babel.config.js +3 -0
  2. package/bin/build.js +5 -8
  3. package/bin/setup.js +2 -1
  4. package/build/development/client/getInj.js +7 -2
  5. package/build/development/client/getInj.js.map +1 -1
  6. package/build/development/client/index.js +1 -2
  7. package/build/development/client/index.js.map +1 -1
  8. package/build/development/client/init.js +16 -13
  9. package/build/development/client/init.js.map +1 -1
  10. package/build/development/index.js +4 -1
  11. package/build/development/index.js.map +1 -1
  12. package/build/development/server/Cache.js +5 -9
  13. package/build/development/server/Cache.js.map +1 -1
  14. package/build/development/server/index.js +14 -11
  15. package/build/development/server/index.js.map +1 -1
  16. package/build/development/server/renderer.js +36 -41
  17. package/build/development/server/renderer.js.map +1 -1
  18. package/build/development/server/server.js +22 -13
  19. package/build/development/server/server.js.map +1 -1
  20. package/build/development/server/utils/errors.js.map +1 -1
  21. package/build/development/shared/components/Button/index.js +2 -3
  22. package/build/development/shared/components/Button/index.js.map +1 -1
  23. package/build/development/shared/components/Checkbox/index.js +3 -1
  24. package/build/development/shared/components/Checkbox/index.js.map +1 -1
  25. package/build/development/shared/components/GenericLink/index.js +13 -6
  26. package/build/development/shared/components/GenericLink/index.js.map +1 -1
  27. package/build/development/shared/components/Input/index.js +5 -1
  28. package/build/development/shared/components/Input/index.js.map +1 -1
  29. package/build/development/shared/components/Link.js +5 -6
  30. package/build/development/shared/components/Link.js.map +1 -1
  31. package/build/development/shared/components/MetaTags.js +31 -24
  32. package/build/development/shared/components/MetaTags.js.map +1 -1
  33. package/build/development/shared/components/Modal/index.js +13 -6
  34. package/build/development/shared/components/Modal/index.js.map +1 -1
  35. package/build/development/shared/components/NavLink.js +6 -6
  36. package/build/development/shared/components/NavLink.js.map +1 -1
  37. package/build/development/shared/components/TextArea/index.js +6 -3
  38. package/build/development/shared/components/TextArea/index.js.map +1 -1
  39. package/build/development/shared/components/WithTooltip/Tooltip.js +35 -39
  40. package/build/development/shared/components/WithTooltip/Tooltip.js.map +1 -1
  41. package/build/development/shared/components/WithTooltip/index.js +27 -21
  42. package/build/development/shared/components/WithTooltip/index.js.map +1 -1
  43. package/build/development/shared/components/YouTubeVideo/index.js +4 -3
  44. package/build/development/shared/components/YouTubeVideo/index.js.map +1 -1
  45. package/build/development/shared/components/selectors/CustomDropdown/Options/index.js +4 -5
  46. package/build/development/shared/components/selectors/CustomDropdown/Options/index.js.map +1 -1
  47. package/build/development/shared/components/selectors/CustomDropdown/index.js +7 -9
  48. package/build/development/shared/components/selectors/CustomDropdown/index.js.map +1 -1
  49. package/build/development/shared/components/selectors/NativeDropdown/index.js +3 -5
  50. package/build/development/shared/components/selectors/NativeDropdown/index.js.map +1 -1
  51. package/build/development/shared/components/selectors/Switch/index.js +21 -20
  52. package/build/development/shared/components/selectors/Switch/index.js.map +1 -1
  53. package/build/development/shared/utils/config.js +6 -3
  54. package/build/development/shared/utils/config.js.map +1 -1
  55. package/build/development/shared/utils/globalState.js +6 -0
  56. package/build/development/shared/utils/globalState.js.map +1 -1
  57. package/build/development/shared/utils/isomorphy/buildInfo.js.map +1 -1
  58. package/build/development/shared/utils/isomorphy/environment-check.js +6 -1
  59. package/build/development/shared/utils/isomorphy/environment-check.js.map +1 -1
  60. package/build/development/shared/utils/isomorphy/index.js +1 -3
  61. package/build/development/shared/utils/isomorphy/index.js.map +1 -1
  62. package/build/development/shared/utils/jest/E2eSsrEnv.js +26 -17
  63. package/build/development/shared/utils/jest/E2eSsrEnv.js.map +1 -1
  64. package/build/development/shared/utils/jest/global.js +0 -7
  65. package/build/development/shared/utils/jest/global.js.map +1 -1
  66. package/build/development/shared/utils/jest/index.js +15 -4
  67. package/build/development/shared/utils/jest/index.js.map +1 -1
  68. package/build/development/shared/utils/splitComponent.js +37 -19
  69. package/build/development/shared/utils/splitComponent.js.map +1 -1
  70. package/build/development/shared/utils/time.js +3 -3
  71. package/build/development/shared/utils/time.js.map +1 -1
  72. package/build/development/shared/utils/webpack.js +11 -11
  73. package/build/development/shared/utils/webpack.js.map +1 -1
  74. package/build/development/web.bundle.js +31 -31
  75. package/build/production/client/getInj.js +4 -2
  76. package/build/production/client/getInj.js.map +1 -1
  77. package/build/production/client/index.js +2 -2
  78. package/build/production/client/index.js.map +1 -1
  79. package/build/production/client/init.js +4 -2
  80. package/build/production/client/init.js.map +1 -1
  81. package/build/production/index.js +2 -1
  82. package/build/production/index.js.map +1 -1
  83. package/build/production/server/Cache.js +1 -2
  84. package/build/production/server/Cache.js.map +1 -1
  85. package/build/production/server/index.js +9 -5
  86. package/build/production/server/index.js.map +1 -1
  87. package/build/production/server/renderer.js +24 -21
  88. package/build/production/server/renderer.js.map +1 -1
  89. package/build/production/server/server.js +14 -9
  90. package/build/production/server/server.js.map +1 -1
  91. package/build/production/server/utils/errors.js.map +1 -1
  92. package/build/production/shared/components/Button/index.js +1 -1
  93. package/build/production/shared/components/Button/index.js.map +1 -1
  94. package/build/production/shared/components/Checkbox/index.js +1 -1
  95. package/build/production/shared/components/Checkbox/index.js.map +1 -1
  96. package/build/production/shared/components/GenericLink/index.js +6 -4
  97. package/build/production/shared/components/GenericLink/index.js.map +1 -1
  98. package/build/production/shared/components/Input/index.js +3 -1
  99. package/build/production/shared/components/Input/index.js.map +1 -1
  100. package/build/production/shared/components/Link.js +3 -1
  101. package/build/production/shared/components/Link.js.map +1 -1
  102. package/build/production/shared/components/MetaTags.js +5 -2
  103. package/build/production/shared/components/MetaTags.js.map +1 -1
  104. package/build/production/shared/components/Modal/index.js +6 -2
  105. package/build/production/shared/components/Modal/index.js.map +1 -1
  106. package/build/production/shared/components/NavLink.js +4 -1
  107. package/build/production/shared/components/NavLink.js.map +1 -1
  108. package/build/production/shared/components/TextArea/index.js +4 -4
  109. package/build/production/shared/components/TextArea/index.js.map +1 -1
  110. package/build/production/shared/components/WithTooltip/Tooltip.js +37 -38
  111. package/build/production/shared/components/WithTooltip/Tooltip.js.map +1 -1
  112. package/build/production/shared/components/WithTooltip/index.js +4 -4
  113. package/build/production/shared/components/WithTooltip/index.js.map +1 -1
  114. package/build/production/shared/components/YouTubeVideo/index.js +3 -2
  115. package/build/production/shared/components/YouTubeVideo/index.js.map +1 -1
  116. package/build/production/shared/components/selectors/CustomDropdown/Options/index.js +2 -2
  117. package/build/production/shared/components/selectors/CustomDropdown/Options/index.js.map +1 -1
  118. package/build/production/shared/components/selectors/CustomDropdown/index.js +2 -2
  119. package/build/production/shared/components/selectors/CustomDropdown/index.js.map +1 -1
  120. package/build/production/shared/components/selectors/NativeDropdown/index.js +2 -2
  121. package/build/production/shared/components/selectors/NativeDropdown/index.js.map +1 -1
  122. package/build/production/shared/components/selectors/Switch/index.js +1 -1
  123. package/build/production/shared/components/selectors/Switch/index.js.map +1 -1
  124. package/build/production/shared/utils/config.js +6 -4
  125. package/build/production/shared/utils/config.js.map +1 -1
  126. package/build/production/shared/utils/globalState.js +4 -1
  127. package/build/production/shared/utils/globalState.js.map +1 -1
  128. package/build/production/shared/utils/isomorphy/buildInfo.js.map +1 -1
  129. package/build/production/shared/utils/isomorphy/environment-check.js +5 -1
  130. package/build/production/shared/utils/isomorphy/environment-check.js.map +1 -1
  131. package/build/production/shared/utils/isomorphy/index.js +1 -3
  132. package/build/production/shared/utils/isomorphy/index.js.map +1 -1
  133. package/build/production/shared/utils/jest/E2eSsrEnv.js +15 -8
  134. package/build/production/shared/utils/jest/E2eSsrEnv.js.map +1 -1
  135. package/build/production/shared/utils/jest/global.js +1 -1
  136. package/build/production/shared/utils/jest/global.js.map +1 -1
  137. package/build/production/shared/utils/jest/index.js +13 -5
  138. package/build/production/shared/utils/jest/index.js.map +1 -1
  139. package/build/production/shared/utils/splitComponent.js +16 -8
  140. package/build/production/shared/utils/splitComponent.js.map +1 -1
  141. package/build/production/shared/utils/time.js +2 -2
  142. package/build/production/shared/utils/time.js.map +1 -1
  143. package/build/production/shared/utils/webpack.js +5 -2
  144. package/build/production/shared/utils/webpack.js.map +1 -1
  145. package/build/production/web.bundle.js +1 -1
  146. package/build/production/web.bundle.js.map +1 -1
  147. package/build/types-code/client/getInj.d.ts +1 -1
  148. package/build/types-code/client/index.d.ts +2 -2
  149. package/build/types-code/client/init.d.ts +1 -1
  150. package/build/types-code/index.d.ts +6 -5
  151. package/build/types-code/server/Cache.d.ts +1 -2
  152. package/build/types-code/server/index.d.ts +4 -5
  153. package/build/types-code/server/renderer.d.ts +9 -11
  154. package/build/types-code/server/server.d.ts +3 -5
  155. package/build/types-code/server/utils/errors.d.ts +1 -1
  156. package/build/types-code/shared/components/Button/index.d.ts +2 -2
  157. package/build/types-code/shared/components/TextArea/index.d.ts +3 -3
  158. package/build/types-code/shared/components/WithTooltip/Tooltip.d.ts +7 -1
  159. package/build/types-code/shared/components/index.d.ts +12 -12
  160. package/build/types-code/shared/utils/config.d.ts +1 -1
  161. package/build/types-code/shared/utils/globalState.d.ts +3 -6
  162. package/build/types-code/shared/utils/isomorphy/index.d.ts +1 -3
  163. package/build/types-code/shared/utils/jest/E2eSsrEnv.d.ts +1 -1
  164. package/build/types-code/shared/utils/jest/global.d.ts +4 -4
  165. package/build/types-code/shared/utils/jest/index.d.ts +2 -2
  166. package/build/types-code/shared/utils/webpack.d.ts +1 -1
  167. package/config/babel/node-ssr.d.ts +3 -2
  168. package/config/babel/node-ssr.js +5 -7
  169. package/config/babel/webpack.d.ts +3 -11
  170. package/config/babel/webpack.js +15 -15
  171. package/config/eslint/default.mjs +32 -0
  172. package/config/jest/default.js +10 -6
  173. package/config/jest/resolver.js +2 -0
  174. package/config/jest/setup.js +2 -2
  175. package/config/stylelint/default.js +3 -0
  176. package/config/webpack/app-base.d.ts +0 -6
  177. package/config/webpack/app-base.js +64 -70
  178. package/config/webpack/app-development.d.ts +2 -2
  179. package/config/webpack/app-development.js +8 -12
  180. package/config/webpack/app-production.js +1 -0
  181. package/config/webpack/lib-base.js +20 -18
  182. package/config/webpack/lib-development.js +1 -0
  183. package/config/webpack/lib-production.js +1 -0
  184. package/config/workbox/default.js +2 -5
  185. package/dev-styles.js +1 -0
  186. package/eslint.config.mjs +13 -0
  187. package/node-entry.js +7 -2
  188. package/null.js +1 -0
  189. package/package.json +27 -35
  190. package/prod-styles.js +1 -0
  191. package/src/client/getInj.ts +8 -3
  192. package/src/client/index.tsx +4 -4
  193. package/src/client/init.ts +18 -15
  194. package/src/index.ts +8 -3
  195. package/src/server/Cache.ts +7 -15
  196. package/src/server/index.ts +30 -21
  197. package/src/server/renderer.tsx +77 -67
  198. package/src/server/server.ts +41 -21
  199. package/src/server/utils/errors.ts +6 -3
  200. package/src/shared/components/Button/index.tsx +4 -5
  201. package/src/shared/components/Checkbox/index.tsx +10 -7
  202. package/src/shared/components/GenericLink/index.tsx +14 -7
  203. package/src/shared/components/Input/index.tsx +4 -1
  204. package/src/shared/components/Link.tsx +9 -5
  205. package/src/shared/components/MetaTags.tsx +21 -15
  206. package/src/shared/components/Modal/index.tsx +12 -11
  207. package/src/shared/components/NavLink.tsx +10 -5
  208. package/src/shared/components/TextArea/index.tsx +17 -9
  209. package/src/shared/components/WithTooltip/{Tooltip.tsx → Tooltip.ts} +35 -39
  210. package/src/shared/components/WithTooltip/index.tsx +36 -30
  211. package/src/shared/components/YouTubeVideo/index.tsx +10 -6
  212. package/src/shared/components/selectors/CustomDropdown/Options/index.tsx +5 -6
  213. package/src/shared/components/selectors/CustomDropdown/index.tsx +10 -14
  214. package/src/shared/components/selectors/NativeDropdown/index.tsx +5 -7
  215. package/src/shared/components/selectors/Switch/index.tsx +19 -17
  216. package/src/shared/utils/config.ts +12 -5
  217. package/src/shared/utils/globalState.ts +8 -5
  218. package/src/shared/utils/isomorphy/buildInfo.ts +1 -1
  219. package/src/shared/utils/isomorphy/environment-check.ts +5 -1
  220. package/src/shared/utils/isomorphy/index.ts +4 -6
  221. package/src/shared/utils/jest/E2eSsrEnv.ts +64 -39
  222. package/src/shared/utils/jest/global.ts +6 -8
  223. package/src/shared/utils/jest/{index.tsx → index.ts} +25 -12
  224. package/src/shared/utils/splitComponent.tsx +44 -25
  225. package/src/shared/utils/time.ts +16 -9
  226. package/src/shared/utils/webpack.ts +19 -14
  227. package/webpack.config.ts +36 -10
  228. package/config/eslint/default.json +0 -30
  229. package/config/eslint/jest.json +0 -19
  230. package/config/eslint/typescript.js +0 -46
@@ -1,7 +1,7 @@
1
1
  // Initialization of client-side code.
2
2
  /* global document */
3
3
 
4
- import { type ComponentType } from 'react';
4
+ import type { ComponentType } from 'react';
5
5
  import { createRoot, hydrateRoot } from 'react-dom/client';
6
6
  import { HelmetProvider } from '@dr.pogodin/react-helmet';
7
7
  import { BrowserRouter } from 'react-router';
@@ -12,7 +12,7 @@ import getInj from './getInj';
12
12
 
13
13
  type OptionsT = {
14
14
  dontHydrate?: boolean;
15
- initialState?: any;
15
+ initialState?: unknown;
16
16
  };
17
17
 
18
18
  /**
@@ -23,11 +23,11 @@ type OptionsT = {
23
23
  export default function Launch(
24
24
  Application: ComponentType,
25
25
  options: OptionsT = {},
26
- ) {
26
+ ): void {
27
27
  const container = document.getElementById('react-view');
28
28
  if (!container) throw Error('Failed to find container for React app');
29
29
  const scene = (
30
- <GlobalStateProvider initialState={getInj().ISTATE || options.initialState}>
30
+ <GlobalStateProvider initialState={getInj().ISTATE ?? options.initialState}>
31
31
  <BrowserRouter>
32
32
  <HelmetProvider>
33
33
  <Application />
@@ -10,8 +10,8 @@ const buildInfo = getBuildInfo();
10
10
 
11
11
  // TODO: Should be moved into buildInfo module?
12
12
  declare global {
13
+ // eslint-disable-next-line @typescript-eslint/consistent-type-definitions
13
14
  interface Window {
14
- // eslint-disable-next-line @typescript-eslint/naming-convention, no-underscore-dangle
15
15
  __DEV_BUILD_INFO__: BuildInfoT | undefined;
16
16
  }
17
17
  }
@@ -21,29 +21,32 @@ declare const BUILD_INFO: BuildInfoT | undefined;
21
21
  if (process.env.NODE_ENV !== 'production') {
22
22
  // eslint-disable-next-line no-console
23
23
  console.warn('Dev mode: "BUILD_INFO" attached to the global "window"');
24
+
24
25
  // eslint-disable-next-line no-underscore-dangle
25
26
  window.__DEV_BUILD_INFO__ = BUILD_INFO;
26
27
  }
27
28
 
28
29
  /* TODO: A proper logger should be moved to `@dr.pogodin/react-utils`. */
29
- /* eslint-disable no-console */
30
30
  const { useServiceWorker } = buildInfo;
31
31
  if (useServiceWorker) {
32
32
  const { navigator } = window;
33
33
  if ('serviceWorker' in navigator) {
34
- window.addEventListener('load', async () => {
35
- try {
36
- // Note: no matter the "publicPath", we want to serve the service worker
37
- // from the web app root, to allow it control any and all pages of the
38
- // web app (otherwise, it will be restricted to the scope of its path).
39
- // The server takes it into account.
40
- const reg = await navigator
41
- .serviceWorker.register('/__service-worker.js');
42
- console.log('SW registered:', reg);
43
- } catch (err) {
44
- console.log('SW registration failed:', err);
45
- }
34
+ window.addEventListener('load', () => {
35
+ void (async () => {
36
+ try {
37
+ // Note: no matter the "publicPath", we want to serve the service worker
38
+ // from the web app root, to allow it control any and all pages of the
39
+ // web app (otherwise, it will be restricted to the scope of its path).
40
+ // The server takes it into account.
41
+ const reg = await navigator
42
+ .serviceWorker.register('/__service-worker.js');
43
+ // eslint-disable-next-line no-console
44
+ console.log('SW registered:', reg);
45
+ } catch (err) {
46
+ // eslint-disable-next-line no-console
47
+ console.log('SW registration failed:', err);
48
+ }
49
+ })();
46
50
  });
47
51
  }
48
52
  }
49
- /* eslint-enable no-console */
package/src/index.ts CHANGED
@@ -2,11 +2,16 @@ import 'styles/global.scss';
2
2
 
3
3
  import { webpack } from 'utils';
4
4
 
5
- import type ServerFactoryT from './server';
5
+ import type * as ClientM from './client';
6
+ import type * as ServerFactoryM from './server';
6
7
 
7
- const server = webpack.requireWeak('./server', __dirname) as (typeof ServerFactoryT) | null;
8
+ const server = webpack.requireWeak<typeof ServerFactoryM>('./server', __dirname);
8
9
 
9
- const client = server ? undefined : require('./client').default;
10
+ const client = server
11
+ ? undefined
12
+
13
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
14
+ : (require('./client') as typeof ClientM).default;
10
15
 
11
16
  export {
12
17
  type AsyncCollectionT,
@@ -10,13 +10,9 @@ type CachedItemT<DatumT> = {
10
10
  export default class Cache<DatumT> {
11
11
  private items: Record<string, CachedItemT<DatumT>> = {};
12
12
 
13
- private maxSize: number;
14
-
15
13
  private size = 0;
16
14
 
17
- constructor(maxSize: number) {
18
- this.maxSize = maxSize;
19
- }
15
+ constructor(private maxSize: number) { }
20
16
 
21
17
  /**
22
18
  * Cache lookup.
@@ -39,12 +35,11 @@ export default class Cache<DatumT> {
39
35
 
40
36
  /**
41
37
  * Adds item to cache.
42
- * @ignore
43
38
  * @param data Item to add.
44
39
  * @param key Key to store the item at.
45
40
  * @param size Byte size of the item.
46
41
  */
47
- add(data: DatumT, key: string, size: number) {
42
+ add(data: DatumT, key: string, size: number): void {
48
43
  const cached = this.items[key];
49
44
  if (cached) this.size -= cached.size;
50
45
 
@@ -55,14 +50,11 @@ export default class Cache<DatumT> {
55
50
  const entries = Object.entries(this.items);
56
51
  entries.sort((a, b) => a[1].timestamp - b[1].timestamp);
57
52
 
58
- for (let i = 0; i < entries.length; ++i) {
59
- const entry = entries[i];
60
- if (entry) {
61
- const [itemKey, item] = entry;
62
- delete this.items[itemKey];
63
- this.size -= item.size;
64
- if (this.size < this.maxSize / 2) break;
65
- }
53
+ for (const entry of entries) {
54
+ const [itemKey, item] = entry;
55
+ delete this.items[itemKey];
56
+ this.size -= item.size;
57
+ if (this.size < this.maxSize / 2) break;
66
58
  }
67
59
  }
68
60
  }
@@ -1,3 +1,4 @@
1
+ // eslint-disable-next-line import/no-unassigned-import
1
2
  import 'source-map-support/register';
2
3
 
3
4
  import http from 'http';
@@ -12,10 +13,12 @@ import {
12
13
  toNumber,
13
14
  } from 'lodash';
14
15
 
15
- /* Polyfill required by ReactJS. */
16
+ // Polyfill required by ReactJS.
17
+ // TODO: Double-check, if it is still required by React v19?
18
+ // eslint-disable-next-line import/no-unassigned-import
16
19
  import 'raf/polyfill';
17
20
 
18
- import { type Configuration } from 'webpack';
21
+ import type { Configuration } from 'webpack';
19
22
 
20
23
  import serverFactory, {
21
24
  type OptionsT as ServerOptionsT,
@@ -27,11 +30,11 @@ import { SCRIPT_LOCATIONS, newDefaultLogger } from './renderer';
27
30
 
28
31
  import { errors } from './utils';
29
32
 
30
- export {
31
- type BeforeRenderResT,
32
- type BeforeRenderT,
33
- type ConfigT,
34
- type ServerSsrContext,
33
+ export type {
34
+ BeforeRenderResT,
35
+ BeforeRenderT,
36
+ ConfigT,
37
+ ServerSsrContext,
35
38
  } from './renderer';
36
39
 
37
40
  export { errors, getDefaultCspSettings, type ServerT };
@@ -53,7 +56,7 @@ type OptionsT = ServerOptionsT & {
53
56
  https?: {
54
57
  cert: string;
55
58
  key: string;
56
- },
59
+ };
57
60
 
58
61
  // TODO: Should we limit it to number | string, and throw if it is different value?
59
62
  port?: false | number | string;
@@ -175,22 +178,27 @@ type OptionsT = ServerOptionsT & {
175
178
  * @param {number} [options.maxSsrRounds=10] Maximum number of SSR rounds.
176
179
  * @param {number} [options.ssrTimeout=1000] SSR timeout in milliseconds,
177
180
  * defaults to 1 second.
178
- * @return {Promise<{ expressServer: object, httpServer: object }>} Resolves to
179
- * an object with created Express and HTTP servers.
181
+ * @return Resolves to an object with created Express and HTTP servers.
180
182
  */
181
- export default async function launchServer(webpackConfig: Configuration, options: OptionsT) {
183
+ export default async function launchServer(
184
+ webpackConfig: Configuration,
185
+ options: OptionsT = {},
186
+ ): Promise<{
187
+ expressServer: ServerT;
188
+ httpServer: http.Server;
189
+ }> {
182
190
  /* Options normalization. */
183
- const ops = options ? cloneDeep(options) : {};
191
+ const ops = cloneDeep(options);
192
+
193
+ // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
184
194
  ops.port = normalizePort(ops.port || process.env.PORT || 3000);
185
195
  defaults(ops, { httpsRedirect: true });
186
196
 
187
197
  // TODO: Need a separate type for normalized options, which guarantees
188
198
  // the logger is set!
189
- if (ops.logger === undefined) {
190
- ops.logger = newDefaultLogger({
191
- defaultLogLevel: ops.defaultLoggerLogLevel,
192
- });
193
- }
199
+ ops.logger ??= newDefaultLogger({
200
+ defaultLogLevel: ops.defaultLoggerLogLevel,
201
+ });
194
202
 
195
203
  /* Creates servers, resolves and sets the port. */
196
204
  const expressServer = await serverFactory(webpackConfig, ops);
@@ -200,22 +208,23 @@ export default async function launchServer(webpackConfig: Configuration, options
200
208
  httpServer = https.createServer({
201
209
  cert: ops.https.cert,
202
210
  key: ops.https.key,
203
- }, expressServer);
204
- } else httpServer = http.createServer(expressServer);
211
+ }, expressServer as unknown as () => void);
212
+ } else httpServer = http.createServer(expressServer as unknown as () => void);
205
213
 
206
214
  /* Sets error handler for HTTP(S) server. */
207
215
  httpServer.on('error', (error: Error) => {
208
- if ((error as any).syscall !== 'listen') throw error;
216
+ if ((error as { syscall?: string }).syscall !== 'listen') throw error;
209
217
  const bind = isString(ops.port) ? `Pipe ${ops.port}` : `Port ${ops.port}`;
210
218
 
211
219
  /* Human-readable message for some specific listen errors. */
212
- switch ((error as any).code) {
220
+ switch ((error as { code?: string }).code) {
213
221
  case 'EACCES':
214
222
  ops.logger!.error(`${bind} requires elevated privileges`);
215
223
  return process.exit(1);
216
224
  case 'EADDRINUSE':
217
225
  ops.logger!.error(`${bind} is already in use`);
218
226
  return process.exit(1);
227
+ case undefined:
219
228
  default:
220
229
  throw error;
221
230
  }
@@ -5,8 +5,8 @@
5
5
  import fs from 'fs';
6
6
  import path from 'path';
7
7
 
8
- import { type Request, type RequestHandler } from 'express';
9
- import { type ComponentType } from 'react';
8
+ import type { Request, RequestHandler } from 'express';
9
+ import type { ComponentType } from 'react';
10
10
  import { Writable } from 'stream';
11
11
  import { brotliCompress, brotliDecompress } from 'zlib';
12
12
  import winston from 'winston';
@@ -28,14 +28,14 @@ import config from 'config';
28
28
  import forge from 'node-forge';
29
29
 
30
30
  import { prerenderToNodeStream } from 'react-dom/static';
31
- import { HelmetProvider } from '@dr.pogodin/react-helmet';
31
+ import { HelmetProvider, type HelmetDataContext } from '@dr.pogodin/react-helmet';
32
32
  import { StaticRouter } from 'react-router';
33
33
  import serializeJs from 'serialize-javascript';
34
34
  import { type BuildInfoT, setBuildInfo } from 'utils/isomorphy/buildInfo';
35
35
 
36
- import { type ChunkGroupsT, type SsrContextT } from 'utils/globalState';
36
+ import type { ChunkGroupsT, SsrContextT } from 'utils/globalState';
37
37
 
38
- import { type Configuration } from 'webpack';
38
+ import type { Configuration, Stats } from 'webpack';
39
39
 
40
40
  import Cache from './Cache';
41
41
 
@@ -44,13 +44,19 @@ const sanitizedConfig = omit(config, 'SECRET');
44
44
  // Note: These type definitions for logger are copied from Winston logger,
45
45
  // then simplified to make it easier to fit an alternative logger into this
46
46
  // interface.
47
+ // eslint-disable-next-line @typescript-eslint/consistent-type-definitions
47
48
  interface LogMethodI {
48
- (level: string, message: string, ...meta: any[]): void;
49
+ // eslint-disable-next-line @typescript-eslint/prefer-function-type
50
+ (level: string, message: string, ...meta: unknown[]): void;
49
51
  }
52
+
53
+ // eslint-disable-next-line @typescript-eslint/consistent-type-definitions
50
54
  interface LeveledLogMethodI {
51
- (message: string, ...meta: any[]): void;
55
+ // eslint-disable-next-line @typescript-eslint/prefer-function-type
56
+ (message: string, ...meta: unknown[]): void;
52
57
  }
53
58
 
59
+ // eslint-disable-next-line @typescript-eslint/consistent-type-definitions
54
60
  export interface LoggerI {
55
61
  debug: LeveledLogMethodI;
56
62
  error: LeveledLogMethodI;
@@ -59,7 +65,6 @@ export interface LoggerI {
59
65
  warn: LeveledLogMethodI;
60
66
  }
61
67
 
62
- // eslint-disable-next-line @typescript-eslint/naming-convention
63
68
  export enum SCRIPT_LOCATIONS {
64
69
  BODY_OPEN = 'BODY_OPEN',
65
70
  DEFAULT = 'DEFAULT',
@@ -82,7 +87,7 @@ export class ServerSsrContext<StateT>
82
87
  chunkGroups: ChunkGroupsT,
83
88
  initialState?: StateT,
84
89
  ) {
85
- super(cloneDeep(initialState) || ({} as StateT));
90
+ super(cloneDeep(initialState) ?? ({} as StateT));
86
91
  this.chunkGroups = chunkGroups;
87
92
  this.req = req;
88
93
  }
@@ -104,7 +109,7 @@ type ScriptT = {
104
109
  */
105
110
  function getBuildInfo(context: string) {
106
111
  const url = path.resolve(context, '.build-info');
107
- return JSON.parse(fs.readFileSync(url, 'utf8'));
112
+ return JSON.parse(fs.readFileSync(url, 'utf8')) as BuildInfoT;
108
113
  }
109
114
 
110
115
  /**
@@ -119,8 +124,9 @@ function readChunkGroupsJson(buildDir: string) {
119
124
  const url = path.resolve(buildDir, '__chunk_groups__.json');
120
125
  let res;
121
126
  try {
122
- res = JSON.parse(fs.readFileSync(url, 'utf8'));
123
- } catch (err) {
127
+ res = JSON.parse(fs.readFileSync(url, 'utf8')) as Record<string, string[]>;
128
+ } catch {
129
+ // TODO: Should we message the error here somehow?
124
130
  res = null;
125
131
  }
126
132
  return res;
@@ -134,7 +140,10 @@ function readChunkGroupsJson(buildDir: string) {
134
140
  * 1. cipher - a new Cipher, ready for encryption;
135
141
  * 2. iv - initial vector used by the cipher.
136
142
  */
137
- function prepareCipher(key: string) {
143
+ async function prepareCipher(key: string): Promise<{
144
+ cipher: forge.cipher.BlockCipher;
145
+ iv: string;
146
+ }> {
138
147
  return new Promise((resolve, reject) => {
139
148
  forge.random.getBytes(32, (err, iv) => {
140
149
  if (err) reject(err);
@@ -150,22 +159,17 @@ function prepareCipher(key: string) {
150
159
  /**
151
160
  * Given an incoming HTTP requests, it deduces whether Brotli-encoded responses
152
161
  * are acceptable to the caller.
153
- * @param {object} req
154
- * @return {boolean}
155
- * @ignore
162
+ * @param req
156
163
  */
157
- export function isBrotliAcceptable(req: Request) {
164
+ export function isBrotliAcceptable(req: Request): boolean {
158
165
  const acceptable = req.get('accept-encoding');
159
166
  if (acceptable) {
160
167
  const ops = acceptable.split(',');
161
- for (let i = 0; i < ops.length; ++i) {
162
- const op = ops[i];
163
- if (op) {
164
- const [type, priority] = op.trim().split(';q=');
165
- if ((type === '*' || type === 'br')
168
+ for (const op of ops) {
169
+ const [type, priority] = op.trim().split(';q=');
170
+ if ((type === '*' || type === 'br')
166
171
  && (!priority || parseFloat(priority) > 0)) {
167
- return true;
168
- }
172
+ return true;
169
173
  }
170
174
  }
171
175
  }
@@ -192,14 +196,12 @@ function groupExtraScripts(scripts: Array<string | ScriptT> = []) {
192
196
  [SCRIPT_LOCATIONS.DEFAULT]: '',
193
197
  [SCRIPT_LOCATIONS.HEAD_OPEN]: '',
194
198
  };
195
- for (let i = 0; i < scripts.length; ++i) {
196
- const script = scripts[i];
199
+ for (const script of scripts) {
197
200
  if (isString(script)) {
198
201
  if (script) res[SCRIPT_LOCATIONS.DEFAULT] += script;
199
- } else if (script?.code) {
200
- if (res[script.location] !== undefined) {
201
- res[script.location] += script.code;
202
- } else throw Error(`Invalid location "${script.location}"`);
202
+ } else if (script.code) {
203
+ if (script.location in res) res[script.location] += script.code;
204
+ else throw Error(`Invalid location "${script.location}"`);
203
205
  }
204
206
  }
205
207
  return res;
@@ -213,10 +215,9 @@ function groupExtraScripts(scripts: Array<string | ScriptT> = []) {
213
215
  */
214
216
  export function newDefaultLogger({
215
217
  defaultLogLevel = 'info',
216
- } = {}) {
218
+ } = {}): winston.Logger {
217
219
  const { format, transports } = winston;
218
220
  return winston.createLogger({
219
- level: defaultLogLevel,
220
221
  format: format.combine(
221
222
  format.splat(),
222
223
  format.timestamp(),
@@ -229,15 +230,16 @@ export function newDefaultLogger({
229
230
  stack,
230
231
  ...rest
231
232
  }) => {
232
- let res = `${level}\t(at ${timestamp}):\t${message}`;
233
+ let res = `${level}\t(at ${timestamp as string}):\t${message as string}`;
233
234
  if (Object.keys(rest).length) {
234
235
  res += `\n${JSON.stringify(rest, null, 2)}`;
235
236
  }
236
- if (stack) res += `\n${stack}`;
237
+ if (stack) res += `\n${stack as string}`;
237
238
  return res;
238
239
  },
239
240
  ),
240
241
  ),
242
+ level: defaultLogLevel,
241
243
  transports: [new transports.Console()],
242
244
  });
243
245
  }
@@ -249,7 +251,7 @@ export type ConfigT = {
249
251
  export type BeforeRenderResT = {
250
252
  configToInject?: ConfigT;
251
253
  extraScripts?: Array<ScriptT | string>;
252
- initialState?: any;
254
+ initialState?: unknown;
253
255
  };
254
256
 
255
257
  export type BeforeRenderT =
@@ -310,7 +312,7 @@ export default function factory(
310
312
  options: OptionsT,
311
313
  ): RequestHandler {
312
314
  const ops: OptionsT = defaults(clone(options), {
313
- beforeRender: () => Promise.resolve({}),
315
+ beforeRender: async () => Promise.resolve({}),
314
316
  maxSsrRounds: 10,
315
317
  ssrTimeout: 1000,
316
318
  staticCacheSize: 1.e7,
@@ -319,21 +321,20 @@ export default function factory(
319
321
  // Note: in normal use the default logger is created and set in the root
320
322
  // server function, and this initialization is for testing uses, where
321
323
  // renderer is imported directly.
322
- if (ops.logger === undefined) {
323
- ops.logger = newDefaultLogger({
324
- defaultLogLevel: ops.defaultLoggerLogLevel,
325
- });
326
- }
324
+ ops.logger ??= newDefaultLogger({
325
+ defaultLogLevel: ops.defaultLoggerLogLevel,
326
+ });
327
327
 
328
- const buildInfo = ops.buildInfo || getBuildInfo(webpackConfig.context!);
328
+ const buildInfo = ops.buildInfo ?? getBuildInfo(webpackConfig.context!);
329
329
  setBuildInfo(buildInfo);
330
330
 
331
331
  // publicPath from webpack.output has a trailing slash at the end.
332
332
  const { publicPath, path: outputPath } = webpackConfig.output!;
333
333
 
334
334
  const manifestLink = fs.existsSync(`${outputPath}/manifest.json`)
335
- ? `<link rel="manifest" href="${publicPath}manifest.json">` : '';
335
+ ? `<link rel="manifest" href="${publicPath as string}manifest.json">` : '';
336
336
 
337
+ // eslint-disable-next-line @typescript-eslint/consistent-type-definitions
337
338
  interface BufferWithNonce extends Buffer {
338
339
  nonce: string;
339
340
  }
@@ -347,6 +348,8 @@ export default function factory(
347
348
 
348
349
  const CHUNK_GROUPS = readChunkGroupsJson(outputPath!);
349
350
 
351
+ // TODO: Look at it later.
352
+ // eslint-disable-next-line complexity
350
353
  return async (req, res, next) => {
351
354
  try {
352
355
  // Ensures any caches always revalidate HTML markup before reuse.
@@ -377,7 +380,11 @@ export default function factory(
377
380
  // .replaceAll() method instead relying on reg. expression for
378
381
  // global matching.
379
382
  const regex = new RegExp(buffer.nonce, 'g');
380
- h = h.replace(regex, (req as any).nonce);
383
+
384
+ // TODO: It should be implemented more careful.
385
+ h = h.replace(regex, (req as unknown as {
386
+ nonce: string;
387
+ }).nonce);
381
388
  }
382
389
  if (status !== 200) res.status(status);
383
390
  res.send(h);
@@ -399,25 +406,26 @@ export default function factory(
399
406
  cipher,
400
407
  iv,
401
408
  }] = await Promise.all([
402
- ops.beforeRender!(req, sanitizedConfig as any),
403
- prepareCipher(buildInfo.key) as Promise<any>,
409
+ ops.beforeRender!(req, sanitizedConfig as unknown as ConfigT),
410
+ prepareCipher(buildInfo.key),
404
411
  ]);
405
412
 
406
- let helmet: any;
413
+ let helmet: HelmetDataContext['helmet'];
407
414
 
408
415
  // Gets the mapping between code chunk names and their asset files.
409
416
  // These data come from the Webpack compilation, either from the stats
410
417
  // attached to the request (in dev mode), or from a file output during
411
418
  // the build (in prod mode).
412
419
  let chunkGroups: ChunkGroupsT;
413
- const webpackStats = get(res.locals, 'webpack.devMiddleware.stats');
420
+ const webpackStats = get(res.locals, 'webpack.devMiddleware.stats') as Stats | undefined;
414
421
  if (webpackStats) {
415
422
  chunkGroups = mapValues(
416
423
  webpackStats.toJson({
417
424
  all: false,
418
425
  chunkGroups: true,
419
426
  }).namedChunkGroups,
420
- (item) => item.assets.map(({ name }: { name: string }) => name),
427
+ (item) => item.assets?.map(({ name }: { name: string }) => name)
428
+ ?? [],
421
429
  );
422
430
  } else if (CHUNK_GROUPS) chunkGroups = CHUNK_GROUPS;
423
431
  else chunkGroups = {};
@@ -439,7 +447,7 @@ export default function factory(
439
447
 
440
448
  // TODO: prerenderToNodeStream has (abort) "signal" option,
441
449
  // and we should wire it up to the SSR timeout below.
442
- const helmetContext = {};
450
+ const helmetContext = {} as HelmetDataContext;
443
451
  const { prelude } = await prerenderToNodeStream(
444
452
  <GlobalStateProvider
445
453
  initialState={ssrContext.state}
@@ -451,9 +459,11 @@ export default function factory(
451
459
  </HelmetProvider>
452
460
  </StaticRouter>
453
461
  </GlobalStateProvider>,
454
- { onError: (error) => { throw error; } },
462
+ { onError: (error) => {
463
+ throw error;
464
+ } },
455
465
  );
456
- ({ helmet } = helmetContext as any);
466
+ ({ helmet } = helmetContext);
457
467
 
458
468
  return prelude;
459
469
  };
@@ -461,18 +471,16 @@ export default function factory(
461
471
  let ssrRound = 0;
462
472
  let bailed = false;
463
473
  for (; ssrRound < ops.maxSsrRounds!; ++ssrRound) {
464
- stream = await renderPass(); // eslint-disable-line no-await-in-loop
474
+ stream = await renderPass();
465
475
 
466
476
  if (!ssrContext.dirty) break;
467
477
 
468
- /* eslint-disable no-await-in-loop */
469
478
  const timeout = ops.ssrTimeout! + ssrStart - Date.now();
470
479
  bailed = timeout <= 0 || !await Promise.race([
471
480
  Promise.allSettled(ssrContext.pending),
472
481
  timer(timeout).then(() => false),
473
482
  ]);
474
483
  if (bailed) break;
475
- /* eslint-enable no-await-in-loop */
476
484
  }
477
485
 
478
486
  let logMsg;
@@ -491,7 +499,7 @@ export default function factory(
491
499
  await new Promise((ready) => {
492
500
  stream!.pipe(new Writable({
493
501
  destroy: ready,
494
- write: (chunk, _, done) => {
502
+ write: (chunk: { toString: () => string }, _, done) => {
495
503
  appHtmlMarkup += chunk.toString();
496
504
  done();
497
505
  },
@@ -506,7 +514,7 @@ export default function factory(
506
514
  * better than injection of a plain text. */
507
515
  const payload = serializeJs({
508
516
  CHUNK_GROUPS: chunkGroups,
509
- CONFIG: configToInject || sanitizedConfig,
517
+ CONFIG: configToInject ?? sanitizedConfig,
510
518
  ISTATE: ssrContext.state,
511
519
  }, {
512
520
  ignoreFunction: true,
@@ -538,22 +546,22 @@ export default function factory(
538
546
  let scriptChunkString = '';
539
547
  chunkSet.forEach((chunk) => {
540
548
  if (chunk.endsWith('.css')) {
541
- styleChunkString += `<link href="${publicPath}${chunk}" rel="stylesheet">`;
549
+ styleChunkString += `<link href="${publicPath as string}${chunk}" rel="stylesheet">`;
542
550
  } else if (
543
551
  chunk.endsWith('.js')
544
- // In dev mode HMR adds JS updates into asset arrays,
545
- // and they (updates) should be ignored.
546
- && !chunk.endsWith('.hot-update.js')
552
+ // In dev mode HMR adds JS updates into asset arrays,
553
+ // and they (updates) should be ignored.
554
+ && !chunk.endsWith('.hot-update.js')
547
555
  ) {
548
- scriptChunkString += `<script src="${publicPath}${chunk}" type="application/javascript"></script>`;
556
+ scriptChunkString += `<script src="${publicPath as string}${chunk}" type="application/javascript"></script>`;
549
557
  }
550
558
  });
551
559
 
552
560
  const grouppedExtraScripts = groupExtraScripts(extraScripts);
553
561
 
554
- const faviconLink = ops.favicon ? (
555
- '<link rel="shortcut icon" href="/favicon.ico">'
556
- ) : '';
562
+ const faviconLink = ops.favicon
563
+ ? '<link rel="shortcut icon" href="/favicon.ico">'
564
+ : '';
557
565
 
558
566
  const html = `<!DOCTYPE html>
559
567
  <html lang="en">
@@ -591,8 +599,10 @@ export default function factory(
591
599
  const b = buffer as BufferWithNonce;
592
600
  if (error) failed(error);
593
601
  else {
594
- b.nonce = (req as any).nonce; // eslint-disable-line no-param-reassign
595
- cache!.add({ buffer: b, status }, cacheRef!.key, buffer.length);
602
+ b.nonce = (req as unknown as {
603
+ nonce: string;
604
+ }).nonce;
605
+ cache!.add({ buffer: b, status }, cacheRef.key, buffer.length);
596
606
  done();
597
607
  }
598
608
  });