Skip to content

Commit

Permalink
fix: buffer is not usable on edge runtime (#39227)
Browse files Browse the repository at this point in the history
* fix: buffer is not usable on edge runtime

* chore: improves implementation to allow any fallbacks
  • Loading branch information
feugy committed Aug 4, 2022
1 parent d315ee1 commit 147a24e
Show file tree
Hide file tree
Showing 3 changed files with 97 additions and 46 deletions.
11 changes: 10 additions & 1 deletion packages/next/build/webpack/plugins/middleware-plugin.ts
Expand Up @@ -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}')`
}
}
}

Expand Down
118 changes: 81 additions & 37 deletions test/integration/edge-runtime-module-errors/test/index.test.js
Expand Up @@ -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)
})

Expand All @@ -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)
})
})
Expand Down
Expand Up @@ -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',
Expand Down Expand Up @@ -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))
Expand Down Expand Up @@ -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}`)
Expand Down

0 comments on commit 147a24e

Please sign in to comment.