From bbd8452d6b1dfe4cefd83820abb911de03a1d647 Mon Sep 17 00:00:00 2001 From: Tim Neutkens Date: Tue, 30 Apr 2024 11:26:52 +0200 Subject: [PATCH] Fix crypto import in edge runtime with Turbopack (#65171) ## What? Ensures just importing `crypto` does not error, only when it is used it shows an error in the edge runtime. This matches webpack behavior. The `crypto` module was missing the list of unsupported packages in the Next.js Turbopack integration. Fixes #64464 Fixes PACK-2954 ## TODO While adding tests for this issue I found another bug that only happens with webpack. Specifically these 4 packages are accidentally being polyfilled even when they're not set up to be polyfilled. i.e. there's no npm package installed for polyfilling them through aliasing or such. Even in that case `punycode`, `process`, `querystring`, and `string_decorder` get polyfilled regardless, this causes the newly added test to fail. Removing the polyfills would be potentially breaking so we'll want to change it in Next.js 15 instead. Closes NEXT-3252 --- .../crates/next-core/src/next_import_map.rs | 3 +- test/development/basic/node-builtins.test.ts | 31 ++++ .../app/middleware-test/page.tsx | 3 + .../basic/node-builtins/middleware.js | 137 ++++++++++++++++++ 4 files changed, 173 insertions(+), 1 deletion(-) create mode 100644 test/development/basic/node-builtins/app/middleware-test/page.tsx create mode 100644 test/development/basic/node-builtins/middleware.js diff --git a/packages/next-swc/crates/next-core/src/next_import_map.rs b/packages/next-swc/crates/next-core/src/next_import_map.rs index 210d9b898a537..e80def16aed70 100644 --- a/packages/next-swc/crates/next-core/src/next_import_map.rs +++ b/packages/next-swc/crates/next-core/src/next_import_map.rs @@ -45,11 +45,12 @@ use crate::{ /// This is not identical to the list of entire node.js internals, refer /// https://vercel.com/docs/functions/runtimes/edge-runtime#compatible-node.js-modules /// for the allowed imports. -const EDGE_UNSUPPORTED_NODE_INTERNALS: [&str; 43] = [ +const EDGE_UNSUPPORTED_NODE_INTERNALS: [&str; 44] = [ "child_process", "cluster", "console", "constants", + "crypto", "dgram", "diagnostics_channel", "dns", diff --git a/test/development/basic/node-builtins.test.ts b/test/development/basic/node-builtins.test.ts index 96a3ce3caeaf9..b695df70cc194 100644 --- a/test/development/basic/node-builtins.test.ts +++ b/test/development/basic/node-builtins.test.ts @@ -167,4 +167,35 @@ describe('node builtins', () => { expect(parsedData.sys).toBe(true) expect(parsedData.timers).toBe(true) }) + + it('should throw when unsupported builtins are used in middleware', async () => { + const res = await next.fetch('/middleware-test') + expect(res.status).toBe(200) + expect(JSON.parse(res.headers.get('supported-result'))) + .toMatchInlineSnapshot(` + { + "assert": true, + "buffer": "hello world", + "eventEmitter": true, + "util": true, + } + `) + expect(JSON.parse(res.headers.get('unsupported-result'))) + .toMatchInlineSnapshot(` + { + "constants": false, + "crypto": false, + "domain": false, + "http": false, + "https": false, + "os": false, + "path": false, + "stream": false, + "timers": false, + "tty": false, + "vm": false, + "zlib": false, + } + `) + }) }) diff --git a/test/development/basic/node-builtins/app/middleware-test/page.tsx b/test/development/basic/node-builtins/app/middleware-test/page.tsx new file mode 100644 index 0000000000000..5a8ee22cb94bd --- /dev/null +++ b/test/development/basic/node-builtins/app/middleware-test/page.tsx @@ -0,0 +1,3 @@ +export default function Page() { + return

Middleware Test

+} diff --git a/test/development/basic/node-builtins/middleware.js b/test/development/basic/node-builtins/middleware.js new file mode 100644 index 0000000000000..f683368408a8c --- /dev/null +++ b/test/development/basic/node-builtins/middleware.js @@ -0,0 +1,137 @@ +// Unsupported in edge +import { Writable } from 'stream' +import path from 'path' +import crypto from 'crypto' +import vm from 'vm' +import constants from 'constants' +import domain from 'domain' +import http from 'http' +import https from 'https' +import os from 'os' +// TODO: These are accidentally polyfilled in edge runtime currently. +// import punycode from 'punycode' +// import process from 'process' +// import querystring from 'querystring' +// import stringDecoder from 'string_decoder' +import sys from 'sys' +import timers from 'timers' +import tty from 'tty' +import zlib from 'zlib' +import 'setimmediate' +// Supported in edge +import { Buffer } from 'buffer' +import assert from 'assert' +import util from 'util' +import { EventEmitter } from 'events' + +// Other imports +import { NextResponse } from 'next/server' + +export default async function middleware(request) { + if (request.nextUrl.pathname !== '/middleware-test') { + return + } + const response = NextResponse.next() + + let emitted = false + class MyEmitter extends EventEmitter {} + const myEmitter = new MyEmitter() + // Only do this once so we don't loop forever + myEmitter.once('myEvent', (_event, _listener) => { + emitted = true + }) + myEmitter.emit('myEvent') + + assert.ok(emitted) + assert.ok(!!util.promisify) + assert.ok(true) + + const supportedResult = { + assert: true, + buffer: Buffer.from('hello world').toString('utf8'), + eventEmitter: true, + util: true, + } + + response.headers.set('supported-result', JSON.stringify(supportedResult)) + + // TODO: These are accidentally polyfilled in edge runtime currently. + // assert.throws(() => { + // console.log(punycode) + // }) + // assert.throws(() => { + // console.log(process.title) + // }) + // assert.throws(() => { + // console.log(querystring) + // }) + // assert.throws(() => { + // console.log(stringDecoder) + // }) + + assert.throws(() => { + console.log(domain) + }) + assert.throws(() => { + console.log(http) + }) + assert.throws(() => { + console.log(https) + }) + assert.throws(() => { + console.log(zlib.Gzip) + }) + assert.throws(() => { + console.log(constants.E2BIG) + }) + assert.throws(() => { + console.log(crypto.createHash) + }) + assert.throws(() => { + console.log(os.hostname) + }) + assert.throws(() => { + console.log(path.join) + }) + assert.throws(() => { + console.log(vm) + }) + assert.throws(() => { + console.log(tty.isatty) + }) + assert.throws(() => { + console.log(timers.clearImmediate) + }) + assert.throws(() => { + console.log(Writable) + }) + assert.throws(() => { + console.log(sys) + }) + + const unsupportedResult = { + // TODO: these are accidentally polyfilled in edge runtime currently. + // punycode: false, + // process: false, + // querystring: false, + // stringDecoder: false, + + domain: false, + http: false, + https: false, + zlib: false, + constants: false, + crypto: false, + os: false, + path: false, + + vm: false, + tty: false, + timers: false, + stream: false, + } + + response.headers.set('unsupported-result', JSON.stringify(unsupportedResult)) + + return response +}