Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: buffer is not usable on edge runtime #39227

Merged
merged 3 commits into from Aug 4, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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