From 147a24e320b64991c415a955ccc1429d9e313f98 Mon Sep 17 00:00:00 2001 From: Damien Simonin Feugas Date: Thu, 4 Aug 2022 15:47:28 +0200 Subject: [PATCH] fix: buffer is not usable on edge runtime (#39227) * fix: buffer is not usable on edge runtime * chore: improves implementation to allow any fallbacks --- .../webpack/plugins/middleware-plugin.ts | 11 +- .../test/index.test.js | 118 ++++++++++++------ .../test/index.test.ts | 14 +-- 3 files changed, 97 insertions(+), 46 deletions(-) diff --git a/packages/next/build/webpack/plugins/middleware-plugin.ts b/packages/next/build/webpack/plugins/middleware-plugin.ts index ebdd8a173a352..f688d4e32e9df 100644 --- a/packages/next/build/webpack/plugins/middleware-plugin.ts +++ b/packages/next/build/webpack/plugins/middleware-plugin.ts @@ -103,13 +103,22 @@ export default class MiddlewarePlugin { export async function handleWebpackExtenalForEdgeRuntime({ request, + context, contextInfo, + getResolve, }: { request: string + context: string contextInfo: any + getResolve: () => any }) { if (contextInfo.issuerLayer === 'middleware' && isNodeJsModule(request)) { - return `root globalThis.__import_unsupported('${request}')` + // allows user to provide and use their polyfills, as we do with buffer. + try { + await getResolve()(context, request) + } catch { + return `root globalThis.__import_unsupported('${request}')` + } } } diff --git a/test/integration/edge-runtime-module-errors/test/index.test.js b/test/integration/edge-runtime-module-errors/test/index.test.js index f883e8476abc1..2906fe6ce0a3d 100644 --- a/test/integration/edge-runtime-module-errors/test/index.test.js +++ b/test/integration/edge-runtime-module-errors/test/index.test.js @@ -490,26 +490,50 @@ describe('Edge runtime code with imports', () => { }) }) - describe('Edge API importing vanilla 3rd party module', () => { + describe.each([ + { + title: 'Edge API', + url: routeUrl, + init(importStatement) { + context.api.write(` + ${importStatement} + export default async function handler(request) { + const response = Response.json({ ok: true }) + response.headers.set('x-from-runtime', nanoid()) + return response + } + + export const config = { runtime: 'experimental-edge' } + `) + }, + }, + { + title: 'Middleware', + url: middlewareUrl, + init(importStatement) { + context.middleware.write(` + import { NextResponse } from 'next/server' + ${importStatement} + + export async function middleware(request) { + const response = NextResponse.next() + response.headers.set('x-from-runtime', nanoid()) + return response + } + `) + }, + }, + ])('$title importing vanilla 3rd party module', ({ init, url }) => { const moduleName = 'nanoid' const importStatement = `import { nanoid } from "${moduleName}"` - beforeEach(() => { - context.api.write(` - ${importStatement} - export default async function handler(request) { - return Response.json({ ok: nanoid() }) - } - - export const config = { runtime: 'experimental-edge' } - `) - }) + beforeEach(() => init(importStatement)) it('does not throw in dev at runtime', async () => { context.app = await launchApp(context.appDir, context.appPort, appOption) - const res = await fetchViaHTTP(context.appPort, routeUrl) + const res = await fetchViaHTTP(context.appPort, url) expect(res.status).toBe(200) - expect(await res.json()).toEqual({ ok: expect.any(String) }) + expect(res.headers.get('x-from-runtime')).toBeDefined() expectNoError(moduleName) }) @@ -519,47 +543,67 @@ describe('Edge runtime code with imports', () => { }) expect(stderr).not.toContain(getUnsupportedModuleWarning(moduleName)) context.app = await nextStart(context.appDir, context.appPort, appOption) - const res = await fetchViaHTTP(context.appPort, routeUrl) + const res = await fetchViaHTTP(context.appPort, url) expect(res.status).toBe(200) - expect(await res.json()).toEqual({ ok: expect.any(String) }) + expect(res.headers.get('x-from-runtime')).toBeDefined() expectNoError(moduleName) }) }) - describe('Middleware importing vanilla 3rd party module', () => { - const moduleName = 'nanoid' - const importStatement = `import { nanoid } from "${moduleName}"` + describe.each([ + { + title: 'Edge API', + url: routeUrl, + init(importStatement) { + context.api.write(` + ${importStatement} + + export default async function handler(request) { + const response = Response.json({ ok: true }) + response.headers.set('x-from-runtime', Buffer.isBuffer('a string')) + return response + } + + export const config = { runtime: 'experimental-edge' } + `) + }, + }, + { + title: 'Middleware', + url: middlewareUrl, + init(importStatement) { + context.middleware.write(` + import { NextResponse } from 'next/server' + ${importStatement} - beforeEach(() => { - context.middleware.write(` - import { NextResponse } from 'next/server' - ${importStatement} + export async function middleware(request) { + const response = NextResponse.next() + response.headers.set('x-from-runtime', Buffer.isBuffer('a string')) + return response + } + `) + }, + }, + ])('$title using Buffer polyfill', ({ init, url }) => { + const moduleName = 'buffer' + const importStatement = `import { Buffer } from "${moduleName}"` - export async function middleware(request) { - const response = NextResponse.next() - response.headers.set('x-from-middleware', nanoid()) - return response - } - `) - }) + beforeEach(() => init(importStatement)) it('does not throw in dev at runtime', async () => { context.app = await launchApp(context.appDir, context.appPort, appOption) - const res = await fetchViaHTTP(context.appPort, middlewareUrl) + const res = await fetchViaHTTP(context.appPort, url) expect(res.status).toBe(200) - expect(res.headers.get('x-from-middleware')).toBeDefined() + expect(res.headers.get('x-from-runtime')).toBe('false') expectNoError(moduleName) }) it('does not throw in production at runtime', async () => { - const { stderr } = await nextBuild(context.appDir, undefined, { - stderr: true, - }) - expect(stderr).not.toContain(getUnsupportedModuleWarning(moduleName)) + await nextBuild(context.appDir, undefined, { stderr: true }) context.app = await nextStart(context.appDir, context.appPort, appOption) - const res = await fetchViaHTTP(context.appPort, middlewareUrl) + const res = await fetchViaHTTP(context.appPort, url) expect(res.status).toBe(200) - expect(res.headers.get('x-from-middleware')).toBeDefined() + expect(res.headers.get('x-from-runtime')).toBe('false') expectNoError(moduleName) }) }) diff --git a/test/integration/edge-runtime-with-node.js-apis/test/index.test.ts b/test/integration/edge-runtime-with-node.js-apis/test/index.test.ts index 090395b2afd43..1b77ecf5744cd 100644 --- a/test/integration/edge-runtime-with-node.js-apis/test/index.test.ts +++ b/test/integration/edge-runtime-with-node.js-apis/test/index.test.ts @@ -22,7 +22,7 @@ const unsupportedFunctions = [ 'process.cpuUsage', 'process.getuid', ] -const undefinedPropertoes = [ +const undefinedProperties = [ // no need to test all of the process properties 'process.arch', 'process.version', @@ -83,7 +83,7 @@ describe.each([ afterAll(() => killApp(app)) - it.each(undefinedPropertoes.map((api) => ({ api })))( + it.each(undefinedProperties.map((api) => ({ api })))( 'does not throw on using $api', async ({ api }) => { const res = await fetchViaHTTP(appPort, computeRoute(api)) @@ -125,16 +125,14 @@ Learn more: https://nextjs.org/docs/api-reference/edge-runtime`) }) it.each( - ['Buffer', ...unsupportedFunctions, ...unsupportedClasses].map( - (api, index) => ({ - api, - }) - ) + [...unsupportedFunctions, ...unsupportedClasses].map((api, index) => ({ + api, + })) )(`warns for $api during build`, ({ api }) => { expect(buildResult.stderr).toContain(`A Node.js API is used (${api}`) }) - it.each(undefinedPropertoes.map((api) => ({ api })))( + it.each(['Buffer', ...undefinedProperties].map((api) => ({ api })))( 'does not warn on using $api', ({ api }) => { expect(buildResult.stderr).toContain(`A Node.js API is used (${api}`)