From 1fe553e09a5e2f4acaabe7bc34988ef01a3b618f Mon Sep 17 00:00:00 2001 From: feugy Date: Mon, 1 Aug 2022 17:22:34 +0200 Subject: [PATCH 1/2] fix: buffer is not usable on edge runtime --- .../webpack/plugins/middleware-plugin.ts | 5 +- .../test/index.test.js | 113 +++++++++++++----- .../test/index.test.ts | 14 +-- 3 files changed, 90 insertions(+), 42 deletions(-) diff --git a/packages/next/build/webpack/plugins/middleware-plugin.ts b/packages/next/build/webpack/plugins/middleware-plugin.ts index ebdd8a173a35..8a06077879ca 100644 --- a/packages/next/build/webpack/plugins/middleware-plugin.ts +++ b/packages/next/build/webpack/plugins/middleware-plugin.ts @@ -760,5 +760,8 @@ function isProcessEnvMemberExpression(memberExpression: any): boolean { } function isNodeJsModule(moduleName: string) { - return require('module').builtinModules.includes(moduleName) + return ( + moduleName !== 'buffer' && + require('module').builtinModules.includes(moduleName) + ) } 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 f883e8476abc..cb31f2bca6cc 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,35 +543,58 @@ 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) }) @@ -557,9 +604,9 @@ 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, 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 090395b2afd4..1b77ecf5744c 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}`) From 5f7a329844e9017e5f36fa6fcf950c92d6501199 Mon Sep 17 00:00:00 2001 From: feugy Date: Wed, 3 Aug 2022 12:52:06 +0200 Subject: [PATCH 2/2] chore: improves implementation to allow any fallbacks --- .../build/webpack/plugins/middleware-plugin.ts | 16 +++++++++++----- .../test/index.test.js | 5 +---- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/packages/next/build/webpack/plugins/middleware-plugin.ts b/packages/next/build/webpack/plugins/middleware-plugin.ts index 8a06077879ca..f688d4e32e9d 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}')` + } } } @@ -760,8 +769,5 @@ function isProcessEnvMemberExpression(memberExpression: any): boolean { } function isNodeJsModule(moduleName: string) { - return ( - moduleName !== 'buffer' && - require('module').builtinModules.includes(moduleName) - ) + return require('module').builtinModules.includes(moduleName) } 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 cb31f2bca6cc..2906fe6ce0a3 100644 --- a/test/integration/edge-runtime-module-errors/test/index.test.js +++ b/test/integration/edge-runtime-module-errors/test/index.test.js @@ -599,10 +599,7 @@ describe('Edge runtime code with imports', () => { }) 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, url) expect(res.status).toBe(200)