Skip to content

Commit d67a211

Browse files
committed
feat: add ImportedController
1 parent 03165dc commit d67a211

File tree

10 files changed

+101
-14
lines changed

10 files changed

+101
-14
lines changed

.size-limit.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,12 @@ module.exports = [
22
{
33
path: ['dist/es2015/entrypoints/index.js', 'dist/es2015/entrypoints/boot.js'],
44
ignore: ['tslib'],
5-
limit: '3.9 KB',
5+
limit: '4.1 KB',
66
},
77
{
88
path: 'dist/es2015/entrypoints/index.js',
99
ignore: ['tslib'],
10-
limit: '3.6 KB',
10+
limit: '3.8 KB',
1111
},
1212
{
1313
path: 'dist/es2015/entrypoints/boot.js',

.size.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,12 @@
22
{
33
"name": "dist/es2015/entrypoints/index.js, dist/es2015/entrypoints/boot.js",
44
"passed": true,
5-
"size": 3975
5+
"size": 4187
66
},
77
{
88
"name": "dist/es2015/entrypoints/index.js",
99
"passed": true,
10-
"size": 3606
10+
"size": 3817
1111
},
1212
{
1313
"name": "dist/es2015/entrypoints/boot.js",

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@
55
"main": "dist/es5/entrypoints/index.js",
66
"jsnext:main": "dist/es2015/entrypoints/index.js",
77
"module": "dist/es2015/entrypoints/index.js",
8-
"sideEffects": false,
98
"types": "dist/es5/entrypoints/index.d.ts",
9+
"sideEffects": false,
1010
"scripts": {
1111
"build:ci": "lib-builder build && yarn size",
1212
"build": "rm -Rf ./dist/* && lib-builder build && yarn size && yarn size:report",
@@ -79,7 +79,7 @@
7979
"crc-32": "^1.2.0",
8080
"detect-node-es": "^1.0.0",
8181
"scan-directory": "^2.0.0",
82-
"tslib": "^1.10.0"
82+
"tslib": "^2.0.0"
8383
},
8484
"engines": {
8585
"node": ">=8.5.0"

src/entrypoints/index.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,12 @@ import { ImportedComponent } from '../ui/Component';
1212
import { ImportedComponent as ComponentLoader } from '../ui/Component';
1313
import { ImportedStream } from '../ui/context';
1414
import imported, { lazy } from '../ui/HOC';
15-
import LazyBoundary from '../ui/LazyBoundary';
15+
import { ImportedController } from '../ui/ImportedController';
16+
import { LazyBoundary } from '../ui/LazyBoundary';
1617
import { ImportedModule, importedModule } from '../ui/Module';
1718
import { useImported, useLazy, useLoadable } from '../ui/useImported';
1819
import { remapImports } from '../utils/helpers';
20+
import { useIsClientPhase } from '../utils/useClientPhase';
1921

2022
export {
2123
printDrainHydrateMarks,
@@ -36,6 +38,8 @@ export {
3638
importedModule,
3739
lazy,
3840
LazyBoundary,
41+
ImportedController,
42+
useIsClientPhase,
3943
remapImports,
4044
useLoadable,
4145
useImported,

src/entrypoints/server.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { drainHydrateMarks, printDrainHydrateMarks } from '../loadable/marks';
44
import { createLoadableStream } from '../loadable/stream';
55
import { getLoadableTrackerCallback } from '../trackers/globalTracker';
66
import { createLoadableTransformer } from '../transformers/loadableTransformer';
7+
import { Stream } from '../types';
78
import { ImportedStream } from '../ui/context';
89

910
export {
@@ -16,4 +17,5 @@ export {
1617
getLoadableTrackerCallback,
1718
getMarkedChunks,
1819
getMarkedFileNames,
20+
Stream,
1921
};

src/loadable/assignImportedComponents.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,16 @@ export const assignImportedComponents = (set: ImportedDefinition[]) => {
1717
assignMetaData(loadable.mark, loadable, imported[1], imported[2]);
1818
});
1919

20-
if (countBefore === LOADABLE_SIGNATURE.size) {
20+
if (set.length === 0) {
2121
// tslint:disable-next-line:no-console
2222
console.error('react-imported-component: no import-marks found, please check babel plugin');
2323
}
2424

25+
if (countBefore === LOADABLE_SIGNATURE.size) {
26+
// tslint:disable-next-line:no-console
27+
console.error('react-imported-component: no new imports found');
28+
}
29+
2530
done();
2631

2732
return set;

src/loadable/stream.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { Stream } from '../types';
22

3-
export const createLoadableStream = () => ({ marks: {} });
3+
export const createLoadableStream = (): Stream => ({ marks: {} });
44
export const clearStream = (stream?: Stream) => {
55
if (stream) {
66
stream.marks = {};

src/ui/ImportedController.tsx

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import React, { createContext, useCallback, useLayoutEffect, useState } from 'react';
2+
3+
interface ImportedState {
4+
usesHydration: boolean;
5+
pastHydration: boolean;
6+
}
7+
8+
export const importedState = createContext<ImportedState | undefined>(undefined);
9+
10+
export const HydrationState: React.FC<{ state: ImportedState }> = ({ state, children }) => (
11+
<importedState.Provider value={state}>{children}</importedState.Provider>
12+
);
13+
14+
/**
15+
* this component just creates a "the first-most" effect in the system
16+
*/
17+
const HydrationEffect = ({ loopCallback }: { loopCallback(): void }): null => {
18+
useLayoutEffect(loopCallback, []);
19+
return null;
20+
};
21+
22+
/**
23+
* @see [LazyBoundary]{@link LazyBoundary} - HydrationController is required for LazyBoundary to properly work with React>16.10
24+
* Established a control over LazyBoundary suppressing fallback during the initial hydration
25+
* @param props
26+
* @param [props.usesHydration=true] determines of Application is rendered using hydrate
27+
*/
28+
export const ImportedController: React.FC<{
29+
/**
30+
* determines of Application is rendered using hydrate
31+
*/
32+
usesHydration?: boolean;
33+
}> = ({ children, usesHydration = true }) => {
34+
const [state, setState] = useState<ImportedState>({
35+
usesHydration,
36+
pastHydration: false,
37+
});
38+
39+
const onFirstHydration = useCallback(() => setState(oldState => ({ ...oldState, pastHydration: true })), []);
40+
return (
41+
<>
42+
<HydrationEffect loopCallback={onFirstHydration} />
43+
<HydrationState state={state}>{children}</HydrationState>
44+
</>
45+
);
46+
};

src/ui/LazyBoundary.tsx

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,25 @@
11
import * as React from 'react';
22
import { isBackend } from '../utils/detectBackend';
3+
import { useIsClientPhase } from '../utils/useClientPhase';
34

4-
const LazyBoundary: React.FC<{
5+
const LazyServerBoundary: React.FC<{
56
fallback: NonNullable<React.ReactNode> | null;
67
}> = ({ children }) => <React.Fragment>{children}</React.Fragment>;
78

9+
const LazyClientBoundary: React.FC<{
10+
fallback: NonNullable<React.ReactNode> | null;
11+
}> = ({ children, fallback }) => (
12+
<React.Suspense
13+
// we keep fallback null during hydration as it is expected behavior for "ssr-ed" Suspense blocks - they should not "fallback"
14+
fallback={useIsClientPhase() ? fallback : null}
15+
>
16+
{children}
17+
</React.Suspense>
18+
);
19+
820
/**
9-
* React.Suspense "as-is" replacement
21+
* React.Suspense "as-is" replacement. Automatically "removed" during SSR and "patched" to work accordingly on the clientside
22+
*
23+
* @see {@link HydrationController} has to wrap entire application in order to provide required information
1024
*/
11-
const Boundary = isBackend ? LazyBoundary : React.Suspense;
12-
13-
export default Boundary;
25+
export const LazyBoundary = isBackend ? LazyServerBoundary : LazyClientBoundary;

src/utils/useClientPhase.tsx

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import { useContext } from 'react';
2+
import { importedState } from '../ui/ImportedController';
3+
4+
/**
5+
* returns "true" if currently is a "client" phase and all features should be active
6+
* @see {@link HydrationController}
7+
*/
8+
export const useIsClientPhase = (): boolean => {
9+
const value = useContext(importedState);
10+
if (!value) {
11+
if (process.env.NODE_ENV !== 'production') {
12+
// tslint:disable-next-line:no-console
13+
console.warn('react-imported-component: please wrap your entire application with ImportedController');
14+
}
15+
return true;
16+
}
17+
return value.pastHydration;
18+
};

0 commit comments

Comments
 (0)