Skip to content

Commit b7fb315

Browse files
authored
Ensure that external modules are not bundled into the client for RSC (vercel#31968)
If importing an external module inside a Server Component (`.server.js`), it shouldn't be bundled into the client. Only client components should be kept. ## Bug - [ ] Related issues linked using `fixes #number` - [x] Integration tests added - [ ] Errors have helpful link attached, see `contributing.md` ## Feature - [ ] Implements an existing feature request or RFC. Make sure the feature request has been accepted for implementation before opening a PR. - [ ] Related issues linked using `fixes #number` - [ ] Integration tests added - [ ] Documentation added - [ ] Telemetry added. In case of a feature if it's used or not. - [ ] Errors have helpful link attached, see `contributing.md` ## Documentation / Examples - [ ] Make sure the linting passes by running `yarn lint`
1 parent a2fa637 commit b7fb315

File tree

4 files changed

+42
-3
lines changed

4 files changed

+42
-3
lines changed

packages/next/build/webpack/loaders/next-flight-server-loader.ts

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,12 @@ function isClientComponent(importSource: string, pageExtensions: string[]) {
77
)
88
}
99

10+
function isServerComponent(importSource: string, pageExtensions: string[]) {
11+
return new RegExp(`\\.server(\\.(${pageExtensions.join('|')}))?`).test(
12+
importSource
13+
)
14+
}
15+
1016
function isNextComponent(importSource: string) {
1117
return (
1218
importSource.includes('next/link') || importSource.includes('next/image')
@@ -39,11 +45,8 @@ async function parseImportsInfo(
3945
const node = body[i]
4046
switch (node.type) {
4147
case 'ImportDeclaration':
42-
// When importing from a server component, ignore
4348
const importSource = node.source.value
4449

45-
// For the client compilation, we have to always import the component to
46-
// ensure that all dependencies are tracked.
4750
if (!isClientCompilation) {
4851
if (
4952
!(
@@ -59,6 +62,22 @@ async function parseImportsInfo(
5962
node.source.start - lastIndex
6063
)
6164
transformedSource += JSON.stringify(`${node.source.value}?flight`)
65+
} else {
66+
// For the client compilation, we skip all modules imports but
67+
// always keep client components in the bundle. All client components
68+
// have to be imported from either server or client components.
69+
if (
70+
!(
71+
isClientComponent(importSource, pageExtensions) ||
72+
isServerComponent(importSource, pageExtensions) ||
73+
// Special cases for Next.js APIs that are considered as client
74+
// components:
75+
isNextComponent(importSource) ||
76+
isImageImport(importSource)
77+
)
78+
) {
79+
continue
80+
}
6281
}
6382

6483
lastIndex = node.source.end

test/integration/react-streaming-and-server-components/app/package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,8 @@
55
"dev": "yarn lnext dev",
66
"build": "yarn lnext build",
77
"start": "yarn lnext start"
8+
},
9+
"dependencies": {
10+
"moment": "*"
811
}
912
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import moment from 'moment'
2+
3+
export default function Page() {
4+
return <div>date:{moment().toString()}</div>
5+
}

test/integration/react-streaming-and-server-components/test/index.test.js

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,18 @@ describe('concurrentFeatures - dev', () => {
182182
expect(content).toMatchInlineSnapshot('"foo.client"')
183183
})
184184

185+
it('should not bundle external imports into client builds for RSC', async () => {
186+
const html = await renderViaHTTP(context.appPort, '/external-imports')
187+
expect(html).toContain('date:')
188+
189+
const distServerDir = join(distDir, 'static', 'chunks', 'pages')
190+
const bundle = fs
191+
.readFileSync(join(distServerDir, 'external-imports.js'))
192+
.toString()
193+
194+
expect(bundle).not.toContain('moment')
195+
})
196+
185197
runBasicTests(context, 'dev')
186198
})
187199

0 commit comments

Comments
 (0)