Add support for "type": "module" in package.json (#33637)
- [x] Add failing test for development / production - [x] Add failing test for client-side JavaScript - [x] Write `.next/package.json` with `"type": "commonjs" - [x] Fix issue with client-side JavaScript showing `module` is not defined Production works after these changes. Development breaks on module not existing because of the Fast Refresh loader. Working with @sokra to add alternatives to what is being used in the loader to webpack so that it can be updated. Fixes #23029, Fixes #24334 ## Bug - [x] Related issues linked using `fixes #number` - [x] Integration tests added - [x] 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`
This commit is contained in:
parent
127f94dc13
commit
62b1704e41
5 changed files with 92 additions and 13 deletions
|
@ -532,6 +532,13 @@ export default async function build(
|
|||
await recursiveDelete(distDir, /^cache/)
|
||||
}
|
||||
|
||||
// Ensure commonjs handling is used for files in the distDir (generally .next)
|
||||
// Files outside of the distDir can be "type": "module"
|
||||
await promises.writeFile(
|
||||
path.join(distDir, 'package.json'),
|
||||
'{"type": "commonjs"}'
|
||||
)
|
||||
|
||||
// We need to write the manifest with rewrites before build
|
||||
// so serverless can import the manifest
|
||||
await nextBuildSpan
|
||||
|
|
|
@ -41,6 +41,7 @@ import { DecodeError } from '../../shared/lib/utils'
|
|||
import { Span, trace } from '../../trace'
|
||||
import { getProperError } from '../../lib/is-error'
|
||||
import ws from 'next/dist/compiled/ws'
|
||||
import { promises as fs } from 'fs'
|
||||
|
||||
const wsServer = new ws.Server({ noServer: true })
|
||||
|
||||
|
@ -432,6 +433,15 @@ export default class HotReloader {
|
|||
startSpan.stop() // Stop immediately to create an artificial parent span
|
||||
|
||||
await this.clean(startSpan)
|
||||
// Ensure distDir exists before writing package.json
|
||||
await fs.mkdir(this.config.distDir, { recursive: true })
|
||||
|
||||
// Ensure commonjs handling is used for files in the distDir (generally .next)
|
||||
// Files outside of the distDir can be "type": "module"
|
||||
await fs.writeFile(
|
||||
join(this.config.distDir, 'package.json'),
|
||||
'{"type": "commonjs"}'
|
||||
)
|
||||
|
||||
const configs = await this.getWebpackConfig(startSpan)
|
||||
|
||||
|
|
|
@ -3,9 +3,9 @@ import { RefreshRuntimeGlobals } from '../runtime'
|
|||
declare const self: Window & RefreshRuntimeGlobals
|
||||
|
||||
type Dictionary = { [key: string]: unknown }
|
||||
declare const module: {
|
||||
declare const __webpack_module__: {
|
||||
id: string
|
||||
__proto__: { exports: unknown }
|
||||
exports: unknown
|
||||
hot: {
|
||||
accept: () => void
|
||||
dispose: (onDispose: (data: Dictionary) => void) => void
|
||||
|
@ -27,14 +27,16 @@ export default function () {
|
|||
// AMP / No-JS mode does not inject these helpers:
|
||||
'$RefreshHelpers$' in self
|
||||
) {
|
||||
var currentExports = module.__proto__.exports
|
||||
var prevExports = module.hot.data?.prevExports ?? null
|
||||
// @ts-ignore __webpack_module__ is global
|
||||
var currentExports = __webpack_module__.exports
|
||||
// @ts-ignore __webpack_module__ is global
|
||||
var prevExports = __webpack_module__.hot.data?.prevExports ?? null
|
||||
|
||||
// This cannot happen in MainTemplate because the exports mismatch between
|
||||
// templating and execution.
|
||||
self.$RefreshHelpers$.registerExportsForReactRefresh(
|
||||
currentExports,
|
||||
module.id
|
||||
__webpack_module__.id
|
||||
)
|
||||
|
||||
// A module can be accepted automatically based on its exports, e.g. when
|
||||
|
@ -42,12 +44,13 @@ export default function () {
|
|||
if (self.$RefreshHelpers$.isReactRefreshBoundary(currentExports)) {
|
||||
// Save the previous exports on update so we can compare the boundary
|
||||
// signatures.
|
||||
module.hot.dispose(function (data) {
|
||||
__webpack_module__.hot.dispose(function (data) {
|
||||
data.prevExports = currentExports
|
||||
})
|
||||
// Unconditionally accept an update to this module, we'll check if it's
|
||||
// still a Refresh Boundary later.
|
||||
module.hot.accept()
|
||||
// @ts-ignore importMeta is replaced in the loader
|
||||
global.importMeta.webpackHot.accept()
|
||||
|
||||
// This field is set when the previous version of this module was a
|
||||
// Refresh Boundary, letting us know we need to check for invalidation or
|
||||
|
@ -66,7 +69,7 @@ export default function () {
|
|||
currentExports
|
||||
)
|
||||
) {
|
||||
module.hot.invalidate()
|
||||
__webpack_module__.hot.invalidate()
|
||||
} else {
|
||||
self.$RefreshHelpers$.scheduleUpdate()
|
||||
}
|
||||
|
@ -78,7 +81,7 @@ export default function () {
|
|||
// because we already accepted this update (accidental side effect).
|
||||
var isNoLongerABoundary = prevExports !== null
|
||||
if (isNoLongerABoundary) {
|
||||
module.hot.invalidate()
|
||||
__webpack_module__.hot.invalidate()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,16 +2,33 @@ import type { LoaderDefinition } from 'webpack'
|
|||
import RefreshModuleRuntime from './internal/ReactRefreshModule.runtime'
|
||||
|
||||
let refreshModuleRuntime = RefreshModuleRuntime.toString()
|
||||
refreshModuleRuntime = refreshModuleRuntime.slice(
|
||||
refreshModuleRuntime.indexOf('{') + 1,
|
||||
refreshModuleRuntime.lastIndexOf('}')
|
||||
refreshModuleRuntime = refreshModuleRuntime
|
||||
.slice(
|
||||
refreshModuleRuntime.indexOf('{') + 1,
|
||||
refreshModuleRuntime.lastIndexOf('}')
|
||||
)
|
||||
// Given that the import above executes the module we need to make sure it does not crash on `import.meta` not being allowed.
|
||||
.replace('global.importMeta', 'import.meta')
|
||||
|
||||
let commonJsrefreshModuleRuntime = refreshModuleRuntime.replace(
|
||||
'import.meta.webpackHot',
|
||||
'module.hot'
|
||||
)
|
||||
|
||||
const ReactRefreshLoader: LoaderDefinition = function ReactRefreshLoader(
|
||||
source,
|
||||
inputSourceMap
|
||||
) {
|
||||
this.callback(null, `${source}\n\n;${refreshModuleRuntime}`, inputSourceMap)
|
||||
this.callback(
|
||||
null,
|
||||
`${source}\n\n;${
|
||||
// Account for commonjs not supporting `import.meta
|
||||
this.resourcePath.endsWith('.cjs')
|
||||
? commonJsrefreshModuleRuntime
|
||||
: refreshModuleRuntime
|
||||
}`,
|
||||
inputSourceMap
|
||||
)
|
||||
}
|
||||
|
||||
export default ReactRefreshLoader
|
||||
|
|
42
test/e2e/type-module-interop/index.test.ts
Normal file
42
test/e2e/type-module-interop/index.test.ts
Normal file
|
@ -0,0 +1,42 @@
|
|||
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('Type module interop', () => {
|
||||
let next: NextInstance
|
||||
|
||||
beforeAll(async () => {
|
||||
next = await createNext({
|
||||
files: {
|
||||
'pages/index.js': `
|
||||
export default function Page() {
|
||||
return <p>hello world</p>
|
||||
}
|
||||
`,
|
||||
},
|
||||
dependencies: {},
|
||||
})
|
||||
const contents = await next.readFile('package.json')
|
||||
const pkg = JSON.parse(contents)
|
||||
await next.patchFile(
|
||||
'package.json',
|
||||
JSON.stringify({
|
||||
...pkg,
|
||||
type: 'module',
|
||||
})
|
||||
)
|
||||
})
|
||||
afterAll(() => next.destroy())
|
||||
|
||||
it('should render server-side', async () => {
|
||||
const html = await renderViaHTTP(next.url, '/')
|
||||
expect(html).toContain('hello world')
|
||||
})
|
||||
|
||||
it('should render client-side', async () => {
|
||||
const browser = await webdriver(next.url, '/')
|
||||
expect(await hasRedbox(browser)).toBe(false)
|
||||
await browser.close()
|
||||
})
|
||||
})
|
Loading…
Reference in a new issue