From 0fdfb7ad15176c342821443dd147c0c0b4343d54 Mon Sep 17 00:00:00 2001 From: LongYinan Date: Wed, 11 May 2022 13:57:26 +0800 Subject: [PATCH] Fix SWC dynamic transform with suspense but without ssr --- .../next-swc/crates/core/src/next_dynamic.rs | 21 +++++--- .../next-dynamic/with-options/input.js | 5 ++ .../next-dynamic/with-options/output-dev.js | 10 ++++ .../next-dynamic/with-options/output-prod.js | 10 ++++ .../with-options/output-server.js | 10 ++++ test/e2e/dynamic-with-suspense/index.test.ts | 51 +++++++++++++++++++ 6 files changed, 101 insertions(+), 6 deletions(-) create mode 100644 test/e2e/dynamic-with-suspense/index.test.ts diff --git a/packages/next-swc/crates/core/src/next_dynamic.rs b/packages/next-swc/crates/core/src/next_dynamic.rs index 4d45798a20a6..b41bb91a24ba 100644 --- a/packages/next-swc/crates/core/src/next_dynamic.rs +++ b/packages/next-swc/crates/core/src/next_dynamic.rs @@ -226,6 +226,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 { @@ -253,21 +254,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 first 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..d5f6ee50cdab --- /dev/null +++ b/test/e2e/dynamic-with-suspense/index.test.ts @@ -0,0 +1,51 @@ +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' + +describe('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() + }) +})