diff --git a/examples/playground/components/after-paint/with-ssr.tsx b/examples/playground/components/after-paint/with-ssr.tsx
index fb0799b..ce4655b 100644
--- a/examples/playground/components/after-paint/with-ssr.tsx
+++ b/examples/playground/components/after-paint/with-ssr.tsx
@@ -1,15 +1,12 @@
import React from 'react';
-import { controlFetch, isServer } from '../../utils';
+import { createSuspendableData } from '../../utils';
import { Result } from '../common/result';
-let hasThrown = false;
+const useSuspendableData = createSuspendableData();
const ComponentWithSSR = () => {
- if (!hasThrown && !isServer()) {
- hasThrown = true;
- throw controlFetch(true);
- }
+ useSuspendableData();
return ;
};
diff --git a/examples/playground/components/after-paint/without-ssr.tsx b/examples/playground/components/after-paint/without-ssr.tsx
index d7a8390..88f492a 100644
--- a/examples/playground/components/after-paint/without-ssr.tsx
+++ b/examples/playground/components/after-paint/without-ssr.tsx
@@ -1,15 +1,12 @@
import React from 'react';
-import { controlFetch } from '../../utils';
+import { createSuspendableData } from '../../utils';
import { Result } from '../common/result';
-let hasThrown = false;
+const useSuspendableData = createSuspendableData();
const ComponentWithoutSSR = () => {
- if (!hasThrown) {
- hasThrown = true;
- throw controlFetch(true);
- }
+ useSuspendableData();
return ;
};
diff --git a/examples/playground/components/common/result.tsx b/examples/playground/components/common/result.tsx
index e27df7f..1bf368a 100644
--- a/examples/playground/components/common/result.tsx
+++ b/examples/playground/components/common/result.tsx
@@ -1,4 +1,4 @@
-import React from 'react';
+import React, { useEffect, useState } from 'react';
import { isServer } from '../../utils';
@@ -8,6 +8,11 @@ type Props = {
};
export const Result = ({ isFallback, hasSsr }: Props) => {
+ const [isDone, setDone] = useState(isFallback);
+ useEffect(() => {
+ setDone(true);
+ }, []);
+
return (
{
{hasSsr ? 'With ssr' : 'Without ssr'}
-
- reactive: {isServer() ? '🅾️' : isFallback ? '☑️' : '✅'}
+ reactive:{' '}
+ {isServer() || !isDone ? '🅾️' : isFallback || !isDone ? '☑️' : '✅'}
-
- content: {isFallback ? '🅾️' : isServer() ? '☑️' : '✅'}
+ content:{' '}
+ {isFallback || !isDone ? '🅾️' : isServer() || !isDone ? '☑️' : '✅'}
diff --git a/examples/playground/components/common/section.tsx b/examples/playground/components/common/section.tsx
index 3970579..389ad59 100644
--- a/examples/playground/components/common/section.tsx
+++ b/examples/playground/components/common/section.tsx
@@ -3,6 +3,7 @@ import { LazySuspense } from 'react-loosely-lazy';
import { Result } from './result';
import { Progress } from './progress';
+import { isServer } from '../../utils';
type Props = {
components: {
@@ -36,7 +37,8 @@ export const Section = memo(
)}
{WithoutSSR != null && (
}>
-
+ {/* given use renderToString, we fake SSR fallback render */}
+ {isServer() ? : }
)}
diff --git a/examples/playground/components/custom-wait/with-ssr.tsx b/examples/playground/components/custom-wait/with-ssr.tsx
index fb0799b..ce4655b 100644
--- a/examples/playground/components/custom-wait/with-ssr.tsx
+++ b/examples/playground/components/custom-wait/with-ssr.tsx
@@ -1,15 +1,12 @@
import React from 'react';
-import { controlFetch, isServer } from '../../utils';
+import { createSuspendableData } from '../../utils';
import { Result } from '../common/result';
-let hasThrown = false;
+const useSuspendableData = createSuspendableData();
const ComponentWithSSR = () => {
- if (!hasThrown && !isServer()) {
- hasThrown = true;
- throw controlFetch(true);
- }
+ useSuspendableData();
return ;
};
diff --git a/examples/playground/components/custom-wait/without-ssr.tsx b/examples/playground/components/custom-wait/without-ssr.tsx
index d7a8390..88f492a 100644
--- a/examples/playground/components/custom-wait/without-ssr.tsx
+++ b/examples/playground/components/custom-wait/without-ssr.tsx
@@ -1,15 +1,12 @@
import React from 'react';
-import { controlFetch } from '../../utils';
+import { createSuspendableData } from '../../utils';
import { Result } from '../common/result';
-let hasThrown = false;
+const useSuspendableData = createSuspendableData();
const ComponentWithoutSSR = () => {
- if (!hasThrown) {
- hasThrown = true;
- throw controlFetch(true);
- }
+ useSuspendableData();
return ;
};
diff --git a/examples/playground/components/for-paint/with-ssr.tsx b/examples/playground/components/for-paint/with-ssr.tsx
index fb0799b..ce4655b 100644
--- a/examples/playground/components/for-paint/with-ssr.tsx
+++ b/examples/playground/components/for-paint/with-ssr.tsx
@@ -1,15 +1,12 @@
import React from 'react';
-import { controlFetch, isServer } from '../../utils';
+import { createSuspendableData } from '../../utils';
import { Result } from '../common/result';
-let hasThrown = false;
+const useSuspendableData = createSuspendableData();
const ComponentWithSSR = () => {
- if (!hasThrown && !isServer()) {
- hasThrown = true;
- throw controlFetch(true);
- }
+ useSuspendableData();
return ;
};
diff --git a/examples/playground/components/for-paint/without-ssr.tsx b/examples/playground/components/for-paint/without-ssr.tsx
index 4961370..ec08858 100644
--- a/examples/playground/components/for-paint/without-ssr.tsx
+++ b/examples/playground/components/for-paint/without-ssr.tsx
@@ -1,15 +1,12 @@
import React from 'react';
-import { controlFetch } from '../../utils';
+import { createSuspendableData } from '../../utils';
import { Result } from '../common/result';
-let hasThrown = false;
+const useSuspendableData = createSuspendableData();
const ComponentNoSSR = () => {
- if (!hasThrown) {
- hasThrown = true;
- throw controlFetch(true);
- }
+ useSuspendableData();
return ;
};
diff --git a/examples/playground/components/lazy/without-ssr.tsx b/examples/playground/components/lazy/without-ssr.tsx
index 2ee3127..7f971f7 100644
--- a/examples/playground/components/lazy/without-ssr.tsx
+++ b/examples/playground/components/lazy/without-ssr.tsx
@@ -1,14 +1,11 @@
import React from 'react';
-import { controlFetch } from '../../utils';
+import { createSuspendableData } from '../../utils';
import { Result } from '../common/result';
-let hasThrown = false;
+const useSuspendableData = createSuspendableData();
const ComponentWithSSR = () => {
- if (!hasThrown) {
- hasThrown = true;
- throw controlFetch(true);
- }
+ useSuspendableData();
return ;
};
diff --git a/examples/playground/constants.tsx b/examples/playground/constants.tsx
index f865a03..9b3c230 100644
--- a/examples/playground/constants.tsx
+++ b/examples/playground/constants.tsx
@@ -7,7 +7,7 @@ export const steps = [
'PAINT READY',
'AFTER LOADING',
'AFTER FETCHING',
- 'AFTER READY', // also 'LAZY READY'
+ 'AFTER READY',
'CUSTOM LOADING',
'CUSTOM FETCHING',
'CUSTOM READY',
diff --git a/examples/playground/index.tsx b/examples/playground/index.tsx
index 43a08d6..8591384 100644
--- a/examples/playground/index.tsx
+++ b/examples/playground/index.tsx
@@ -1,11 +1,17 @@
-import React, { useState, useEffect, FunctionComponent } from 'react';
-import { hydrate, render } from 'react-dom';
+import React, {
+ useState,
+ useEffect,
+ FunctionComponent,
+ StrictMode,
+} from 'react';
+import { createRoot, hydrateRoot } from 'react-dom/client';
import { renderToString } from 'react-dom/server';
import { useLazyPhase, MODE } from 'react-loosely-lazy';
-import { listeners, steps, pastSteps } from './constants';
+import { listeners, steps, pastSteps } from './constants';
import { buildServerComponents, buildClientComponents } from './components';
+import { isFailSsr, isRender } from './utils';
/**
* Controls App
@@ -84,7 +90,7 @@ const App = ({ initialStep, components }: AppProps) => {
}, [step, startNextPhase]);
return (
- <>
+
@@ -92,29 +98,34 @@ const App = ({ initialStep, components }: AppProps) => {
- >
+
);
};
const renderApp = (v: string) => {
const appContainer = document.querySelector('#app');
- const isFailSsr = window.location.search.includes('failssr');
- const isRender = isFailSsr || window.location.search.includes('render');
- const mode = isRender ? MODE.RENDER : MODE.HYDRATE;
+ const mode = isRender() ? MODE.RENDER : MODE.HYDRATE;
- if (v === 'SSR' && appContainer && !isFailSsr) {
+ if (v === 'SSR' && appContainer && !isFailSsr()) {
const components = buildServerComponents(mode);
const ssr = renderToString();
- appContainer.innerHTML = isRender ? `${ssr}
` : ssr;
+ appContainer.innerHTML = isRender() ? `${ssr}
` : ssr;
}
if (v === 'PAINT LOADING') {
const components = buildClientComponents(mode);
- const renderer = isRender ? render : hydrate;
-
- renderer(, appContainer);
+ if (isRender()) {
+ createRoot(appContainer!).render(
+
+ );
+ } else {
+ hydrateRoot(
+ appContainer!,
+
+ );
+ }
}
};
listeners.add(renderApp);
-render(, document.querySelector('#controls'));
+createRoot(document.querySelector('#controls')!).render();
diff --git a/examples/playground/utils.ts b/examples/playground/utils.ts
index cf42a75..778a08e 100644
--- a/examples/playground/utils.ts
+++ b/examples/playground/utils.ts
@@ -1,6 +1,9 @@
import { listeners } from './constants';
export const isServer = () => window.name === 'nodejs';
+export const isFailSsr = () => window.location.search.includes('failssr');
+export const isRender = () =>
+ isFailSsr() || window.location.search.includes('render');
export const controlLoad = (result: T): Promise => {
let resolve: (v: T) => void;
@@ -14,7 +17,7 @@ export const controlLoad = (result: T): Promise => {
return deferred;
};
-export const controlFetch = (result: T): Promise => {
+const controlFetch = (result: T): Promise => {
let resolve: (v: T) => void;
const deferred = new Promise(r => {
resolve = r;
@@ -25,3 +28,20 @@ export const controlFetch = (result: T): Promise => {
return deferred;
};
+
+export const createSuspendableData = () => {
+ let promise: Promise | null = null;
+ let resolved = false;
+
+ return function useSuspendableData() {
+ if (promise == null && !isServer()) {
+ promise = controlFetch(true);
+ }
+
+ if (promise && !resolved) {
+ throw promise.then(() => {
+ resolved = true;
+ });
+ }
+ };
+};
diff --git a/package-lock.json b/package-lock.json
index 3632b4a..b39a02f 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -24,8 +24,8 @@
"@babel/runtime": "^7.14.5",
"@testing-library/jest-dom": "^5.14.1",
"@types/jest": "^26.0.23",
- "@types/react": "^16.14.8",
- "@types/react-dom": "^16.9.13",
+ "@types/react": "^17.0.0",
+ "@types/react-dom": "^17.0.0",
"@types/testing-library__jest-dom": "^5.14.0",
"@types/webpack": "^4.41.29",
"@typescript-eslint/eslint-plugin": "^4.27.0",
@@ -47,8 +47,8 @@
"flow-bin": "^0.151.0",
"jest": "^26.6.3",
"prettier": "^2.3.1",
- "react": "^16.14.0",
- "react-dom": "^16.14.0",
+ "react": "^18.0.0-0",
+ "react-dom": "^18.0.0-0",
"semver": "^7.3.5",
"tsconfig-paths-webpack-plugin": "^3.5.1",
"typescript": "^4.3.3",
@@ -3974,24 +3974,6 @@
"integrity": "sha512-hsPGC/U/0xe/WghMeSgyFsq9nNPfA5oY1Il2AaeAJcu/vTm4Bv8o9ev4eAgxcA61i5WWp72amN20XVyxWwM5aQ==",
"dev": true
},
- "node_modules/@testing-library/dom": {
- "version": "7.31.2",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/code-frame": "^7.10.4",
- "@babel/runtime": "^7.12.5",
- "@types/aria-query": "^4.2.0",
- "aria-query": "^4.2.2",
- "chalk": "^4.1.0",
- "dom-accessibility-api": "^0.5.6",
- "lz-string": "^1.4.4",
- "pretty-format": "^26.6.2"
- },
- "engines": {
- "node": ">=10"
- }
- },
"node_modules/@testing-library/jest-dom": {
"version": "5.14.1",
"dev": true,
@@ -4025,22 +4007,6 @@
"node": ">=8"
}
},
- "node_modules/@testing-library/react": {
- "version": "11.2.7",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/runtime": "^7.12.5",
- "@testing-library/dom": "^7.28.1"
- },
- "engines": {
- "node": ">=10"
- },
- "peerDependencies": {
- "react": "*",
- "react-dom": "*"
- }
- },
"node_modules/@tootallnate/once": {
"version": "1.1.2",
"dev": true,
@@ -4566,7 +4532,9 @@
"license": "MIT"
},
"node_modules/@types/react": {
- "version": "16.14.8",
+ "version": "17.0.41",
+ "resolved": "https://packages.atlassian.com/api/npm/npm-remote/@types/react/-/react-17.0.41.tgz",
+ "integrity": "sha512-chYZ9ogWUodyC7VUTRBfblysKLjnohhFY9bGLwvnUFFy48+vB9DikmB3lW0qTFmBcKSzmdglcvkHK71IioOlDA==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -4576,11 +4544,13 @@
}
},
"node_modules/@types/react-dom": {
- "version": "16.9.13",
+ "version": "17.0.14",
+ "resolved": "https://packages.atlassian.com/api/npm/npm-remote/@types/react-dom/-/react-dom-17.0.14.tgz",
+ "integrity": "sha512-H03xwEP1oXmSfl3iobtmQ/2dHF5aBHr8aUMwyGZya6OW45G+xtdzmq6HkncefiBt5JU8DVyaWl/nWZbjZCnzAQ==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@types/react": "^16"
+ "@types/react": "*"
}
},
"node_modules/@types/scheduler": {
@@ -5167,7 +5137,9 @@
}
},
"node_modules/ansi-regex": {
- "version": "5.0.0",
+ "version": "5.0.1",
+ "resolved": "https://packages.atlassian.com/api/npm/npm-remote/ansi-regex/-/ansi-regex-5.0.1.tgz",
+ "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
"license": "MIT",
"engines": {
"node": ">=8"
@@ -7803,7 +7775,9 @@
}
},
"node_modules/dom-accessibility-api": {
- "version": "0.5.6",
+ "version": "0.5.13",
+ "resolved": "https://packages.atlassian.com/api/npm/npm-remote/dom-accessibility-api/-/dom-accessibility-api-0.5.13.tgz",
+ "integrity": "sha512-R305kwb5CcMDIpSHUnLyIAp7SrSPBx6F0VfQFB3M75xVMHhXJJIdePYgbPPh1o57vCHNu5QztokWUPsLjWzFqw==",
"dev": true,
"license": "MIT"
},
@@ -13023,6 +12997,7 @@
},
"node_modules/object-assign": {
"version": "4.1.1",
+ "dev": true,
"license": "MIT",
"engines": {
"node": ">=0.10.0"
@@ -14628,6 +14603,7 @@
},
"node_modules/prop-types": {
"version": "15.7.2",
+ "dev": true,
"license": "MIT",
"dependencies": {
"loose-envify": "^1.4.0",
@@ -14637,6 +14613,7 @@
},
"node_modules/prop-types/node_modules/react-is": {
"version": "16.13.1",
+ "dev": true,
"license": "MIT"
},
"node_modules/proxy-addr": {
@@ -14832,29 +14809,29 @@
}
},
"node_modules/react": {
- "version": "16.14.0",
+ "version": "18.0.0-rc.2-next-b9de50d2f-20220308",
+ "resolved": "https://packages.atlassian.com/api/npm/npm-remote/react/-/react-18.0.0-rc.2-next-b9de50d2f-20220308.tgz",
+ "integrity": "sha512-1RpONBLI/vgKN/kdq0DtPxrgXHS5OXCHbzXWXk2RcLRjlbBiqmAPUIpCq7x+02WhYQkgA6D0iRJo4NJ9fZMCGQ==",
"license": "MIT",
"dependencies": {
- "loose-envify": "^1.1.0",
- "object-assign": "^4.1.1",
- "prop-types": "^15.6.2"
+ "loose-envify": "^1.1.0"
},
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/react-dom": {
- "version": "16.14.0",
+ "version": "18.0.0-rc.1",
+ "resolved": "https://packages.atlassian.com/api/npm/npm-remote/react-dom/-/react-dom-18.0.0-rc.1.tgz",
+ "integrity": "sha512-BZ9NEwUp56MEguEwAzuh3u4bYE9Jv3QrzjaTmu11PV4C/lJCARTELEI16vjnHLq184GoJcCHMBrDILqlCrkZFQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"loose-envify": "^1.1.0",
- "object-assign": "^4.1.1",
- "prop-types": "^15.6.2",
- "scheduler": "^0.19.1"
+ "scheduler": "^0.21.0-rc.1"
},
"peerDependencies": {
- "react": "^16.14.0"
+ "react": "^18.0.0-rc.1"
}
},
"node_modules/react-is": {
@@ -15661,12 +15638,13 @@
}
},
"node_modules/scheduler": {
- "version": "0.19.1",
+ "version": "0.21.0-rc.2-next-b9de50d2f-20220308",
+ "resolved": "https://packages.atlassian.com/api/npm/npm-remote/scheduler/-/scheduler-0.21.0-rc.2-next-b9de50d2f-20220308.tgz",
+ "integrity": "sha512-aBVNOtcCn7k8FUi0lsIY34DZN1SUeuAjAQB6Y55Cxrhtrx9mggUepmF5gTWn7Z2RkEhQtrp7fzmRA/24bxxNJQ==",
"dev": true,
"license": "MIT",
"dependencies": {
- "loose-envify": "^1.1.0",
- "object-assign": "^4.1.1"
+ "loose-envify": "^1.1.0"
}
},
"node_modules/schema-utils": {
@@ -19416,14 +19394,90 @@
"version": "1.0.0",
"license": "Apache-2.0",
"devDependencies": {
- "@testing-library/react": "^11.2.7",
- "@types/react": "^16.14.8",
- "@types/react-dom": "^16.9.13",
- "react-dom": "^16.14.0"
+ "@testing-library/react": "^13.0.0-alpha.6",
+ "@types/react": "^17.0.0",
+ "@types/react-dom": "^17.0.0",
+ "react-dom": "^18.0.0-0"
},
"peerDependencies": {
"@react-loosely-lazy/manifest": "1.0.0",
- "react": "^16.9.0 || ^17.0.0-0"
+ "react": "^16.9.0 || ^17.0.0-0 || ^18.0.0-0"
+ }
+ },
+ "packages/core/react-loosely-lazy/node_modules/@testing-library/dom": {
+ "version": "8.11.3",
+ "resolved": "https://packages.atlassian.com/api/npm/npm-remote/@testing-library/dom/-/dom-8.11.3.tgz",
+ "integrity": "sha512-9LId28I+lx70wUiZjLvi1DB/WT2zGOxUh46glrSNMaWVx849kKAluezVzZrXJfTKKoQTmEOutLes/bHg4Bj3aA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.10.4",
+ "@babel/runtime": "^7.12.5",
+ "@types/aria-query": "^4.2.0",
+ "aria-query": "^5.0.0",
+ "chalk": "^4.1.0",
+ "dom-accessibility-api": "^0.5.9",
+ "lz-string": "^1.4.4",
+ "pretty-format": "^27.0.2"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "packages/core/react-loosely-lazy/node_modules/@testing-library/react": {
+ "version": "13.0.0-alpha.6",
+ "resolved": "https://packages.atlassian.com/api/npm/npm-remote/@testing-library/react/-/react-13.0.0-alpha.6.tgz",
+ "integrity": "sha512-AVJTnwLlnjvXDNe91P6Nt9pN2fMS4csAzTmIbOewja+LVKzhlr53EONhv3ck0J3GzSZ5MIN5qL3BfISX/Wf1Jg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.12.5",
+ "@testing-library/dom": "^8.5.0"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "peerDependencies": {
+ "react": "18.0.0-rc.1",
+ "react-dom": "18.0.0-rc.1"
+ }
+ },
+ "packages/core/react-loosely-lazy/node_modules/ansi-styles": {
+ "version": "5.2.0",
+ "resolved": "https://packages.atlassian.com/api/npm/npm-remote/ansi-styles/-/ansi-styles-5.2.0.tgz",
+ "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "packages/core/react-loosely-lazy/node_modules/aria-query": {
+ "version": "5.0.0",
+ "resolved": "https://packages.atlassian.com/api/npm/npm-remote/aria-query/-/aria-query-5.0.0.tgz",
+ "integrity": "sha512-V+SM7AbUwJ+EBnB8+DXs0hPZHO0W6pqBcc0dW90OwtVG02PswOu/teuARoLQjdDOH+t9pJgGnW5/Qmouf3gPJg==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=6.0"
+ }
+ },
+ "packages/core/react-loosely-lazy/node_modules/pretty-format": {
+ "version": "27.5.1",
+ "resolved": "https://packages.atlassian.com/api/npm/npm-remote/pretty-format/-/pretty-format-27.5.1.tgz",
+ "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-regex": "^5.0.1",
+ "ansi-styles": "^5.0.0",
+ "react-is": "^17.0.1"
+ },
+ "engines": {
+ "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0"
}
},
"packages/plugins/babel": {
@@ -19487,13 +19541,13 @@
"@react-loosely-lazy/integration-app": "^1.0.0",
"@react-loosely-lazy/manifest": "^1.0.0",
"@types/mini-css-extract-plugin": "^1.4.3",
- "@types/react": "^16.14.8",
- "@types/react-dom": "^16.9.13",
+ "@types/react": "^17.0.0",
+ "@types/react-dom": "^17.0.0",
"@types/webpack": "^4.41.29",
"css-loader": "^5.2.6",
"mini-css-extract-plugin": "^1.6.0",
- "react": "^16.14.0",
- "react-dom": "^16.14.0",
+ "react": "^18.0.0-0",
+ "react-dom": "^18.0.0-0",
"react-loosely-lazy": "1.0.0",
"tsconfig-paths-webpack-plugin": "^3.5.1",
"webpack": "^4.46.0"
@@ -19508,11 +19562,11 @@
"version": "1.0.0",
"license": "Apache-2.0",
"dependencies": {
- "react": "^16.14.0",
+ "react": "^18.0.0-0",
"react-loosely-lazy": "^1.0.0"
},
"devDependencies": {
- "@types/react": "^16.14.8"
+ "@types/react": "^17.0.0"
}
}
},
@@ -22052,8 +22106,8 @@
"@react-loosely-lazy/integration-app": {
"version": "file:packages/testing/integration-app",
"requires": {
- "@types/react": "^16.14.8",
- "react": "^16.14.0",
+ "@types/react": "^17.0.0",
+ "react": "^18.0.0-0",
"react-loosely-lazy": "^1.0.0"
}
},
@@ -22087,13 +22141,13 @@
"@react-loosely-lazy/integration-app": "^1.0.0",
"@react-loosely-lazy/manifest": "^1.0.0",
"@types/mini-css-extract-plugin": "^1.4.3",
- "@types/react": "^16.14.8",
- "@types/react-dom": "^16.9.13",
+ "@types/react": "^17.0.0",
+ "@types/react-dom": "^17.0.0",
"@types/webpack": "^4.41.29",
"css-loader": "^5.2.6",
"mini-css-extract-plugin": "^1.6.0",
- "react": "^16.14.0",
- "react-dom": "^16.14.0",
+ "react": "^18.0.0-0",
+ "react-dom": "^18.0.0-0",
"react-loosely-lazy": "1.0.0",
"tsconfig-paths-webpack-plugin": "^3.5.1",
"webpack": "^4.46.0"
@@ -22119,20 +22173,6 @@
"integrity": "sha512-hsPGC/U/0xe/WghMeSgyFsq9nNPfA5oY1Il2AaeAJcu/vTm4Bv8o9ev4eAgxcA61i5WWp72amN20XVyxWwM5aQ==",
"dev": true
},
- "@testing-library/dom": {
- "version": "7.31.2",
- "dev": true,
- "requires": {
- "@babel/code-frame": "^7.10.4",
- "@babel/runtime": "^7.12.5",
- "@types/aria-query": "^4.2.0",
- "aria-query": "^4.2.2",
- "chalk": "^4.1.0",
- "dom-accessibility-api": "^0.5.6",
- "lz-string": "^1.4.4",
- "pretty-format": "^26.6.2"
- }
- },
"@testing-library/jest-dom": {
"version": "5.14.1",
"dev": true,
@@ -22158,14 +22198,6 @@
}
}
},
- "@testing-library/react": {
- "version": "11.2.7",
- "dev": true,
- "requires": {
- "@babel/runtime": "^7.12.5",
- "@testing-library/dom": "^7.28.1"
- }
- },
"@tootallnate/once": {
"version": "1.1.2",
"dev": true
@@ -22565,7 +22597,9 @@
"dev": true
},
"@types/react": {
- "version": "16.14.8",
+ "version": "17.0.41",
+ "resolved": "https://packages.atlassian.com/api/npm/npm-remote/@types/react/-/react-17.0.41.tgz",
+ "integrity": "sha512-chYZ9ogWUodyC7VUTRBfblysKLjnohhFY9bGLwvnUFFy48+vB9DikmB3lW0qTFmBcKSzmdglcvkHK71IioOlDA==",
"dev": true,
"requires": {
"@types/prop-types": "*",
@@ -22574,10 +22608,12 @@
}
},
"@types/react-dom": {
- "version": "16.9.13",
+ "version": "17.0.14",
+ "resolved": "https://packages.atlassian.com/api/npm/npm-remote/@types/react-dom/-/react-dom-17.0.14.tgz",
+ "integrity": "sha512-H03xwEP1oXmSfl3iobtmQ/2dHF5aBHr8aUMwyGZya6OW45G+xtdzmq6HkncefiBt5JU8DVyaWl/nWZbjZCnzAQ==",
"dev": true,
"requires": {
- "@types/react": "^16"
+ "@types/react": "*"
}
},
"@types/scheduler": {
@@ -22989,7 +23025,9 @@
"version": "0.0.7"
},
"ansi-regex": {
- "version": "5.0.0"
+ "version": "5.0.1",
+ "resolved": "https://packages.atlassian.com/api/npm/npm-remote/ansi-regex/-/ansi-regex-5.0.1.tgz",
+ "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="
},
"ansi-styles": {
"version": "4.3.0",
@@ -24898,7 +24936,9 @@
}
},
"dom-accessibility-api": {
- "version": "0.5.6",
+ "version": "0.5.13",
+ "resolved": "https://packages.atlassian.com/api/npm/npm-remote/dom-accessibility-api/-/dom-accessibility-api-0.5.13.tgz",
+ "integrity": "sha512-R305kwb5CcMDIpSHUnLyIAp7SrSPBx6F0VfQFB3M75xVMHhXJJIdePYgbPPh1o57vCHNu5QztokWUPsLjWzFqw==",
"dev": true
},
"dom-serializer": {
@@ -28480,7 +28520,8 @@
"dev": true
},
"object-assign": {
- "version": "4.1.1"
+ "version": "4.1.1",
+ "dev": true
},
"object-copy": {
"version": "0.1.0",
@@ -29541,6 +29582,7 @@
},
"prop-types": {
"version": "15.7.2",
+ "dev": true,
"requires": {
"loose-envify": "^1.4.0",
"object-assign": "^4.1.1",
@@ -29548,7 +29590,8 @@
},
"dependencies": {
"react-is": {
- "version": "16.13.1"
+ "version": "16.13.1",
+ "dev": true
}
}
},
@@ -29693,21 +29736,21 @@
}
},
"react": {
- "version": "16.14.0",
+ "version": "18.0.0-rc.2-next-b9de50d2f-20220308",
+ "resolved": "https://packages.atlassian.com/api/npm/npm-remote/react/-/react-18.0.0-rc.2-next-b9de50d2f-20220308.tgz",
+ "integrity": "sha512-1RpONBLI/vgKN/kdq0DtPxrgXHS5OXCHbzXWXk2RcLRjlbBiqmAPUIpCq7x+02WhYQkgA6D0iRJo4NJ9fZMCGQ==",
"requires": {
- "loose-envify": "^1.1.0",
- "object-assign": "^4.1.1",
- "prop-types": "^15.6.2"
+ "loose-envify": "^1.1.0"
}
},
"react-dom": {
- "version": "16.14.0",
+ "version": "18.0.0-rc.1",
+ "resolved": "https://packages.atlassian.com/api/npm/npm-remote/react-dom/-/react-dom-18.0.0-rc.1.tgz",
+ "integrity": "sha512-BZ9NEwUp56MEguEwAzuh3u4bYE9Jv3QrzjaTmu11PV4C/lJCARTELEI16vjnHLq184GoJcCHMBrDILqlCrkZFQ==",
"dev": true,
"requires": {
"loose-envify": "^1.1.0",
- "object-assign": "^4.1.1",
- "prop-types": "^15.6.2",
- "scheduler": "^0.19.1"
+ "scheduler": "^0.21.0-rc.1"
}
},
"react-is": {
@@ -29717,10 +29760,61 @@
"react-loosely-lazy": {
"version": "file:packages/core/react-loosely-lazy",
"requires": {
- "@testing-library/react": "^11.2.7",
- "@types/react": "^16.14.8",
- "@types/react-dom": "^16.9.13",
- "react-dom": "^16.14.0"
+ "@testing-library/react": "^13.0.0-alpha.6",
+ "@types/react": "^17.0.0",
+ "@types/react-dom": "^17.0.0",
+ "react-dom": "^18.0.0-0"
+ },
+ "dependencies": {
+ "@testing-library/dom": {
+ "version": "8.11.3",
+ "resolved": "https://packages.atlassian.com/api/npm/npm-remote/@testing-library/dom/-/dom-8.11.3.tgz",
+ "integrity": "sha512-9LId28I+lx70wUiZjLvi1DB/WT2zGOxUh46glrSNMaWVx849kKAluezVzZrXJfTKKoQTmEOutLes/bHg4Bj3aA==",
+ "dev": true,
+ "requires": {
+ "@babel/code-frame": "^7.10.4",
+ "@babel/runtime": "^7.12.5",
+ "@types/aria-query": "^4.2.0",
+ "aria-query": "^5.0.0",
+ "chalk": "^4.1.0",
+ "dom-accessibility-api": "^0.5.9",
+ "lz-string": "^1.4.4",
+ "pretty-format": "^27.0.2"
+ }
+ },
+ "@testing-library/react": {
+ "version": "13.0.0-alpha.6",
+ "resolved": "https://packages.atlassian.com/api/npm/npm-remote/@testing-library/react/-/react-13.0.0-alpha.6.tgz",
+ "integrity": "sha512-AVJTnwLlnjvXDNe91P6Nt9pN2fMS4csAzTmIbOewja+LVKzhlr53EONhv3ck0J3GzSZ5MIN5qL3BfISX/Wf1Jg==",
+ "dev": true,
+ "requires": {
+ "@babel/runtime": "^7.12.5",
+ "@testing-library/dom": "^8.5.0"
+ }
+ },
+ "ansi-styles": {
+ "version": "5.2.0",
+ "resolved": "https://packages.atlassian.com/api/npm/npm-remote/ansi-styles/-/ansi-styles-5.2.0.tgz",
+ "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==",
+ "dev": true
+ },
+ "aria-query": {
+ "version": "5.0.0",
+ "resolved": "https://packages.atlassian.com/api/npm/npm-remote/aria-query/-/aria-query-5.0.0.tgz",
+ "integrity": "sha512-V+SM7AbUwJ+EBnB8+DXs0hPZHO0W6pqBcc0dW90OwtVG02PswOu/teuARoLQjdDOH+t9pJgGnW5/Qmouf3gPJg==",
+ "dev": true
+ },
+ "pretty-format": {
+ "version": "27.5.1",
+ "resolved": "https://packages.atlassian.com/api/npm/npm-remote/pretty-format/-/pretty-format-27.5.1.tgz",
+ "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==",
+ "dev": true,
+ "requires": {
+ "ansi-regex": "^5.0.1",
+ "ansi-styles": "^5.0.0",
+ "react-is": "^17.0.1"
+ }
+ }
}
},
"react-refresh": {
@@ -30259,11 +30353,12 @@
}
},
"scheduler": {
- "version": "0.19.1",
+ "version": "0.21.0-rc.2-next-b9de50d2f-20220308",
+ "resolved": "https://packages.atlassian.com/api/npm/npm-remote/scheduler/-/scheduler-0.21.0-rc.2-next-b9de50d2f-20220308.tgz",
+ "integrity": "sha512-aBVNOtcCn7k8FUi0lsIY34DZN1SUeuAjAQB6Y55Cxrhtrx9mggUepmF5gTWn7Z2RkEhQtrp7fzmRA/24bxxNJQ==",
"dev": true,
"requires": {
- "loose-envify": "^1.1.0",
- "object-assign": "^4.1.1"
+ "loose-envify": "^1.1.0"
}
},
"schema-utils": {
diff --git a/package.json b/package.json
index cc4141b..bb39c89 100644
--- a/package.json
+++ b/package.json
@@ -31,8 +31,8 @@
"@babel/runtime": "^7.14.5",
"@testing-library/jest-dom": "^5.14.1",
"@types/jest": "^26.0.23",
- "@types/react": "^16.14.8",
- "@types/react-dom": "^16.9.13",
+ "@types/react": "^17.0.0",
+ "@types/react-dom": "^17.0.0",
"@types/testing-library__jest-dom": "^5.14.0",
"@types/webpack": "^4.41.29",
"@typescript-eslint/eslint-plugin": "^4.27.0",
@@ -54,8 +54,8 @@
"flow-bin": "^0.151.0",
"jest": "^26.6.3",
"prettier": "^2.3.1",
- "react": "^16.14.0",
- "react-dom": "^16.14.0",
+ "react": "^18.0.0-0",
+ "react-dom": "^18.0.0-0",
"semver": "^7.3.5",
"tsconfig-paths-webpack-plugin": "^3.5.1",
"typescript": "^4.3.3",
diff --git a/packages/core/react-loosely-lazy/package.json b/packages/core/react-loosely-lazy/package.json
index 241af0a..9b16213 100644
--- a/packages/core/react-loosely-lazy/package.json
+++ b/packages/core/react-loosely-lazy/package.json
@@ -26,13 +26,13 @@
"dist"
],
"devDependencies": {
- "@testing-library/react": "^11.2.7",
- "@types/react": "^16.14.8",
- "@types/react-dom": "^16.9.13",
- "react-dom": "^16.14.0"
+ "@testing-library/react": "^13.0.0-alpha.6",
+ "@types/react": "^17.0.0",
+ "@types/react-dom": "^17.0.0",
+ "react-dom": "^18.0.0-0"
},
"peerDependencies": {
"@react-loosely-lazy/manifest": "1.0.0",
- "react": "^16.9.0 || ^17.0.0-0"
+ "react": "^16.9.0 || ^17.0.0-0 || ^18.0.0-0"
}
}
diff --git a/packages/core/react-loosely-lazy/src/lazy-wait/context.ts b/packages/core/react-loosely-lazy/src/lazy-wait/context.ts
index bcaa2ab..2f3e5c3 100644
--- a/packages/core/react-loosely-lazy/src/lazy-wait/context.ts
+++ b/packages/core/react-loosely-lazy/src/lazy-wait/context.ts
@@ -1,19 +1,9 @@
import { createContext } from 'react';
-import type { MutableRefObject } from 'react';
-import type { Cleanup } from '../cleanup';
+import type { SubscriptionContextValue } from '../lazy/types';
import { noopCleanup } from '../cleanup';
-export type UntilSubscriber = (until: boolean) => void;
-
-export type UntilContextValue = {
- subscribe: (subscriber: UntilSubscriber) => Cleanup;
- value: MutableRefObject;
-};
-
-export const UntilContext = createContext({
+export const WaitContext = createContext({
subscribe: () => noopCleanup,
- value: {
- current: true,
- },
+ currentValue: () => 1,
});
diff --git a/packages/core/react-loosely-lazy/src/lazy-wait/index.tsx b/packages/core/react-loosely-lazy/src/lazy-wait/index.tsx
index 54ab05e..71fc0ae 100644
--- a/packages/core/react-loosely-lazy/src/lazy-wait/index.tsx
+++ b/packages/core/react-loosely-lazy/src/lazy-wait/index.tsx
@@ -1,4 +1,3 @@
export { LazyWait } from './main';
export type { LazyWaitProps } from './main';
-
-export { useUntil } from './utils';
+export { WaitContext } from './context';
diff --git a/packages/core/react-loosely-lazy/src/lazy-wait/main.tsx b/packages/core/react-loosely-lazy/src/lazy-wait/main.tsx
index 174e5b8..c636eb3 100644
--- a/packages/core/react-loosely-lazy/src/lazy-wait/main.tsx
+++ b/packages/core/react-loosely-lazy/src/lazy-wait/main.tsx
@@ -1,8 +1,9 @@
import React, { useContext, useEffect, useRef } from 'react';
import type { ReactNode } from 'react';
-import { UntilContext } from './context';
-import type { UntilSubscriber } from './context';
+import { WaitContext } from './context';
+import type { SubscriptionContextValue } from '../lazy/types';
+// import type { UntilSubscriber } from './context';
export type LazyWaitProps = {
until: boolean;
@@ -10,37 +11,35 @@ export type LazyWaitProps = {
};
export const LazyWait = ({ until, children }: LazyWaitProps) => {
- const closestUntil = useContext(UntilContext);
- const value = useRef(until && closestUntil.value.current);
- const subscribers = useRef>(new Set());
- const api = useRef({
- subscribe: (subscriber: UntilSubscriber) => {
+ const closestWait = useContext(WaitContext);
+ const value = useRef(until && closestWait.currentValue() ? 1 : 0);
+ const subscribers = useRef void>>(new Set());
+ const api = useRef({
+ subscribe: subscriber => {
subscribers.current.add(subscriber);
return () => {
subscribers.current.delete(subscriber);
};
},
- value,
+ currentValue: () => value.current,
});
useEffect(() => {
// Notify subscribers when until prop or closest until value changes
- const notify = (nextUntil: boolean) => {
- value.current = nextUntil && until;
+ const notify = () => {
+ value.current = closestWait.currentValue() && until ? 1 : 0;
for (const subscriber of subscribers.current) {
- subscriber(value.current);
+ subscriber();
}
};
- notify(closestUntil.value.current);
+ notify();
- return closestUntil.subscribe(notify);
- }, [closestUntil, until]);
+ return closestWait.subscribe(notify);
+ }, [closestWait, until]);
return (
-
- {children}
-
+ {children}
);
};
diff --git a/packages/core/react-loosely-lazy/src/lazy-wait/utils.tsx b/packages/core/react-loosely-lazy/src/lazy-wait/utils.tsx
deleted file mode 100644
index f654ed3..0000000
--- a/packages/core/react-loosely-lazy/src/lazy-wait/utils.tsx
+++ /dev/null
@@ -1,18 +0,0 @@
-import { useContext, useEffect, useState } from 'react';
-
-import { UntilContext } from './context';
-
-export const useUntil = () => {
- const { subscribe, value } = useContext(UntilContext);
- const [until, setUntil] = useState(value.current);
-
- useEffect(
- () =>
- subscribe(nextUntil => {
- setUntil(nextUntil);
- }),
- [subscribe]
- );
-
- return until;
-};
diff --git a/packages/core/react-loosely-lazy/src/lazy/components/client.tsx b/packages/core/react-loosely-lazy/src/lazy/components/client.tsx
index 19fb289..b1d2b96 100644
--- a/packages/core/react-loosely-lazy/src/lazy/components/client.tsx
+++ b/packages/core/react-loosely-lazy/src/lazy/components/client.tsx
@@ -1,26 +1,64 @@
+/* eslint-disable react-hooks/exhaustive-deps */
import React, {
ComponentProps,
ComponentType,
lazy,
+ useCallback,
useContext,
- useEffect,
useMemo,
- useRef,
useState,
} from 'react';
import { getConfig, MODE } from '../../config';
import { COLLECTED, PHASE } from '../../constants';
-import { useUntil } from '../../lazy-wait';
+import { WaitContext } from '../../lazy-wait';
import { LazySuspenseContext } from '../../suspense';
-import { usePhaseSubscription } from '../../phase';
+import { LazyPhaseContext } from '../../phase';
import { PRIORITY } from '../constants';
import { Deferred } from '../deferred';
import { createLoaderError } from '../errors';
import { PlaceholderFallbackRender } from '../placeholders/render';
-import { PlaceholderFallbackHydrate } from '../placeholders/hydrate';
import { preloadAsset } from '../preload';
+import { SubscriptionContextValue } from '../types';
+
+type Status = {
+ preload: boolean;
+ phase: boolean;
+ noWait: boolean;
+ start: boolean;
+};
+
+type UseSubscriptionArgs = {
+ context: C;
+ key: 'phase' | 'noWait';
+ status: Status;
+ load: () => void;
+ comparator: (v: number) => boolean;
+};
+
+function useSubscription>({
+ context,
+ key,
+ status,
+ load,
+ comparator,
+}: UseSubscriptionArgs) {
+ const { subscribe, currentValue } = useContext(context);
+ useMemo(() => {
+ let unsubscribe: (() => void) | null = null;
+ const check = () => {
+ const done = comparator(currentValue());
+ status[key] = done;
+ if (done) load();
+ if (done && unsubscribe) unsubscribe();
+
+ return done;
+ };
+
+ unsubscribe = !check() ? subscribe(check) : null;
+ }, []);
+}
export function createComponentClient>({
defer,
@@ -38,76 +76,78 @@ export function createComponentClient>({
const ResolvedLazy = lazy(() => deferred.promise);
return (props: ComponentProps) => {
- const { setFallback } = useContext(LazySuspenseContext);
- const started = useRef(false);
- const [, setState] = useState();
- const until = useUntil();
+ const [status, bubbleError] = useState(() => ({
+ preload: defer !== PHASE.AFTER_PAINT,
+ phase: defer === PHASE.LAZY,
+ noWait: true,
+ start: false,
+ }));
- const load = useRef(() => {
- if (started.current) {
+ const load = useCallback(() => {
+ if (deferred.result || status.start || !status.phase || !status.noWait) {
return;
}
- started.current = true;
+ status.start = true;
deferred.start().catch((err: Error) => {
// Throw the error within the component lifecycle
// refer to https://github.com/facebook/react/issues/11409
- setState(() => {
+ bubbleError(() => {
throw createLoaderError(err);
});
});
+ }, []);
+
+ // Subscribe to LazyWait context, triggering load when until is true
+ useSubscription({
+ context: WaitContext,
+ key: 'noWait',
+ status,
+ load,
+ comparator: v => v === 1,
});
- if (defer === PHASE.LAZY) {
- useEffect(() => {
- if (until) {
- load.current();
- }
- }, [until]);
- } else {
- const isOwnPhase = usePhaseSubscription(defer);
+ // Subscribe to LazyPhase context, triggering load when own phase starts
+ useSubscription({
+ // @ts-expect-error Context Provider shape confuses TS
+ context: LazyPhaseContext,
+ key: 'phase',
+ status,
+ load,
+ comparator: v => v >= defer,
+ });
- useMemo(() => {
- if (isOwnPhase && until) {
- load.current();
- }
- }, [isOwnPhase, until]);
-
- if (defer === PHASE.AFTER_PAINT) {
- // Start preloading as will be needed soon
- useEffect(() => {
- if (!isOwnPhase) {
- return preloadAsset({
- loader: deferred.preload,
- moduleId,
- priority: PRIORITY.LOW,
- });
- }
- }, [isOwnPhase]);
+ useMemo(() => {
+ if (!status.preload) {
+ status.preload = true;
+ preloadAsset({
+ loader: deferred.preload,
+ moduleId,
+ priority: PRIORITY.LOW,
+ });
}
- }
+ }, []);
- useMemo(() => {
- // find SSR content (or fallbacks) wrapped in inputs based on lazyId
- const content = (COLLECTED.get(dataLazyId) || []).shift();
- if (!content) return;
-
- // override Suspense fallback with magic input wrappers
- const { mode } = getConfig();
- const component =
- mode === MODE.RENDER ? (
+ const { setFallback } = useContext(LazySuspenseContext);
+
+ if (getConfig().mode === MODE.RENDER) {
+ // Allow render mode to support partial progressive hydration
+ useMemo(() => {
+ // find SSR content (or fallbacks) wrapped in inputs based on lazyId
+ const content = (COLLECTED.get(dataLazyId) || []).shift();
+ if (!content || !ssr) return;
+
+ // override Suspense fallback with magic input wrappers
+ setFallback(
- ) : (
-
);
- setFallback(component);
- }, [setFallback]);
-
- if (!ssr) {
- // as the fallback is SSRd too, we want to discard it as soon as this
- // mounts (to avoid hydration warnings) and let Suspense render it
- useEffect(() => {
- setFallback(null);
+ }, [setFallback]);
+ } else {
+ // Allow hydration to support partials without server components
+ useMemo(() => {
+ // suspense will discard ssr during hydration if re-renders so we
+ // set a dummy fallback to block updates from the provider until we resolve
+ setFallback(deferred.result ? null : <>>);
}, [setFallback]);
}
diff --git a/packages/core/react-loosely-lazy/src/lazy/components/server.tsx b/packages/core/react-loosely-lazy/src/lazy/components/server.tsx
index 17916d8..1b04b84 100644
--- a/packages/core/react-loosely-lazy/src/lazy/components/server.tsx
+++ b/packages/core/react-loosely-lazy/src/lazy/components/server.tsx
@@ -1,9 +1,7 @@
-import { getAssetUrlsFromId } from '@react-loosely-lazy/manifest';
import React, { useContext } from 'react';
import type { ComponentProps, ComponentType } from 'react';
-import { getConfig } from '../../config';
-import { PHASE } from '../../constants';
+import { getConfig, MODE } from '../../config';
import { LazySuspenseContext } from '../../suspense';
import { getExport } from '../../utils';
@@ -34,24 +32,18 @@ export function createComponentServer>({
return (props: ComponentProps) => {
const Resolved = ssr ? load(moduleId, loader) : null;
const { fallback } = useContext(LazySuspenseContext);
- const { crossOrigin, manifest } = getConfig();
+ const { mode } = getConfig();
- return (
- <>
-
- {defer !== PHASE.LAZY &&
- getAssetUrlsFromId(manifest, moduleId)?.map(url => (
-
- ))}
- {Resolved ? : fallback}
-
- >
- );
+ if (mode === MODE.RENDER) {
+ return (
+ <>
+
+ {Resolved ? : fallback}
+
+ >
+ );
+ }
+
+ return <>{Resolved ? : fallback}>;
};
}
diff --git a/packages/core/react-loosely-lazy/src/lazy/deferred.ts b/packages/core/react-loosely-lazy/src/lazy/deferred.ts
index 075a0c3..f0ee642 100644
--- a/packages/core/react-loosely-lazy/src/lazy/deferred.ts
+++ b/packages/core/react-loosely-lazy/src/lazy/deferred.ts
@@ -31,21 +31,11 @@ export const createDeferred = >(
if (deferred.result) {
return;
}
-
- loader().then((m: any) => {
- deferred.result = m;
- });
+ // just load the code, without resolving
+ loader();
},
start: () => {
- if (deferred.result) {
- resolve(deferred.result);
-
- return deferred.promise.then(() => {
- // Return void...
- });
- } else {
- return loader().then(resolve);
- }
+ return loader().then(resolve);
},
};
diff --git a/packages/core/react-loosely-lazy/src/lazy/placeholders/hydrate.tsx b/packages/core/react-loosely-lazy/src/lazy/placeholders/hydrate.tsx
deleted file mode 100644
index 55eb76c..0000000
--- a/packages/core/react-loosely-lazy/src/lazy/placeholders/hydrate.tsx
+++ /dev/null
@@ -1,38 +0,0 @@
-import React, { createElement, Fragment } from 'react';
-import { attrToProp } from '../../utils';
-
-export type PlaceholderFallbackHydrateProps = {
- id: string;
- content: HTMLElement[];
-};
-
-export const PlaceholderFallbackHydrate = ({
- id,
- content,
-}: PlaceholderFallbackHydrateProps) => {
- return (
- <>
-
- {content.map((el: HTMLElement, i: number) => {
- const { tagName = '', childNodes = [], attributes = [] } = el;
- const props = Array.from(attributes).reduce(attrToProp, {
- key: String(i),
- });
- // text node
- if (!tagName) return createElement(Fragment, props, el.textContent);
-
- // childless tag
- if (!childNodes.length)
- return createElement(tagName.toLowerCase(), props);
-
- // tag with content
- return createElement(tagName.toLowerCase(), {
- ...props,
- dangerouslySetInnerHTML: { __html: '' },
- suppressHydrationWarning: true,
- });
- })}
-
- >
- );
-};
diff --git a/packages/core/react-loosely-lazy/src/lazy/placeholders/render.tsx b/packages/core/react-loosely-lazy/src/lazy/placeholders/render.tsx
index 3ab1f67..bfb95bb 100644
--- a/packages/core/react-loosely-lazy/src/lazy/placeholders/render.tsx
+++ b/packages/core/react-loosely-lazy/src/lazy/placeholders/render.tsx
@@ -1,9 +1,5 @@
import React, { useRef, useLayoutEffect } from 'react';
-function isLinkPrefetch(el: HTMLElement): el is HTMLLinkElement {
- return el.tagName === 'LINK' && (el as HTMLLinkElement).rel === 'prefetch';
-}
-
const usePlaceholderRender = (resolveId: string, content: HTMLElement[]) => {
const hydrationRef = useRef(null);
const { current: ssrDomNodes } = useRef(content || ([] as HTMLElement[]));
@@ -14,10 +10,6 @@ const usePlaceholderRender = (resolveId: string, content: HTMLElement[]) => {
if (parentNode && !parentNode.contains(ssrDomNodes[0])) {
ssrDomNodes.reverse().forEach((node: HTMLElement) => {
- // this fixes an issue with Chrome that re-triggers and cancels prefetch
- // when node is appended again, making network panel quite noisy
- if (isLinkPrefetch(node)) node.rel = '';
-
parentNode.insertBefore(node, (element as any).nextSibling);
});
}
diff --git a/packages/core/react-loosely-lazy/src/lazy/types.ts b/packages/core/react-loosely-lazy/src/lazy/types.ts
index a16a018..abec18b 100644
--- a/packages/core/react-loosely-lazy/src/lazy/types.ts
+++ b/packages/core/react-loosely-lazy/src/lazy/types.ts
@@ -28,3 +28,8 @@ export type LazyComponent> = FunctionComponent<
preload: (priority?: PreloadPriority) => Cleanup;
getAssetUrls: () => string[] | undefined;
};
+
+export interface SubscriptionContextValue {
+ subscribe: (callback: () => void) => Cleanup;
+ currentValue: () => number;
+}
diff --git a/packages/core/react-loosely-lazy/src/phase/context.tsx b/packages/core/react-loosely-lazy/src/phase/context.tsx
index 9a5f9ad..55fe71b 100644
--- a/packages/core/react-loosely-lazy/src/phase/context.tsx
+++ b/packages/core/react-loosely-lazy/src/phase/context.tsx
@@ -1,6 +1,7 @@
import { createContext, useContext } from 'react';
import { PHASE } from '../constants';
+import type { SubscriptionContextValue } from '../lazy/types';
import { LISTENERS } from './listeners';
import type { Listener } from './listeners';
@@ -13,9 +14,16 @@ export const setCurrent = (phase: number) => {
LISTENERS.slice(0).forEach((listener: Listener) => listener(phase));
};
-export const LazyPhaseContext = createContext({
+interface LazyPhaseContextApi extends SubscriptionContextValue {
+ api: {
+ startNextPhase: () => void;
+ resetPhase: () => void;
+ };
+}
+
+export const LazyPhaseContext = createContext({
subscribe: createSubscribe(LISTENERS),
- currentPhase: () => CURRENT_PHASE,
+ currentValue: () => CURRENT_PHASE,
api: {
startNextPhase: () => {
setCurrent(PHASE.AFTER_PAINT);
diff --git a/packages/core/react-loosely-lazy/src/phase/controller.ts b/packages/core/react-loosely-lazy/src/phase/controller.ts
deleted file mode 100644
index 680d804..0000000
--- a/packages/core/react-loosely-lazy/src/phase/controller.ts
+++ /dev/null
@@ -1,20 +0,0 @@
-import { useContext, useMemo, useState, useEffect } from 'react';
-
-import { LazyPhaseContext } from './context';
-
-export const usePhaseSubscription = (targetPhase = -1) => {
- const { subscribe, currentPhase } = useContext(LazyPhaseContext);
- const [run, setRun] = useState(() => currentPhase() >= targetPhase);
-
- // subscribe with memo instead of effect to retain tree order
- const unsubscribe = useMemo(
- () => subscribe((v: number) => setRun(v >= targetPhase)),
- [subscribe, setRun, targetPhase]
- );
- // subscription is done on first render, here just unsubscribe
- useEffect(() => {
- return unsubscribe;
- }, [unsubscribe]);
-
- return run;
-};
diff --git a/packages/core/react-loosely-lazy/src/phase/index.tsx b/packages/core/react-loosely-lazy/src/phase/index.tsx
index a995851..bc97dde 100644
--- a/packages/core/react-loosely-lazy/src/phase/index.tsx
+++ b/packages/core/react-loosely-lazy/src/phase/index.tsx
@@ -1,7 +1,5 @@
export { LazyPhaseContext, setCurrent, useLazyPhase } from './context';
-export { usePhaseSubscription } from './controller';
-
export { LISTENERS } from './listeners';
export type { Listener } from './listeners';
diff --git a/packages/core/react-loosely-lazy/src/suspense/component.tsx b/packages/core/react-loosely-lazy/src/suspense/component.tsx
index 75a3dfd..ce12131 100644
--- a/packages/core/react-loosely-lazy/src/suspense/component.tsx
+++ b/packages/core/react-loosely-lazy/src/suspense/component.tsx
@@ -1,8 +1,7 @@
import React, { Component, Suspense, useLayoutEffect } from 'react';
import type { FunctionComponent } from 'react';
-import { isNodeEnvironment } from '../utils';
-
+import { getConfig, MODE } from '../config';
import { LazySuspenseContext } from './context';
import { Fallback, LazySuspenseContextType, LazySuspenseProps } from './types';
@@ -10,7 +9,6 @@ type LazySuspenseState = LazySuspenseContextType;
type DynamicFallbackProps = {
children(fallback: Fallback): any;
- outsideSuspense: boolean;
};
/**
@@ -51,27 +49,25 @@ export class LazySuspense extends Component<
setFallback: (fallback: Fallback) => {
if (this.hydrationFallback === fallback) return;
this.hydrationFallback = fallback;
- // Schedule an update so we force switch from the sibling tree
- // back to the suspense boundary
- if (this.mounted) this.forceUpdate();
},
};
private hydrationFallback: Fallback = null;
- private mounted = false;
constructor(props: LazySuspenseProps) {
super(props);
this.DynamicFallback.displayName = 'DynamicFallback';
}
- componentDidMount() {
- this.mounted = true;
+ shouldComponentUpdate() {
+ // This is a workaround to prevent Suspense during hydration
+ // from switching to the fallback if a re-render occurs
+ // https://github.com/facebook/react/issues/22692
+ return this.hydrationFallback ? false : true;
}
private DynamicFallback: FunctionComponent = ({
children,
- outsideSuspense,
}) => {
// eslint-disable-next-line react-hooks/rules-of-hooks
useLayoutEffect(() => {
@@ -80,46 +76,33 @@ export class LazySuspense extends Component<
// when both Lazy AND the eventual promises thrown are done
// so Suspense will re-render with actual content and we remove
// the hydration fallback at the same time
- if (!outsideSuspense) this.state.setFallback(null);
+ this.state.setFallback(this.hydrationFallback);
};
- }, [outsideSuspense]);
+ }, []);
- return outsideSuspense
- ? children(this.hydrationFallback ? this.hydrationFallback : null)
- : children(this.hydrationFallback ? null : this.props.fallback);
+ return children(this.hydrationFallback || this.props.fallback);
};
- private renderFallback(outsideSuspense: boolean) {
- const { DynamicFallback } = this;
+ private renderFallback() {
+ if (getConfig().mode === MODE.RENDER) {
+ const { DynamicFallback } = this;
- // Use render prop component to allow switch to hydration fallback
- return (
-
- {(fallback: Fallback) => fallback}
-
- );
- }
+ // Use render prop component to allow switch to hydration fallback
+ return (
+ {(fallback: Fallback) => fallback}
+ );
+ }
- private renderServer() {
- return (
-
- {this.props.children}
-
- );
+ return this.props.fallback;
}
- private renderClient() {
+ render() {
return (
-
+
{this.props.children}
- {(!this.mounted || this.hydrationFallback) && this.renderFallback(true)}
);
}
-
- render() {
- return isNodeEnvironment() ? this.renderServer() : this.renderClient();
- }
}
diff --git a/packages/core/react-loosely-lazy/src/suspense/context.tsx b/packages/core/react-loosely-lazy/src/suspense/context.tsx
index 8134c75..176d492 100644
--- a/packages/core/react-loosely-lazy/src/suspense/context.tsx
+++ b/packages/core/react-loosely-lazy/src/suspense/context.tsx
@@ -1,10 +1,9 @@
import React, { Fragment, createContext } from 'react';
-import { Fallback, LazySuspenseContextType } from './types';
+import { LazySuspenseContextType } from './types';
export const LazySuspenseContext = createContext({
fallback: ,
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
- setFallback: (fallback: Fallback) => {
+ setFallback: () => {
console.warn('Missing boundary');
},
});
diff --git a/packages/plugins/webpack/package.json b/packages/plugins/webpack/package.json
index a858687..1ee30a5 100644
--- a/packages/plugins/webpack/package.json
+++ b/packages/plugins/webpack/package.json
@@ -28,13 +28,13 @@
"@react-loosely-lazy/integration-app": "^1.0.0",
"@react-loosely-lazy/manifest": "^1.0.0",
"@types/mini-css-extract-plugin": "^1.4.3",
- "@types/react": "^16.14.8",
- "@types/react-dom": "^16.9.13",
+ "@types/react": "^17.0.0",
+ "@types/react-dom": "^17.0.0",
"@types/webpack": "^4.41.29",
"css-loader": "^5.2.6",
"mini-css-extract-plugin": "^1.6.0",
- "react": "^16.14.0",
- "react-dom": "^16.14.0",
+ "react": "^18.0.0-0",
+ "react-dom": "^18.0.0-0",
"react-loosely-lazy": "1.0.0",
"tsconfig-paths-webpack-plugin": "^3.5.1",
"webpack": "^4.46.0"
diff --git a/packages/testing/integration-app/package.json b/packages/testing/integration-app/package.json
index 8e7ef2f..9ad5a76 100644
--- a/packages/testing/integration-app/package.json
+++ b/packages/testing/integration-app/package.json
@@ -20,10 +20,10 @@
"sideEffects": true,
"source": "src/index.tsx",
"dependencies": {
- "react": "^16.14.0",
+ "react": "^18.0.0-0",
"react-loosely-lazy": "^1.0.0"
},
"devDependencies": {
- "@types/react": "^16.14.8"
+ "@types/react": "^17.0.0"
}
}