fix: buffer is not usable on edge runtime (#39227)

* fix: buffer is not usable on edge runtime

* chore: improves implementation to allow any fallbacks
This commit is contained in:
Damien Simonin Feugas 2022-08-04 15:47:28 +02:00 committed by GitHub
parent d315ee1786
commit 147a24e320
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 97 additions and 46 deletions

View file

@ -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}')`
}
}
}

View file

@ -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}
beforeEach(() => {
context.middleware.write(`
import { NextResponse } from 'next/server'
${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}
export async function middleware(request) {
const response = NextResponse.next()
response.headers.set('x-from-middleware', nanoid())
return response
}
`)
})
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}"`
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)
})
})

View file

@ -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}`)