diff --git a/packages/next-swc/crates/core/src/next_dynamic.rs b/packages/next-swc/crates/core/src/next_dynamic.rs index fef78a24f828..b8c2f7fdd120 100644 --- a/packages/next-swc/crates/core/src/next_dynamic.rs +++ b/packages/next-swc/crates/core/src/next_dynamic.rs @@ -223,6 +223,7 @@ impl Fold for NextDynamicPatcher { })))]; let mut has_ssr_false = false; + let mut has_suspense = false; if expr.args.len() == 2 { if let Expr::Object(ObjectLit { @@ -250,21 +251,29 @@ impl Fold for NextDynamicPatcher { if let Some(Lit::Bool(Bool { value: false, span: _, - })) = match &**value { - Expr::Lit(lit) => Some(lit), - _ => None, - } { + })) = value.as_lit() + { has_ssr_false = true } } + if sym == "suspense" { + if let Some(Lit::Bool(Bool { + value: true, + span: _, + })) = value.as_lit() + { + has_suspense = true + } + } } } } props.extend(options_props.iter().cloned()); } } - - if has_ssr_false && self.is_server { + // Don't need to strip the `loader` argument if suspense is true + // See https://github.com/vercel/next.js/issues/36636 for background + if has_ssr_false && !has_suspense && self.is_server { expr.args[0] = Lit::Null(Null { span: DUMMY_SP }).as_arg(); } diff --git a/packages/next-swc/crates/core/tests/fixture/next-dynamic/with-options/input.js b/packages/next-swc/crates/core/tests/fixture/next-dynamic/with-options/input.js index 82cb14f53566..c258d8fade56 100644 --- a/packages/next-swc/crates/core/tests/fixture/next-dynamic/with-options/input.js +++ b/packages/next-swc/crates/core/tests/fixture/next-dynamic/with-options/input.js @@ -9,3 +9,8 @@ const DynamicClientOnlyComponent = dynamic( () => import('../components/hello'), { ssr: false } ) + +const DynamicClientOnlyComponentWithSuspense = dynamic( + () => import('../components/hello'), + { ssr: false, suspense: true } +) diff --git a/packages/next-swc/crates/core/tests/fixture/next-dynamic/with-options/output-dev.js b/packages/next-swc/crates/core/tests/fixture/next-dynamic/with-options/output-dev.js index b0aba0caa23a..d4d144e1e364 100644 --- a/packages/next-swc/crates/core/tests/fixture/next-dynamic/with-options/output-dev.js +++ b/packages/next-swc/crates/core/tests/fixture/next-dynamic/with-options/output-dev.js @@ -17,3 +17,13 @@ const DynamicClientOnlyComponent = dynamic(()=>import('../components/hello') }, ssr: false }); +const DynamicClientOnlyComponentWithSuspense = dynamic(()=>import('../components/hello') +, { + loadableGenerated: { + modules: [ + "some-file.js -> " + "../components/hello" + ] + }, + ssr: false, + suspense: true +}); diff --git a/packages/next-swc/crates/core/tests/fixture/next-dynamic/with-options/output-prod.js b/packages/next-swc/crates/core/tests/fixture/next-dynamic/with-options/output-prod.js index 6ba8d24a9b45..d7e8a3ae55f2 100644 --- a/packages/next-swc/crates/core/tests/fixture/next-dynamic/with-options/output-prod.js +++ b/packages/next-swc/crates/core/tests/fixture/next-dynamic/with-options/output-prod.js @@ -17,3 +17,13 @@ const DynamicClientOnlyComponent = dynamic(()=>import('../components/hello') }, ssr: false }); +const DynamicClientOnlyComponentWithSuspense = dynamic(()=>import('../components/hello') +, { + loadableGenerated: { + webpack: ()=>[ + require.resolveWeak("../components/hello") + ] + }, + ssr: false, + suspense: true +}); diff --git a/packages/next-swc/crates/core/tests/fixture/next-dynamic/with-options/output-server.js b/packages/next-swc/crates/core/tests/fixture/next-dynamic/with-options/output-server.js index 453c19d3af24..fbaab8c10c03 100644 --- a/packages/next-swc/crates/core/tests/fixture/next-dynamic/with-options/output-server.js +++ b/packages/next-swc/crates/core/tests/fixture/next-dynamic/with-options/output-server.js @@ -16,3 +16,13 @@ const DynamicClientOnlyComponent = dynamic(null, { }, ssr: false }); +const DynamicClientOnlyComponentWithSuspense = dynamic(()=>import('../components/hello') +, { + loadableGenerated: { + modules: [ + "some-file.js -> " + "../components/hello" + ] + }, + ssr: false, + suspense: true +}); diff --git a/test/e2e/dynamic-with-suspense/index.test.ts b/test/e2e/dynamic-with-suspense/index.test.ts new file mode 100644 index 000000000000..b0f24fb96a1e --- /dev/null +++ b/test/e2e/dynamic-with-suspense/index.test.ts @@ -0,0 +1,55 @@ +import { createNext } from 'e2e-utils' +import { NextInstance } from 'test/lib/next-modes/base' +import { hasRedbox, renderViaHTTP } from 'next-test-utils' +import webdriver from 'next-webdriver' + +const suite = + process.env.NEXT_TEST_REACT_VERSION === '^17' ? describe.skip : describe + +// Skip the suspense test if react version is 17 +suite('dynamic with suspense', () => { + let next: NextInstance + + beforeAll(async () => { + next = await createNext({ + files: { + 'pages/index.js': ` + import { Suspense } from "react"; + import dynamic from "next/dynamic"; + + const Thing = dynamic(() => import("./thing"), { ssr: false, suspense: true }); + + export default function IndexPage() { + return ( +
+

Next.js Example

+ + + +
+ ); + } + `, + 'pages/thing.js': ` + export default function Thing() { + return "Thing"; + } + `, + }, + dependencies: {}, + }) + }) + afterAll(() => next.destroy()) + + it('should render server-side', async () => { + const html = await renderViaHTTP(next.url, '/') + expect(html).toContain('Next.js Example') + expect(html).toContain('Thing') + }) + + it('should render client-side', async () => { + const browser = await webdriver(next.url, '/') + expect(await hasRedbox(browser)).toBe(false) + await browser.close() + }) +})