Skip to content

Commit c7f2c63

Browse files
authored
Fix SWC dynamic transform with suspense but without ssr (#36825)
The Babel plugin works fine, so it seems not a runtime issue. fixes #36636
1 parent 31a538d commit c7f2c63

File tree

6 files changed

+105
-6
lines changed

6 files changed

+105
-6
lines changed

packages/next-swc/crates/core/src/next_dynamic.rs

+15-6
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,7 @@ impl Fold for NextDynamicPatcher {
223223
})))];
224224

225225
let mut has_ssr_false = false;
226+
let mut has_suspense = false;
226227

227228
if expr.args.len() == 2 {
228229
if let Expr::Object(ObjectLit {
@@ -250,21 +251,29 @@ impl Fold for NextDynamicPatcher {
250251
if let Some(Lit::Bool(Bool {
251252
value: false,
252253
span: _,
253-
})) = match &**value {
254-
Expr::Lit(lit) => Some(lit),
255-
_ => None,
256-
} {
254+
})) = value.as_lit()
255+
{
257256
has_ssr_false = true
258257
}
259258
}
259+
if sym == "suspense" {
260+
if let Some(Lit::Bool(Bool {
261+
value: true,
262+
span: _,
263+
})) = value.as_lit()
264+
{
265+
has_suspense = true
266+
}
267+
}
260268
}
261269
}
262270
}
263271
props.extend(options_props.iter().cloned());
264272
}
265273
}
266-
267-
if has_ssr_false && self.is_server {
274+
// Don't need to strip the `loader` argument if suspense is true
275+
// See https://github.com/vercel/next.js/issues/36636 for background
276+
if has_ssr_false && !has_suspense && self.is_server {
268277
expr.args[0] = Lit::Null(Null { span: DUMMY_SP }).as_arg();
269278
}
270279

packages/next-swc/crates/core/tests/fixture/next-dynamic/with-options/input.js

+5
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,8 @@ const DynamicClientOnlyComponent = dynamic(
99
() => import('../components/hello'),
1010
{ ssr: false }
1111
)
12+
13+
const DynamicClientOnlyComponentWithSuspense = dynamic(
14+
() => import('../components/hello'),
15+
{ ssr: false, suspense: true }
16+
)

packages/next-swc/crates/core/tests/fixture/next-dynamic/with-options/output-dev.js

+10
Original file line numberDiff line numberDiff line change
@@ -17,3 +17,13 @@ const DynamicClientOnlyComponent = dynamic(()=>import('../components/hello')
1717
},
1818
ssr: false
1919
});
20+
const DynamicClientOnlyComponentWithSuspense = dynamic(()=>import('../components/hello')
21+
, {
22+
loadableGenerated: {
23+
modules: [
24+
"some-file.js -> " + "../components/hello"
25+
]
26+
},
27+
ssr: false,
28+
suspense: true
29+
});

packages/next-swc/crates/core/tests/fixture/next-dynamic/with-options/output-prod.js

+10
Original file line numberDiff line numberDiff line change
@@ -17,3 +17,13 @@ const DynamicClientOnlyComponent = dynamic(()=>import('../components/hello')
1717
},
1818
ssr: false
1919
});
20+
const DynamicClientOnlyComponentWithSuspense = dynamic(()=>import('../components/hello')
21+
, {
22+
loadableGenerated: {
23+
webpack: ()=>[
24+
require.resolveWeak("../components/hello")
25+
]
26+
},
27+
ssr: false,
28+
suspense: true
29+
});

packages/next-swc/crates/core/tests/fixture/next-dynamic/with-options/output-server.js

+10
Original file line numberDiff line numberDiff line change
@@ -16,3 +16,13 @@ const DynamicClientOnlyComponent = dynamic(null, {
1616
},
1717
ssr: false
1818
});
19+
const DynamicClientOnlyComponentWithSuspense = dynamic(()=>import('../components/hello')
20+
, {
21+
loadableGenerated: {
22+
modules: [
23+
"some-file.js -> " + "../components/hello"
24+
]
25+
},
26+
ssr: false,
27+
suspense: true
28+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import { createNext } from 'e2e-utils'
2+
import { NextInstance } from 'test/lib/next-modes/base'
3+
import { hasRedbox, renderViaHTTP } from 'next-test-utils'
4+
import webdriver from 'next-webdriver'
5+
6+
const suite =
7+
process.env.NEXT_TEST_REACT_VERSION === '^17' ? describe.skip : describe
8+
9+
// Skip the suspense test if react version is 17
10+
suite('dynamic with suspense', () => {
11+
let next: NextInstance
12+
13+
beforeAll(async () => {
14+
next = await createNext({
15+
files: {
16+
'pages/index.js': `
17+
import { Suspense } from "react";
18+
import dynamic from "next/dynamic";
19+
20+
const Thing = dynamic(() => import("./thing"), { ssr: false, suspense: true });
21+
22+
export default function IndexPage() {
23+
return (
24+
<div>
25+
<p>Next.js Example</p>
26+
<Suspense fallback="Loading...">
27+
<Thing />
28+
</Suspense>
29+
</div>
30+
);
31+
}
32+
`,
33+
'pages/thing.js': `
34+
export default function Thing() {
35+
return "Thing";
36+
}
37+
`,
38+
},
39+
dependencies: {},
40+
})
41+
})
42+
afterAll(() => next.destroy())
43+
44+
it('should render server-side', async () => {
45+
const html = await renderViaHTTP(next.url, '/')
46+
expect(html).toContain('Next.js Example')
47+
expect(html).toContain('Thing')
48+
})
49+
50+
it('should render client-side', async () => {
51+
const browser = await webdriver(next.url, '/')
52+
expect(await hasRedbox(browser)).toBe(false)
53+
await browser.close()
54+
})
55+
})

0 commit comments

Comments
 (0)