@dr.pogodin/react-utils 1.42.0 → 1.43.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/babel.config.js +3 -0
- package/bin/build.js +5 -8
- package/bin/setup.js +2 -1
- package/build/development/client/getInj.js +7 -2
- package/build/development/client/getInj.js.map +1 -1
- package/build/development/client/index.js +1 -2
- package/build/development/client/index.js.map +1 -1
- package/build/development/client/init.js +16 -13
- package/build/development/client/init.js.map +1 -1
- package/build/development/index.js +4 -1
- package/build/development/index.js.map +1 -1
- package/build/development/server/Cache.js +5 -9
- package/build/development/server/Cache.js.map +1 -1
- package/build/development/server/index.js +14 -11
- package/build/development/server/index.js.map +1 -1
- package/build/development/server/renderer.js +36 -40
- package/build/development/server/renderer.js.map +1 -1
- package/build/development/server/server.js +19 -12
- package/build/development/server/server.js.map +1 -1
- package/build/development/server/utils/errors.js.map +1 -1
- package/build/development/shared/components/Button/index.js +2 -3
- package/build/development/shared/components/Button/index.js.map +1 -1
- package/build/development/shared/components/Checkbox/index.js +3 -1
- package/build/development/shared/components/Checkbox/index.js.map +1 -1
- package/build/development/shared/components/GenericLink/index.js +13 -6
- package/build/development/shared/components/GenericLink/index.js.map +1 -1
- package/build/development/shared/components/Input/index.js +5 -1
- package/build/development/shared/components/Input/index.js.map +1 -1
- package/build/development/shared/components/Link.js +5 -6
- package/build/development/shared/components/Link.js.map +1 -1
- package/build/development/shared/components/MetaTags.js +31 -24
- package/build/development/shared/components/MetaTags.js.map +1 -1
- package/build/development/shared/components/Modal/index.js +13 -6
- package/build/development/shared/components/Modal/index.js.map +1 -1
- package/build/development/shared/components/NavLink.js +6 -6
- package/build/development/shared/components/NavLink.js.map +1 -1
- package/build/development/shared/components/TextArea/index.js +6 -3
- package/build/development/shared/components/TextArea/index.js.map +1 -1
- package/build/development/shared/components/WithTooltip/Tooltip.js +35 -39
- package/build/development/shared/components/WithTooltip/Tooltip.js.map +1 -1
- package/build/development/shared/components/WithTooltip/index.js +27 -21
- package/build/development/shared/components/WithTooltip/index.js.map +1 -1
- package/build/development/shared/components/YouTubeVideo/index.js +4 -3
- package/build/development/shared/components/YouTubeVideo/index.js.map +1 -1
- package/build/development/shared/components/selectors/CustomDropdown/Options/index.js +4 -5
- package/build/development/shared/components/selectors/CustomDropdown/Options/index.js.map +1 -1
- package/build/development/shared/components/selectors/CustomDropdown/index.js +7 -9
- package/build/development/shared/components/selectors/CustomDropdown/index.js.map +1 -1
- package/build/development/shared/components/selectors/NativeDropdown/index.js +3 -5
- package/build/development/shared/components/selectors/NativeDropdown/index.js.map +1 -1
- package/build/development/shared/components/selectors/Switch/index.js +21 -20
- package/build/development/shared/components/selectors/Switch/index.js.map +1 -1
- package/build/development/shared/utils/config.js +6 -3
- package/build/development/shared/utils/config.js.map +1 -1
- package/build/development/shared/utils/globalState.js +6 -0
- package/build/development/shared/utils/globalState.js.map +1 -1
- package/build/development/shared/utils/isomorphy/buildInfo.js.map +1 -1
- package/build/development/shared/utils/isomorphy/environment-check.js +6 -1
- package/build/development/shared/utils/isomorphy/environment-check.js.map +1 -1
- package/build/development/shared/utils/isomorphy/index.js +1 -3
- package/build/development/shared/utils/isomorphy/index.js.map +1 -1
- package/build/development/shared/utils/jest/E2eSsrEnv.js +26 -17
- package/build/development/shared/utils/jest/E2eSsrEnv.js.map +1 -1
- package/build/development/shared/utils/jest/global.js +0 -7
- package/build/development/shared/utils/jest/global.js.map +1 -1
- package/build/development/shared/utils/jest/index.js +15 -4
- package/build/development/shared/utils/jest/index.js.map +1 -1
- package/build/development/shared/utils/splitComponent.js +37 -19
- package/build/development/shared/utils/splitComponent.js.map +1 -1
- package/build/development/shared/utils/time.js +3 -3
- package/build/development/shared/utils/time.js.map +1 -1
- package/build/development/shared/utils/webpack.js +11 -11
- package/build/development/shared/utils/webpack.js.map +1 -1
- package/build/development/web.bundle.js +30 -30
- package/build/production/client/getInj.js +4 -2
- package/build/production/client/getInj.js.map +1 -1
- package/build/production/client/index.js +2 -2
- package/build/production/client/index.js.map +1 -1
- package/build/production/client/init.js +4 -2
- package/build/production/client/init.js.map +1 -1
- package/build/production/index.js +2 -1
- package/build/production/index.js.map +1 -1
- package/build/production/server/Cache.js +1 -2
- package/build/production/server/Cache.js.map +1 -1
- package/build/production/server/index.js +9 -5
- package/build/production/server/index.js.map +1 -1
- package/build/production/server/renderer.js +24 -21
- package/build/production/server/renderer.js.map +1 -1
- package/build/production/server/server.js +13 -8
- package/build/production/server/server.js.map +1 -1
- package/build/production/server/utils/errors.js.map +1 -1
- package/build/production/shared/components/Button/index.js +1 -1
- package/build/production/shared/components/Button/index.js.map +1 -1
- package/build/production/shared/components/Checkbox/index.js +1 -1
- package/build/production/shared/components/Checkbox/index.js.map +1 -1
- package/build/production/shared/components/GenericLink/index.js +6 -4
- package/build/production/shared/components/GenericLink/index.js.map +1 -1
- package/build/production/shared/components/Input/index.js +3 -1
- package/build/production/shared/components/Input/index.js.map +1 -1
- package/build/production/shared/components/Link.js +3 -1
- package/build/production/shared/components/Link.js.map +1 -1
- package/build/production/shared/components/MetaTags.js +5 -2
- package/build/production/shared/components/MetaTags.js.map +1 -1
- package/build/production/shared/components/Modal/index.js +6 -2
- package/build/production/shared/components/Modal/index.js.map +1 -1
- package/build/production/shared/components/NavLink.js +4 -1
- package/build/production/shared/components/NavLink.js.map +1 -1
- package/build/production/shared/components/TextArea/index.js +4 -4
- package/build/production/shared/components/TextArea/index.js.map +1 -1
- package/build/production/shared/components/WithTooltip/Tooltip.js +37 -38
- package/build/production/shared/components/WithTooltip/Tooltip.js.map +1 -1
- package/build/production/shared/components/WithTooltip/index.js +4 -4
- package/build/production/shared/components/WithTooltip/index.js.map +1 -1
- package/build/production/shared/components/YouTubeVideo/index.js +3 -2
- package/build/production/shared/components/YouTubeVideo/index.js.map +1 -1
- package/build/production/shared/components/selectors/CustomDropdown/Options/index.js +2 -2
- package/build/production/shared/components/selectors/CustomDropdown/Options/index.js.map +1 -1
- package/build/production/shared/components/selectors/CustomDropdown/index.js +2 -2
- package/build/production/shared/components/selectors/CustomDropdown/index.js.map +1 -1
- package/build/production/shared/components/selectors/NativeDropdown/index.js +2 -2
- package/build/production/shared/components/selectors/NativeDropdown/index.js.map +1 -1
- package/build/production/shared/components/selectors/Switch/index.js +1 -1
- package/build/production/shared/components/selectors/Switch/index.js.map +1 -1
- package/build/production/shared/utils/config.js +6 -4
- package/build/production/shared/utils/config.js.map +1 -1
- package/build/production/shared/utils/globalState.js +4 -1
- package/build/production/shared/utils/globalState.js.map +1 -1
- package/build/production/shared/utils/isomorphy/buildInfo.js.map +1 -1
- package/build/production/shared/utils/isomorphy/environment-check.js +5 -1
- package/build/production/shared/utils/isomorphy/environment-check.js.map +1 -1
- package/build/production/shared/utils/isomorphy/index.js +1 -3
- package/build/production/shared/utils/isomorphy/index.js.map +1 -1
- package/build/production/shared/utils/jest/E2eSsrEnv.js +15 -8
- package/build/production/shared/utils/jest/E2eSsrEnv.js.map +1 -1
- package/build/production/shared/utils/jest/global.js +1 -1
- package/build/production/shared/utils/jest/global.js.map +1 -1
- package/build/production/shared/utils/jest/index.js +13 -5
- package/build/production/shared/utils/jest/index.js.map +1 -1
- package/build/production/shared/utils/splitComponent.js +16 -8
- package/build/production/shared/utils/splitComponent.js.map +1 -1
- package/build/production/shared/utils/time.js +2 -2
- package/build/production/shared/utils/time.js.map +1 -1
- package/build/production/shared/utils/webpack.js +5 -2
- package/build/production/shared/utils/webpack.js.map +1 -1
- package/build/production/web.bundle.js +1 -1
- package/build/production/web.bundle.js.map +1 -1
- package/build/types-code/client/getInj.d.ts +1 -1
- package/build/types-code/client/index.d.ts +2 -2
- package/build/types-code/client/init.d.ts +1 -1
- package/build/types-code/index.d.ts +6 -5
- package/build/types-code/server/Cache.d.ts +1 -2
- package/build/types-code/server/index.d.ts +4 -5
- package/build/types-code/server/renderer.d.ts +8 -10
- package/build/types-code/server/server.d.ts +3 -5
- package/build/types-code/server/utils/errors.d.ts +1 -1
- package/build/types-code/shared/components/Button/index.d.ts +2 -2
- package/build/types-code/shared/components/TextArea/index.d.ts +3 -3
- package/build/types-code/shared/components/WithTooltip/Tooltip.d.ts +7 -1
- package/build/types-code/shared/components/index.d.ts +12 -12
- package/build/types-code/shared/utils/config.d.ts +1 -1
- package/build/types-code/shared/utils/globalState.d.ts +3 -6
- package/build/types-code/shared/utils/isomorphy/index.d.ts +1 -3
- package/build/types-code/shared/utils/jest/E2eSsrEnv.d.ts +1 -1
- package/build/types-code/shared/utils/jest/global.d.ts +4 -4
- package/build/types-code/shared/utils/jest/index.d.ts +2 -2
- package/build/types-code/shared/utils/webpack.d.ts +1 -1
- package/config/babel/node-ssr.d.ts +3 -2
- package/config/babel/node-ssr.js +5 -7
- package/config/babel/webpack.d.ts +3 -11
- package/config/babel/webpack.js +15 -15
- package/config/eslint/default.mjs +32 -0
- package/config/jest/default.js +10 -6
- package/config/jest/resolver.js +2 -0
- package/config/jest/setup.js +2 -2
- package/config/stylelint/default.js +3 -0
- package/config/webpack/app-base.d.ts +0 -6
- package/config/webpack/app-base.js +64 -70
- package/config/webpack/app-development.d.ts +2 -2
- package/config/webpack/app-development.js +8 -12
- package/config/webpack/app-production.js +1 -0
- package/config/webpack/lib-base.js +20 -18
- package/config/webpack/lib-development.js +1 -0
- package/config/webpack/lib-production.js +1 -0
- package/config/workbox/default.js +2 -5
- package/dev-styles.js +1 -0
- package/eslint.config.mjs +13 -0
- package/node-entry.js +7 -2
- package/null.js +1 -0
- package/package.json +17 -25
- package/prod-styles.js +1 -0
- package/src/client/getInj.ts +8 -3
- package/src/client/index.tsx +4 -4
- package/src/client/init.ts +18 -15
- package/src/index.ts +8 -3
- package/src/server/Cache.ts +7 -15
- package/src/server/index.ts +30 -21
- package/src/server/renderer.tsx +76 -66
- package/src/server/server.ts +38 -20
- package/src/server/utils/errors.ts +6 -3
- package/src/shared/components/Button/index.tsx +4 -5
- package/src/shared/components/Checkbox/index.tsx +10 -7
- package/src/shared/components/GenericLink/index.tsx +14 -7
- package/src/shared/components/Input/index.tsx +4 -1
- package/src/shared/components/Link.tsx +9 -5
- package/src/shared/components/MetaTags.tsx +21 -15
- package/src/shared/components/Modal/index.tsx +12 -11
- package/src/shared/components/NavLink.tsx +10 -5
- package/src/shared/components/TextArea/index.tsx +17 -9
- package/src/shared/components/WithTooltip/{Tooltip.tsx → Tooltip.ts} +35 -39
- package/src/shared/components/WithTooltip/index.tsx +36 -30
- package/src/shared/components/YouTubeVideo/index.tsx +10 -6
- package/src/shared/components/selectors/CustomDropdown/Options/index.tsx +5 -6
- package/src/shared/components/selectors/CustomDropdown/index.tsx +10 -14
- package/src/shared/components/selectors/NativeDropdown/index.tsx +5 -7
- package/src/shared/components/selectors/Switch/index.tsx +19 -17
- package/src/shared/utils/config.ts +12 -5
- package/src/shared/utils/globalState.ts +8 -5
- package/src/shared/utils/isomorphy/buildInfo.ts +1 -1
- package/src/shared/utils/isomorphy/environment-check.ts +5 -1
- package/src/shared/utils/isomorphy/index.ts +4 -6
- package/src/shared/utils/jest/E2eSsrEnv.ts +64 -39
- package/src/shared/utils/jest/global.ts +6 -8
- package/src/shared/utils/jest/{index.tsx → index.ts} +25 -12
- package/src/shared/utils/splitComponent.tsx +44 -25
- package/src/shared/utils/time.ts +16 -9
- package/src/shared/utils/webpack.ts +19 -14
- package/webpack.config.ts +36 -10
- package/config/eslint/default.json +0 -30
- package/config/eslint/jest.json +0 -19
- package/config/eslint/typescript.js +0 -46
package/src/client/init.ts
CHANGED
|
@@ -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',
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
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
|
|
5
|
+
import type * as ClientM from './client';
|
|
6
|
+
import type * as ServerFactoryM from './server';
|
|
6
7
|
|
|
7
|
-
const server = webpack.requireWeak('./server', __dirname)
|
|
8
|
+
const server = webpack.requireWeak<typeof ServerFactoryM>('./server', __dirname);
|
|
8
9
|
|
|
9
|
-
const client = server
|
|
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,
|
package/src/server/Cache.ts
CHANGED
|
@@ -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 (
|
|
59
|
-
const
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
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
|
}
|
package/src/server/index.ts
CHANGED
|
@@ -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
|
-
|
|
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 {
|
|
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
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
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
|
|
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(
|
|
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 =
|
|
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
|
-
|
|
190
|
-
ops.
|
|
191
|
-
|
|
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
|
|
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
|
|
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
|
}
|
package/src/server/renderer.tsx
CHANGED
|
@@ -6,7 +6,7 @@ import fs from 'fs';
|
|
|
6
6
|
import path from 'path';
|
|
7
7
|
|
|
8
8
|
import type { Request, RequestHandler } from 'express';
|
|
9
|
-
import {
|
|
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 {
|
|
36
|
+
import type { ChunkGroupsT, SsrContextT } from 'utils/globalState';
|
|
37
37
|
|
|
38
|
-
import {
|
|
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
|
-
|
|
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
|
-
|
|
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)
|
|
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
|
|
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
|
|
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 (
|
|
162
|
-
const
|
|
163
|
-
if (
|
|
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
|
-
|
|
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 (
|
|
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
|
|
200
|
-
if (res[script.location]
|
|
201
|
-
|
|
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?:
|
|
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
|
-
|
|
323
|
-
ops.
|
|
324
|
-
|
|
325
|
-
});
|
|
326
|
-
}
|
|
324
|
+
ops.logger ??= newDefaultLogger({
|
|
325
|
+
defaultLogLevel: ops.defaultLoggerLogLevel,
|
|
326
|
+
});
|
|
327
327
|
|
|
328
|
-
const buildInfo = ops.buildInfo
|
|
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
|
-
|
|
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
|
|
403
|
-
prepareCipher(buildInfo.key)
|
|
409
|
+
ops.beforeRender!(req, sanitizedConfig as unknown as ConfigT),
|
|
410
|
+
prepareCipher(buildInfo.key),
|
|
404
411
|
]);
|
|
405
412
|
|
|
406
|
-
let helmet:
|
|
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
|
|
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) => {
|
|
462
|
+
{ onError: (error) => {
|
|
463
|
+
throw error;
|
|
464
|
+
} },
|
|
455
465
|
);
|
|
456
|
-
({ helmet } = helmetContext
|
|
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();
|
|
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
|
|
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
|
-
|
|
545
|
-
|
|
546
|
-
|
|
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
|
|
595
|
-
|
|
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
|
});
|