Skip to content

Commit da6f271

Browse files
authored
Interpolate module.exports as default import (#36082)
fixes #34412 ## Bug - [x] Related issues linked using `fixes #number` - [ ] 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 a9d6d9f commit da6f271

File tree

8 files changed

+77
-12
lines changed

8 files changed

+77
-12
lines changed

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

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ import { promisify } from 'util'
1010
import { parse } from '../../swc'
1111
import { buildExports } from './utils'
1212

13+
const IS_NEXT_CLIENT_BUILT_IN = /[\\/]next[\\/](link|image)\.js$/
14+
1315
function addExportNames(names: string[], node: any) {
1416
if (!node) return
1517
switch (node.type) {
@@ -57,7 +59,7 @@ async function collectExports(
5759
const names: string[] = []
5860

5961
// Next.js built-in client components
60-
if (/[\\/]next[\\/](link|image)\.js$/.test(resourcePath)) {
62+
if (IS_NEXT_CLIENT_BUILT_IN.test(resourcePath)) {
6163
names.push('default')
6264
}
6365

@@ -158,12 +160,14 @@ export default async function transformSource(
158160
const moduleRefDef =
159161
"const MODULE_REFERENCE = Symbol.for('react.module.reference');\n"
160162

163+
const isNextClientBuiltIn = IS_NEXT_CLIENT_BUILT_IN.test(resourcePath)
164+
161165
const clientRefsExports = names.reduce((res: any, name) => {
162166
const moduleRef =
163167
'{ $$typeof: MODULE_REFERENCE, filepath: ' +
164168
JSON.stringify(resourcePath) +
165169
', name: ' +
166-
JSON.stringify(name) +
170+
JSON.stringify(name === 'default' && isNextClientBuiltIn ? '' : name) +
167171
' };\n'
168172
res[name] = moduleRef
169173
return res

packages/next/lib/eslint/runLintCheck.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,12 @@ function isValidSeverity(severity: string): severity is Severity {
3434
}
3535

3636
const requiredPackages = [
37-
{ file: 'eslint', pkg: 'eslint' },
38-
{ file: 'eslint-config-next', pkg: 'eslint-config-next' },
37+
{ file: 'eslint', pkg: 'eslint', exportsRestrict: false },
38+
{
39+
file: 'eslint-config-next',
40+
pkg: 'eslint-config-next',
41+
exportsRestrict: false,
42+
},
3943
]
4044

4145
async function cliPrompt() {

packages/next/lib/has-necessary-dependencies.ts

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
1+
import { existsSync } from 'fs'
2+
import { join, relative } from 'path'
3+
14
export interface MissingDependency {
25
file: string
36
pkg: string
7+
exportsRestrict: boolean
48
}
59

610
export type NecessaryDependencies = {
@@ -15,7 +19,24 @@ export async function hasNecessaryDependencies(
1519
let resolutions = new Map<string, string>()
1620
const missingPackages = requiredPackages.filter((p) => {
1721
try {
18-
resolutions.set(p.pkg, require.resolve(p.file, { paths: [baseDir] }))
22+
if (p.exportsRestrict) {
23+
const pkgPath = require.resolve(`${p.pkg}/package.json`, {
24+
paths: [baseDir],
25+
})
26+
const fileNameToVerify = relative(p.pkg, p.file)
27+
if (fileNameToVerify) {
28+
const fileToVerify = join(pkgPath, '..', fileNameToVerify)
29+
if (existsSync(fileToVerify)) {
30+
resolutions.set(p.pkg, join(pkgPath, '..'))
31+
} else {
32+
return true
33+
}
34+
} else {
35+
resolutions.set(p.pkg, pkgPath)
36+
}
37+
} else {
38+
resolutions.set(p.pkg, require.resolve(p.file, { paths: [baseDir] }))
39+
}
1940
return false
2041
} catch (_) {
2142
return true

packages/next/lib/verify-partytown-setup.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,13 @@ export async function verifyPartytownSetup(
6161
try {
6262
const partytownDeps: NecessaryDependencies = await hasNecessaryDependencies(
6363
dir,
64-
[{ file: '@builder.io/partytown', pkg: '@builder.io/partytown' }]
64+
[
65+
{
66+
file: '@builder.io/partytown',
67+
pkg: '@builder.io/partytown',
68+
exportsRestrict: false,
69+
},
70+
]
6571
)
6672

6773
if (partytownDeps.missing?.length > 0) {

packages/next/lib/verifyTypeScriptSetup.ts

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,17 @@ import { missingDepsError } from './typescript/missingDependencyError'
1717
import { NextConfigComplete } from '../server/config-shared'
1818

1919
const requiredPackages = [
20-
{ file: 'typescript', pkg: 'typescript' },
21-
{ file: '@types/react/index.d.ts', pkg: '@types/react' },
22-
{ file: '@types/node/index.d.ts', pkg: '@types/node' },
20+
{ file: 'typescript', pkg: 'typescript', exportsRestrict: false },
21+
{
22+
file: '@types/react/index.d.ts',
23+
pkg: '@types/react',
24+
exportsRestrict: true,
25+
},
26+
{
27+
file: '@types/node/index.d.ts',
28+
pkg: '@types/node',
29+
exportsRestrict: false,
30+
},
2331
]
2432

2533
export async function verifyTypeScriptSetup(

packages/next/taskfile-swc.js

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,11 @@ module.exports = function (task) {
1414
function* (
1515
file,
1616
serverOrClient,
17-
{ stripExtension, keepImportAssertions = false } = {}
17+
{
18+
stripExtension,
19+
keepImportAssertions = false,
20+
interopClientDefaultExport = false,
21+
} = {}
1822
) {
1923
// Don't compile .d.ts
2024
if (file.base.endsWith('.d.ts')) return
@@ -111,6 +115,15 @@ module.exports = function (task) {
111115
}
112116

113117
if (output.map) {
118+
if (interopClientDefaultExport) {
119+
output.code += `
120+
if (typeof exports.default === 'function' || (typeof exports.default === 'object' && exports.default !== null)) {
121+
Object.assign(exports.default, exports);
122+
module.exports = exports.default;
123+
}
124+
`
125+
}
126+
114127
const map = `${file.base}.map`
115128

116129
output.code += Buffer.from(`\n//# sourceMappingURL=${map}`)

packages/next/taskfile.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1826,7 +1826,7 @@ export async function nextbuild(task, opts) {
18261826
export async function client(task, opts) {
18271827
await task
18281828
.source(opts.src || 'client/**/*.+(js|ts|tsx)')
1829-
.swc('client', { dev: opts.dev })
1829+
.swc('client', { dev: opts.dev, interopClientDefaultExport: true })
18301830
.target('dist/client')
18311831
notify('Compiled client files')
18321832
}

test/e2e/type-module-interop/index.test.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,17 @@ describe('Type module interop', () => {
1111
next = await createNext({
1212
files: {
1313
'pages/index.js': `
14+
import Link from 'next/link'
15+
1416
export default function Page() {
15-
return <p>hello world</p>
17+
return (
18+
<>
19+
<p>hello world</p>
20+
<Link href="/modules">
21+
<a id="link-to-module">link to module</a>
22+
</Link>
23+
</>
24+
)
1625
}
1726
`,
1827
'pages/modules.jsx': `

0 commit comments

Comments
 (0)