New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Support suspense in next dynamic #27611
Conversation
This comment has been hidden.
This comment has been hidden.
This comment has been hidden.
This comment has been hidden.
This comment has been hidden.
This comment has been hidden.
This comment has been hidden.
This comment has been hidden.
This comment has been hidden.
This comment has been hidden.
This comment has been hidden.
This comment has been hidden.
This comment has been hidden.
This comment has been hidden.
This comment has been hidden.
This comment has been hidden.
This comment has been hidden.
This comment has been hidden.
This comment has been hidden.
This comment has been hidden.
This comment has been hidden.
This comment has been hidden.
When will this be available for testing in combination with the React 18 alpha builds? Will there be a canary release containing Suspense support in the coming weeks? |
This comment has been hidden.
This comment has been hidden.
This comment has been hidden.
This comment has been hidden.
This comment has been hidden.
This comment has been hidden.
This comment has been hidden.
This comment has been hidden.
This comment has been hidden.
This comment has been hidden.
`Disallowed suspense option usage with next/dynamic in blocking mode` | ||
) | ||
} | ||
suspenseOptions.suspense = false |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we want to also show errors if users enable suspense w/o concurrent features?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we do, yes. Since it's also experimental.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Suspense will be resolved as fallback on server side in blocking rendering, do we want to skip this scenario? I think this can expand use cases, throwing error will limit usage of client suspenses.
Stats from current PRDefault Build (Increase detected
|
vercel/next.js canary | huozhi/next.js dynamic-lazy | Change | |
---|---|---|---|
buildDuration | 13.6s | 13.7s | |
buildDurationCached | 3.2s | 3.1s | -82ms |
nodeModulesSize | 49.1 MB | 49.1 MB |
Page Load Tests Overall increase ✓
vercel/next.js canary | huozhi/next.js dynamic-lazy | Change | |
---|---|---|---|
/ failed reqs | 0 | 0 | ✓ |
/ total time (seconds) | 2.467 | 2.501 | |
/ avg req/sec | 1013.35 | 999.73 | |
/error-in-render failed reqs | 0 | 0 | ✓ |
/error-in-render total time (seconds) | 1.398 | 1.362 | -0.04 |
/error-in-render avg req/sec | 1788.17 | 1835.65 | +47.48 |
Client Bundles (main, webpack, commons)
vercel/next.js canary | huozhi/next.js dynamic-lazy | Change | |
---|---|---|---|
745.HASH.js gzip | 179 B | 179 B | ✓ |
framework-HASH.js gzip | 42.2 kB | 42.2 kB | ✓ |
main-HASH.js gzip | 23.1 kB | 23.1 kB | ✓ |
webpack-HASH.js gzip | 1.5 kB | 1.5 kB | ✓ |
Overall change | 67 kB | 67 kB | ✓ |
Legacy Client Bundles (polyfills)
vercel/next.js canary | huozhi/next.js dynamic-lazy | Change | |
---|---|---|---|
polyfills-HASH.js gzip | 31.1 kB | 31.1 kB | ✓ |
Overall change | 31.1 kB | 31.1 kB | ✓ |
Client Pages Overall increase ⚠️
vercel/next.js canary | huozhi/next.js dynamic-lazy | Change | |
---|---|---|---|
_app-HASH.js gzip | 980 B | 980 B | ✓ |
_error-HASH.js gzip | 194 B | 194 B | ✓ |
amp-HASH.js gzip | 312 B | 312 B | ✓ |
css-HASH.js gzip | 329 B | 329 B | ✓ |
dynamic-HASH.js gzip | 2.52 kB | 2.64 kB | |
head-HASH.js gzip | 350 B | 350 B | ✓ |
hooks-HASH.js gzip | 904 B | 904 B | ✓ |
image-HASH.js gzip | 4.13 kB | 4.13 kB | ✓ |
index-HASH.js gzip | 261 B | 261 B | ✓ |
link-HASH.js gzip | 1.66 kB | 1.66 kB | ✓ |
routerDirect..HASH.js gzip | 319 B | 319 B | ✓ |
script-HASH.js gzip | 387 B | 387 B | ✓ |
withRouter-HASH.js gzip | 320 B | 320 B | ✓ |
bb14e60e810b..30f.css gzip | 125 B | 125 B | ✓ |
Overall change | 12.8 kB | 12.9 kB |
Client Build Manifests Overall increase ⚠️
vercel/next.js canary | huozhi/next.js dynamic-lazy | Change | |
---|---|---|---|
_buildManifest.js gzip | 491 B | 492 B | |
Overall change | 491 B | 492 B |
Rendered Page Sizes
vercel/next.js canary | huozhi/next.js dynamic-lazy | Change | |
---|---|---|---|
index.html gzip | 533 B | 533 B | ✓ |
link.html gzip | 546 B | 546 B | ✓ |
withRouter.html gzip | 525 B | 525 B | ✓ |
Overall change | 1.6 kB | 1.6 kB | ✓ |
Diffs
Diff for _buildManifest.js
@@ -10,7 +10,7 @@ self.__BUILD_MANIFEST = {
"static\u002Fchunks\u002Fpages\u002Fcss-979928a4957344dd4cfa.js"
],
"/dynamic": [
- "static\u002Fchunks\u002Fpages\u002Fdynamic-3ae32efdc092678bfdaf.js"
+ "static\u002Fchunks\u002Fpages\u002Fdynamic-cfe35dffa9e5283a3e13.js"
],
"/head": ["static\u002Fchunks\u002Fpages\u002Fhead-b93e7fccd3af1c72154e.js"],
"/hooks": [
Diff for dynamic-HASH.js
@@ -126,7 +126,24 @@
loadableOptions = _objectSpread(
_objectSpread({}, loadableOptions),
options
- ); // coming from build/babel/plugins/react-loadable-plugin.js
+ );
+ var suspenseOptions = loadableOptions;
+
+ if (true) {
+ // Error if react root is not enabled and `suspense` option is set to true
+ if (true && suspenseOptions.suspense) {
+ // TODO: add error doc when this feature is stable
+ throw new Error(
+ "Disallowed suspense option usage with next/dynamic in blocking mode"
+ );
+ }
+
+ suspenseOptions.suspense = false;
+ }
+
+ if (suspenseOptions.suspense) {
+ return loadableFn(suspenseOptions);
+ } // coming from build/babel/plugins/react-loadable-plugin.js
if (loadableOptions.loadableGenerated) {
loadableOptions = _objectSpread(
@@ -190,12 +207,12 @@
) {
"use strict";
- var _defineProperty = __webpack_require__(9713);
-
var _classCallCheck = __webpack_require__(4575);
var _createClass = __webpack_require__(3913);
+ var _defineProperty = __webpack_require__(9713);
+
function ownKeys(object, enumerableOnly) {
var keys = Object.keys(object);
if (Object.getOwnPropertySymbols) {
@@ -364,10 +381,16 @@
delay: 200,
timeout: null,
webpack: null,
- modules: null
+ modules: null,
+ suspense: false
},
options
);
+
+ if (opts.suspense) {
+ opts.lazy = _react["default"].lazy(opts.loader);
+ }
+
var subscription = null;
function init() {
@@ -391,7 +414,8 @@
!initialized &&
true &&
typeof opts.webpack === "function" &&
- "function" === "function"
+ "function" === "function" &&
+ !opts.suspense
) {
var moduleIds = opts.webpack();
READY_INITIALIZERS.push(function(ids) {
@@ -414,7 +438,7 @@
});
}
- var LoadableComponent = function LoadableComponent(props, ref) {
+ function LoadableImpl(props, ref) {
init();
var context = _react["default"].useContext(
@@ -460,10 +484,25 @@
},
[props, state]
);
- };
+ }
+
+ function LazyImpl(props, ref) {
+ return _react["default"].createElement(
+ opts.lazy,
+ _objectSpread(
+ _objectSpread({}, props),
+ {},
+ {
+ ref: ref
+ }
+ )
+ );
+ }
+
+ var LoadableComponent = opts.suspense ? LazyImpl : LoadableImpl;
LoadableComponent.preload = function() {
- return init();
+ return !opts.suspense && init();
};
LoadableComponent.displayName = "LoadableComponent";
Webpack 4 Mode (Increase detected ⚠️ )
General Overall increase ⚠️
vercel/next.js canary | huozhi/next.js dynamic-lazy | Change | |
---|---|---|---|
buildDuration | 11.2s | 11.2s | |
buildDurationCached | 4.5s | 4.4s | -94ms |
nodeModulesSize | 49.1 MB | 49.1 MB |
Page Load Tests Overall increase ✓
vercel/next.js canary | huozhi/next.js dynamic-lazy | Change | |
---|---|---|---|
/ failed reqs | 0 | 0 | ✓ |
/ total time (seconds) | 2.515 | 2.489 | -0.03 |
/ avg req/sec | 994.14 | 1004.45 | +10.31 |
/error-in-render failed reqs | 0 | 0 | ✓ |
/error-in-render total time (seconds) | 1.384 | 1.355 | -0.03 |
/error-in-render avg req/sec | 1806.01 | 1844.56 | +38.55 |
Client Bundles (main, webpack, commons)
vercel/next.js canary | huozhi/next.js dynamic-lazy | Change | |
---|---|---|---|
17.HASH.js gzip | 185 B | 185 B | ✓ |
677f882d2ed8..HASH.js gzip | 14 kB | 14 kB | ✓ |
framework.HASH.js gzip | 41.9 kB | 41.9 kB | ✓ |
main-HASH.js gzip | 10.6 kB | 10.6 kB | ✓ |
webpack-HASH.js gzip | 1.19 kB | 1.19 kB | ✓ |
Overall change | 67.9 kB | 67.9 kB | ✓ |
Legacy Client Bundles (polyfills)
vercel/next.js canary | huozhi/next.js dynamic-lazy | Change | |
---|---|---|---|
polyfills-HASH.js gzip | 31.3 kB | 31.3 kB | ✓ |
Overall change | 31.3 kB | 31.3 kB | ✓ |
Client Pages Overall increase ⚠️
vercel/next.js canary | huozhi/next.js dynamic-lazy | Change | |
---|---|---|---|
_app-HASH.js gzip | 965 B | 965 B | ✓ |
_error-HASH.js gzip | 3.71 kB | 3.71 kB | ✓ |
amp-HASH.js gzip | 552 B | 552 B | ✓ |
css-HASH.js gzip | 333 B | 333 B | ✓ |
dynamic-HASH.js gzip | 2.71 kB | 2.83 kB | |
head-HASH.js gzip | 2.97 kB | 2.97 kB | ✓ |
hooks-HASH.js gzip | 911 B | 911 B | ✓ |
index-HASH.js gzip | 231 B | 231 B | ✓ |
link-HASH.js gzip | 1.64 kB | 1.64 kB | ✓ |
routerDirect..HASH.js gzip | 298 B | 298 B | ✓ |
script-HASH.js gzip | 2.95 kB | 2.95 kB | ✓ |
withRouter-HASH.js gzip | 294 B | 294 B | ✓ |
e025d2764813..52f.css gzip | 125 B | 125 B | ✓ |
Overall change | 17.7 kB | 17.8 kB |
Client Build Manifests
vercel/next.js canary | huozhi/next.js dynamic-lazy | Change | |
---|---|---|---|
_buildManifest.js gzip | 498 B | 498 B | ✓ |
Overall change | 498 B | 498 B | ✓ |
Rendered Page Sizes
vercel/next.js canary | huozhi/next.js dynamic-lazy | Change | |
---|---|---|---|
index.html gzip | 578 B | 578 B | ✓ |
link.html gzip | 591 B | 591 B | ✓ |
withRouter.html gzip | 570 B | 570 B | ✓ |
Overall change | 1.74 kB | 1.74 kB | ✓ |
Diffs
Diff for _buildManifest.js
@@ -10,7 +10,7 @@ self.__BUILD_MANIFEST = {
"static\u002Fchunks\u002Fpages\u002Fcss-0fc40b547d044664b6a0.js"
],
"/dynamic": [
- "static\u002Fchunks\u002Fpages\u002Fdynamic-cbc12d944fc8bf945134.js"
+ "static\u002Fchunks\u002Fpages\u002Fdynamic-97a9156f97752a73abfb.js"
],
"/head": ["static\u002Fchunks\u002Fpages\u002Fhead-abecfbe6a75f8cfa32c0.js"],
"/hooks": [
Diff for dynamic-HASH.js
@@ -4,12 +4,12 @@ _N_E = (window["webpackJsonp_N_E"] = window["webpackJsonp_N_E"] || []).push([
/***/ O4nQ: /***/ function(module, exports, __webpack_require__) {
"use strict";
- var _defineProperty = __webpack_require__("lSNA");
-
var _classCallCheck = __webpack_require__("lwsE");
var _createClass = __webpack_require__("W8MJ");
+ var _defineProperty = __webpack_require__("lSNA");
+
function ownKeys(object, enumerableOnly) {
var keys = Object.keys(object);
if (Object.getOwnPropertySymbols) {
@@ -178,10 +178,16 @@ _N_E = (window["webpackJsonp_N_E"] = window["webpackJsonp_N_E"] || []).push([
delay: 200,
timeout: null,
webpack: null,
- modules: null
+ modules: null,
+ suspense: false
},
options
);
+
+ if (opts.suspense) {
+ opts.lazy = _react["default"].lazy(opts.loader);
+ }
+
var subscription = null;
function init() {
@@ -205,7 +211,8 @@ _N_E = (window["webpackJsonp_N_E"] = window["webpackJsonp_N_E"] || []).push([
!initialized &&
true &&
typeof opts.webpack === "function" &&
- "function" === "function"
+ "function" === "function" &&
+ !opts.suspense
) {
var moduleIds = opts.webpack();
READY_INITIALIZERS.push(function(ids) {
@@ -228,7 +235,7 @@ _N_E = (window["webpackJsonp_N_E"] = window["webpackJsonp_N_E"] || []).push([
});
}
- var LoadableComponent = function LoadableComponent(props, ref) {
+ function LoadableImpl(props, ref) {
init();
var context = _react["default"].useContext(
@@ -274,10 +281,25 @@ _N_E = (window["webpackJsonp_N_E"] = window["webpackJsonp_N_E"] || []).push([
},
[props, state]
);
- };
+ }
+
+ function LazyImpl(props, ref) {
+ return _react["default"].createElement(
+ opts.lazy,
+ _objectSpread(
+ _objectSpread({}, props),
+ {},
+ {
+ ref: ref
+ }
+ )
+ );
+ }
+
+ var LoadableComponent = opts.suspense ? LazyImpl : LoadableImpl;
LoadableComponent.preload = function() {
- return init();
+ return !opts.suspense && init();
};
LoadableComponent.displayName = "LoadableComponent";
@@ -742,7 +764,24 @@ _N_E = (window["webpackJsonp_N_E"] = window["webpackJsonp_N_E"] || []).push([
loadableOptions = _objectSpread(
_objectSpread({}, loadableOptions),
options
- ); // coming from build/babel/plugins/react-loadable-plugin.js
+ );
+ var suspenseOptions = loadableOptions;
+
+ if (true) {
+ // Error if react root is not enabled and `suspense` option is set to true
+ if (true && suspenseOptions.suspense) {
+ // TODO: add error doc when this feature is stable
+ throw new Error(
+ "Disallowed suspense option usage with next/dynamic in blocking mode"
+ );
+ }
+
+ suspenseOptions.suspense = false;
+ }
+
+ if (suspenseOptions.suspense) {
+ return loadableFn(suspenseOptions);
+ } // coming from build/babel/plugins/react-loadable-plugin.js
if (loadableOptions.loadableGenerated) {
loadableOptions = _objectSpread(
@huozhi Thank you for your this feature. But there is a small problem with Typescript const MyComponent = dynamic(() => import('components/MyComponent'), {
susspense: true,
});
|
Changes
Feature
React.lazy
intonext/dynamic
, enable it whenoptions.suspense
istrue
next/dynamic
withsuspense=true
in SSR and SSGTests
reactRoot: true
+concurrentFeatures: false
reactRoot: true
+concurrentFeatures: true
Feature
fixes #number
contributing.md