` - a controller for Suspense Hydration. **Compulsory** for async/lazy usecases
+
##### Misc
There is also API method, unique for imported-component, which could be useful on the client side
@@ -442,12 +446,17 @@ Before rendering your application you have to ensure - all parts are loaded.
`rehydrateMarks` will load everything you need, and provide a promise to await.
```js
-import { rehydrateMarks } from 'react-imported-component';
+import { rehydrateMarks, ImportedController } from 'react-imported-component';
// this will trigger all marked imports, and await for competition.
rehydrateMarks().then(() => {
- // better
- ReactDOM.hydrate(, document.getElementById('main'));
+ // better (note ImportedController usage)
+ ReactDOM.hydrate(
+
+
+ ,
+ document.getElementById('main')
+ );
// or
ReactDOM.render(, document.getElementById('main'));
});
diff --git a/package.json b/package.json
index 649d670e..2ac815fb 100644
--- a/package.json
+++ b/package.json
@@ -5,8 +5,8 @@
"main": "dist/es5/entrypoints/index.js",
"jsnext:main": "dist/es2015/entrypoints/index.js",
"module": "dist/es2015/entrypoints/index.js",
- "sideEffects": false,
"types": "dist/es5/entrypoints/index.d.ts",
+ "sideEffects": false,
"scripts": {
"build:ci": "lib-builder build && yarn size",
"build": "rm -Rf ./dist/* && lib-builder build && yarn size && yarn size:report",
@@ -79,7 +79,7 @@
"crc-32": "^1.2.0",
"detect-node-es": "^1.0.0",
"scan-directory": "^2.0.0",
- "tslib": "^1.10.0"
+ "tslib": "^2.0.0"
},
"engines": {
"node": ">=8.5.0"
diff --git a/src/entrypoints/index.ts b/src/entrypoints/index.ts
index fd3f914c..7c397a5f 100644
--- a/src/entrypoints/index.ts
+++ b/src/entrypoints/index.ts
@@ -12,10 +12,12 @@ import { ImportedComponent } from '../ui/Component';
import { ImportedComponent as ComponentLoader } from '../ui/Component';
import { ImportedStream } from '../ui/context';
import imported, { lazy } from '../ui/HOC';
-import LazyBoundary from '../ui/LazyBoundary';
+import { ImportedController } from '../ui/ImportedController';
+import { LazyBoundary } from '../ui/LazyBoundary';
import { ImportedModule, importedModule } from '../ui/Module';
import { useImported, useLazy, useLoadable } from '../ui/useImported';
import { remapImports } from '../utils/helpers';
+import { useIsClientPhase } from '../utils/useClientPhase';
export {
printDrainHydrateMarks,
@@ -36,6 +38,8 @@ export {
importedModule,
lazy,
LazyBoundary,
+ ImportedController,
+ useIsClientPhase,
remapImports,
useLoadable,
useImported,
diff --git a/src/entrypoints/server.ts b/src/entrypoints/server.ts
index ebc463dc..08cbdcd9 100644
--- a/src/entrypoints/server.ts
+++ b/src/entrypoints/server.ts
@@ -4,6 +4,7 @@ import { drainHydrateMarks, printDrainHydrateMarks } from '../loadable/marks';
import { createLoadableStream } from '../loadable/stream';
import { getLoadableTrackerCallback } from '../trackers/globalTracker';
import { createLoadableTransformer } from '../transformers/loadableTransformer';
+import { Stream as ImportedStreamTracker } from '../types';
import { ImportedStream } from '../ui/context';
export {
@@ -16,4 +17,5 @@ export {
getLoadableTrackerCallback,
getMarkedChunks,
getMarkedFileNames,
+ ImportedStreamTracker,
};
diff --git a/src/loadable/assignImportedComponents.ts b/src/loadable/assignImportedComponents.ts
index df05427c..02c5ee5b 100644
--- a/src/loadable/assignImportedComponents.ts
+++ b/src/loadable/assignImportedComponents.ts
@@ -17,11 +17,16 @@ export const assignImportedComponents = (set: ImportedDefinition[]) => {
assignMetaData(loadable.mark, loadable, imported[1], imported[2]);
});
- if (countBefore === LOADABLE_SIGNATURE.size) {
+ if (set.length === 0) {
// tslint:disable-next-line:no-console
console.error('react-imported-component: no import-marks found, please check babel plugin');
}
+ if (countBefore === LOADABLE_SIGNATURE.size) {
+ // tslint:disable-next-line:no-console
+ console.error('react-imported-component: no new imports found');
+ }
+
done();
return set;
diff --git a/src/loadable/stream.ts b/src/loadable/stream.ts
index 4a461fb5..6a16d474 100644
--- a/src/loadable/stream.ts
+++ b/src/loadable/stream.ts
@@ -1,6 +1,6 @@
import { Stream } from '../types';
-export const createLoadableStream = () => ({ marks: {} });
+export const createLoadableStream = (): Stream => ({ marks: {} });
export const clearStream = (stream?: Stream) => {
if (stream) {
stream.marks = {};
diff --git a/src/ui/HOC.tsx b/src/ui/HOC.tsx
index 23178ff0..8449f0cb 100644
--- a/src/ui/HOC.tsx
+++ b/src/ui/HOC.tsx
@@ -25,10 +25,12 @@ function loader(
loaderFunction: DefaultComponentImport
,
baseOptions: Partial> & HOCOptions = {}
): HOCType {
- const loadable = getLoadable(loaderFunction);
+ let loadable = getLoadable(loaderFunction);
const Imported = React.forwardRef(function ImportedComponentHOC({ importedProps = {}, ...props }, ref) {
const options = { ...baseOptions, ...importedProps };
+ // re-get loadable in order to have fresh reference
+ loadable = getLoadable(loaderFunction);
return (
(
return loadable.resolution;
};
- Imported.done = loadable.resolution;
+ Object.defineProperty(Imported, 'done', {
+ get() {
+ return loadable.resolution;
+ },
+ });
return Imported;
}
diff --git a/src/ui/ImportedController.tsx b/src/ui/ImportedController.tsx
new file mode 100644
index 00000000..d26d87de
--- /dev/null
+++ b/src/ui/ImportedController.tsx
@@ -0,0 +1,46 @@
+import React, { createContext, useCallback, useLayoutEffect, useState } from 'react';
+
+interface ImportedState {
+ usesHydration: boolean;
+ pastHydration: boolean;
+}
+
+export const importedState = createContext(undefined);
+
+export const HydrationState: React.FC<{ state: ImportedState }> = ({ state, children }) => (
+ {children}
+);
+
+/**
+ * this component just creates a "the first-most" effect in the system
+ */
+const HydrationEffect = ({ loopCallback }: { loopCallback(): void }): null => {
+ useLayoutEffect(loopCallback, []);
+ return null;
+};
+
+/**
+ * @see [LazyBoundary]{@link LazyBoundary} - HydrationController is required for LazyBoundary to properly work with React>16.10
+ * Established a control over LazyBoundary suppressing fallback during the initial hydration
+ * @param props
+ * @param [props.usesHydration=true] determines of Application is rendered using hydrate
+ */
+export const ImportedController: React.FC<{
+ /**
+ * determines of Application is rendered using hydrate
+ */
+ usesHydration?: boolean;
+}> = ({ children, usesHydration = true }) => {
+ const [state, setState] = useState({
+ usesHydration,
+ pastHydration: false,
+ });
+
+ const onFirstHydration = useCallback(() => setState(oldState => ({ ...oldState, pastHydration: true })), []);
+ return (
+ <>
+
+ {children}
+ >
+ );
+};
diff --git a/src/ui/LazyBoundary.tsx b/src/ui/LazyBoundary.tsx
index 26fa8c71..2c672ae7 100644
--- a/src/ui/LazyBoundary.tsx
+++ b/src/ui/LazyBoundary.tsx
@@ -1,13 +1,26 @@
import * as React from 'react';
import { isBackend } from '../utils/detectBackend';
+import { useIsClientPhase } from '../utils/useClientPhase';
-const LazyBoundary: React.FC<{
+const LazyServerBoundary: React.FC<{
fallback: NonNullable | null;
}> = ({ children }) => {children};
+const LazyClientBoundary: React.FC<{
+ fallback: NonNullable | null;
+}> = ({ children, fallback }) => (
+
+ {children}
+
+);
+
/**
- * React.Suspense "as-is" replacement
+ * React.Suspense "as-is" replacement. Automatically "removed" during SSR and "patched" to work accordingly on the clientside
+ *
+ * @see {@link HydrationController} has to wrap entire application in order to provide required information
*/
-const Boundary = isBackend ? LazyBoundary : React.Suspense;
-
-export default Boundary;
+export const LazyBoundary = isBackend ? LazyServerBoundary : LazyClientBoundary;
diff --git a/src/ui/context.tsx b/src/ui/context.tsx
index 3e4b9405..82a21dc7 100644
--- a/src/ui/context.tsx
+++ b/src/ui/context.tsx
@@ -2,16 +2,14 @@ import * as React from 'react';
import { defaultStream } from '../loadable/stream';
import { Stream } from '../types';
-interface TakeProps {
- stream: Stream;
-}
-
export const streamContext = React.createContext(defaultStream);
/**
* SSR. Tracker for used marks
*/
-export const ImportedStream: React.FC = ({ stream, children, ...props }) => {
+export const ImportedStream: React.FC<{
+ stream: Stream;
+}> = ({ stream, children, ...props }) => {
if (process.env.NODE_ENV !== 'development') {
if ('takeUID' in props) {
throw new Error('react-imported-component: `takeUID` was replaced by `stream`.');
diff --git a/src/utils/useClientPhase.tsx b/src/utils/useClientPhase.tsx
new file mode 100644
index 00000000..2daef073
--- /dev/null
+++ b/src/utils/useClientPhase.tsx
@@ -0,0 +1,18 @@
+import { useContext } from 'react';
+import { importedState } from '../ui/ImportedController';
+
+/**
+ * returns "true" if currently is a "client" phase and all features should be active
+ * @see {@link HydrationController}
+ */
+export const useIsClientPhase = (): boolean => {
+ const value = useContext(importedState);
+ if (!value) {
+ if (process.env.NODE_ENV !== 'production') {
+ // tslint:disable-next-line:no-console
+ console.warn('react-imported-component: please wrap your entire application with ImportedController');
+ }
+ return true;
+ }
+ return value.pastHydration;
+};
diff --git a/yarn.lock b/yarn.lock
index d9da3282..4c0df7b5 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -15976,7 +15976,7 @@ ts-pnp@^1.1.6:
resolved "https://registry.yarnpkg.com/ts-pnp/-/ts-pnp-1.2.0.tgz#a500ad084b0798f1c3071af391e65912c86bca92"
integrity sha512-csd+vJOb/gkzvcCHgTGSChYpy5f1/XKNsmvBGO4JXS+z1v2HobugDz4s1IeFXM3wZB44uczs+eazB5Q/ccdhQw==
-tslib@^1.10.0, tslib@^1.6.0, tslib@^1.8.0, tslib@^1.8.1, tslib@^1.9.3:
+tslib@^1.6.0, tslib@^1.8.0, tslib@^1.8.1, tslib@^1.9.3:
version "1.10.0"
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.10.0.tgz#c3c19f95973fb0a62973fb09d90d961ee43e5c8a"
integrity sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ==
@@ -15986,6 +15986,11 @@ tslib@^1.9.0:
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.9.3.tgz#d7e4dd79245d85428c4d7e4822a79917954ca286"
integrity sha512-4krF8scpejhaOgqzBEcGM7yDIEfi0/8+8zDRZhNZZ2kjmHJ4hv3zCbQWxoJGz1iw5U0Jl0nma13xzHXcncMavQ==
+tslib@^2.0.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.1.0.tgz#da60860f1c2ecaa5703ab7d39bc05b6bf988b97a"
+ integrity sha512-hcVC3wYEziELGGmEEXue7D75zbwIIVUMWAVbHItGPx0ziyXxrOMQx4rQEVEV45Ut/1IotuEvwqPopzIOkDMf0A==
+
tslint-config-prettier@^1.18.0:
version "1.18.0"
resolved "https://registry.yarnpkg.com/tslint-config-prettier/-/tslint-config-prettier-1.18.0.tgz#75f140bde947d35d8f0d238e0ebf809d64592c37"