Skip to content

Commit 325968d

Browse files
authored
fix: apply type: module only to runtime modules (#2549)
* test: add integration and e2e test for requiring CJS .js module in serverless runtime * fix: apply type: module only to runtime modules
1 parent 663d6dd commit 325968d

File tree

8 files changed

+71
-12
lines changed

8 files changed

+71
-12
lines changed

src/build/content/server.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -260,9 +260,9 @@ export const copyNextDependencies = async (ctx: PluginContext): Promise<void> =>
260260
await tracer.withActiveSpan('copyNextDependencies', async () => {
261261
const entries = await readdir(ctx.standaloneDir)
262262
const promises: Promise<void>[] = entries.map(async (entry) => {
263-
// copy all except the package.json and distDir (.next) folder as this is handled in a separate function
263+
// copy all except the distDir (.next) folder as this is handled in a separate function
264264
// this will include the node_modules folder as well
265-
if (entry === 'package.json' || entry === ctx.nextDistDir) {
265+
if (entry === ctx.nextDistDir) {
266266
return
267267
}
268268
const src = join(ctx.standaloneDir, entry)

src/build/functions/server.ts

+11-10
Original file line numberDiff line numberDiff line change
@@ -53,11 +53,20 @@ const copyHandlerDependencies = async (ctx: PluginContext) => {
5353
)
5454
}
5555

56+
// We need to create a package.json file with type: module to make sure that the runtime modules
57+
// are handled correctly as ESM modules
58+
promises.push(
59+
writeFile(
60+
join(ctx.serverHandlerRuntimeModulesDir, 'package.json'),
61+
JSON.stringify({ type: 'module' }),
62+
),
63+
)
64+
5665
const fileList = await glob('dist/**/*', { cwd: ctx.pluginDir })
5766

5867
for (const filePath of fileList) {
5968
promises.push(
60-
cp(join(ctx.pluginDir, filePath), join(ctx.serverHandlerDir, '.netlify', filePath), {
69+
cp(join(ctx.pluginDir, filePath), join(ctx.serverHandlerRuntimeModulesDir, filePath), {
6170
recursive: true,
6271
force: true,
6372
}),
@@ -85,13 +94,6 @@ const writeHandlerManifest = async (ctx: PluginContext) => {
8594
)
8695
}
8796

88-
const writePackageMetadata = async (ctx: PluginContext) => {
89-
await writeFile(
90-
join(ctx.serverHandlerRootDir, 'package.json'),
91-
JSON.stringify({ type: 'module' }),
92-
)
93-
}
94-
9597
const applyTemplateVariables = (template: string, variables: Record<string, string>) => {
9698
return Object.entries(variables).reduce((acc, [key, value]) => {
9799
return acc.replaceAll(key, value)
@@ -136,13 +138,12 @@ export const clearStaleServerHandlers = async (ctx: PluginContext) => {
136138
*/
137139
export const createServerHandler = async (ctx: PluginContext) => {
138140
await tracer.withActiveSpan('createServerHandler', async () => {
139-
await mkdir(join(ctx.serverHandlerDir, '.netlify'), { recursive: true })
141+
await mkdir(join(ctx.serverHandlerRuntimeModulesDir), { recursive: true })
140142

141143
await copyNextServerCode(ctx)
142144
await copyNextDependencies(ctx)
143145
await copyHandlerDependencies(ctx)
144146
await writeHandlerManifest(ctx)
145-
await writePackageMetadata(ctx)
146147
await writeHandlerFile(ctx)
147148

148149
await verifyHandlerDirStructure(ctx)

src/build/plugin-context.ts

+4
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,10 @@ export class PluginContext {
182182
return join(this.serverHandlerRootDir, this.distDirParent)
183183
}
184184

185+
get serverHandlerRuntimeModulesDir(): string {
186+
return join(this.serverHandlerDir, '.netlify')
187+
}
188+
185189
get nextServerHandler(): string {
186190
if (this.relativeAppDir.length !== 0) {
187191
return join(this.lambdaWorkingDirectory, '.netlify/dist/run/handlers/server.js')

tests/e2e/simple-app.test.ts

+11
Original file line numberDiff line numberDiff line change
@@ -252,3 +252,14 @@ test('Compressed rewrites are readable', async ({ simple }) => {
252252
expect(resp.headers.get('content-encoding')).toEqual('br')
253253
expect(await resp.text()).toContain('<title>Example Domain</title>')
254254
})
255+
256+
test('can require CJS module that is not bundled', async ({ simple }) => {
257+
const resp = await fetch(`${simple.url}/api/cjs-file-with-js-extension`)
258+
259+
expect(resp.status).toBe(200)
260+
261+
const parsedBody = await resp.json()
262+
263+
expect(parsedBody.notBundledCJSModule.isBundled).toEqual(false)
264+
expect(parsedBody.bundledCJSModule.isBundled).toEqual(true)
265+
})
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
const { parse: pathParse } = require('node:path')
2+
3+
const fileBase = pathParse(__filename).base
4+
5+
module.exports = {
6+
fileBase,
7+
// if fileBase is not the same as this module name, it was bundled
8+
isBundled: fileBase !== 'bundled.cjs',
9+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { NextResponse } from 'next/server'
2+
import { resolve } from 'node:path'
3+
4+
export async function GET() {
5+
return NextResponse.json({
6+
notBundledCJSModule: __non_webpack_require__(resolve('./cjs-file-with-js-extension.js')),
7+
bundledCJSModule: require('./bundled.cjs'),
8+
})
9+
}
10+
11+
export const dynamic = 'force-dynamic'
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
const { parse: pathParse } = require('node:path')
2+
3+
const fileBase = pathParse(__filename).base
4+
5+
module.exports = {
6+
fileBase,
7+
// if fileBase is not the same as this module name, it was bundled
8+
isBundled: fileBase !== 'cjs-file-with-js-extension.js',
9+
}

tests/integration/simple-app.test.ts

+14
Original file line numberDiff line numberDiff line change
@@ -245,6 +245,20 @@ test.skipIf(process.env.NEXT_VERSION !== 'canary')<FixtureTestContext>(
245245
},
246246
)
247247

248+
test<FixtureTestContext>('can require CJS module that is not bundled', async (ctx) => {
249+
await createFixture('simple', ctx)
250+
await runPlugin(ctx)
251+
252+
const response = await invokeFunction(ctx, { url: '/api/cjs-file-with-js-extension' })
253+
254+
expect(response.statusCode).toBe(200)
255+
256+
const parsedBody = JSON.parse(response.body)
257+
258+
expect(parsedBody.notBundledCJSModule.isBundled).toEqual(false)
259+
expect(parsedBody.bundledCJSModule.isBundled).toEqual(true)
260+
})
261+
248262
describe('next patching', async () => {
249263
const { cp: originalCp, appendFile } = (await vi.importActual(
250264
'node:fs/promises',

0 commit comments

Comments
 (0)