Skip to content

Commit

Permalink
Ensure WebSocket is polyfilled for node runtime as well (#48924)
Browse files Browse the repository at this point in the history
Follow-up to #48870 this exposes
WebSocket for the Next.js runtime so we don't have divergence in APIs as
discussed with @cramforce
  • Loading branch information
ijjk committed Apr 28, 2023
1 parent d922e3d commit 7bfd582
Show file tree
Hide file tree
Showing 9 changed files with 43 additions and 14 deletions.
5 changes: 1 addition & 4 deletions packages/next/src/build/utils.ts
Expand Up @@ -59,10 +59,7 @@ import { StaticGenerationAsyncStorageWrapper } from '../server/async-storage/sta
import { IncrementalCache } from '../server/lib/incremental-cache'
import { patchFetch } from '../server/lib/patch-fetch'
import { nodeFs } from '../server/lib/node-fs-methods'

// expose AsyncLocalStorage on global for react usage
const { AsyncLocalStorage } = require('async_hooks')
;(globalThis as any).AsyncLocalStorage = AsyncLocalStorage
import '../server/node-environment'

export type ROUTER_TYPE = 'pages' | 'app'

Expand Down
5 changes: 1 addition & 4 deletions packages/next/src/export/worker.ts
Expand Up @@ -12,6 +12,7 @@ import type { OutgoingHttpHeaders } from 'http'

// Polyfill fetch for the export worker.
import '../server/node-polyfill-fetch'
import '../server/node-environment'

import { extname, join, dirname, sep, posix } from 'path'
import fs, { promises } from 'fs'
Expand Down Expand Up @@ -119,10 +120,6 @@ interface RenderOpts {
strictNextHead?: boolean
}

// expose AsyncLocalStorage on globalThis for react usage
const { AsyncLocalStorage } = require('async_hooks')
;(globalThis as any).AsyncLocalStorage = AsyncLocalStorage

export default async function exportPage({
parentSpanId,
path,
Expand Down
7 changes: 2 additions & 5 deletions packages/next/src/server/dev/static-paths-worker.ts
@@ -1,8 +1,9 @@
import type { NextConfigComplete } from '../config-shared'
import type { AppRouteUserlandModule } from '../future/route-modules/app-route/module'

import '../../server/require-hook'
import '../require-hook'
import '../node-polyfill-fetch'
import '../node-environment'
import {
buildAppStaticPaths,
buildStaticPaths,
Expand All @@ -17,10 +18,6 @@ import { staticGenerationAsyncStorage } from '../../client/components/static-gen

type RuntimeConfig = any

// expose AsyncLocalStorage on globalThis for react usage
const { AsyncLocalStorage } = require('async_hooks')
;(globalThis as any).AsyncLocalStorage = AsyncLocalStorage

// we call getStaticPaths in a separate process to ensure
// side-effects aren't relied on in dev that will break
// during a production build
Expand Down
13 changes: 13 additions & 0 deletions packages/next/src/server/node-environment.ts
Expand Up @@ -6,3 +6,16 @@ if (typeof (globalThis as any).AsyncLocalStorage !== 'function') {
const { AsyncLocalStorage } = require('async_hooks')
;(globalThis as any).AsyncLocalStorage = AsyncLocalStorage
}

if (typeof (globalThis as any).WebSocket !== 'function') {
let WebSocket

// undici's WebSocket handling is only available in Node.js >= 18
// so fallback to using ws for v16
if (Number(process.version.split('.')[0].substring(1)) < 18) {
WebSocket = require('next/dist/compiled/ws').WebSocket
} else {
WebSocket = require('next/dist/compiled/undici').WebSocket
}
;(globalThis as any).WebSocket = WebSocket
}
12 changes: 11 additions & 1 deletion packages/next/src/server/web/sandbox/context.ts
Expand Up @@ -235,7 +235,17 @@ async function createModuleContext(options: ModuleContextOptions) {
? { strings: true, wasm: true }
: undefined,
extend: (context) => {
context.WebSocket = require('next/dist/compiled/undici').WebSocket
let WebSocket

// undici's WebSocket handling is only available in Node.js >= 18
// so fallback to using ws for v16
if (Number(process.version.split('.')[0].substring(1)) < 18) {
WebSocket = require('next/dist/compiled/ws').WebSocket
} else {
WebSocket = require('next/dist/compiled/undici').WebSocket
}

context.WebSocket = WebSocket
context.process = createProcessPolyfill(options)

Object.defineProperty(context, 'require', {
Expand Down
4 changes: 4 additions & 0 deletions test/e2e/app-dir/app-routes/handlers/hello.ts
Expand Up @@ -7,6 +7,10 @@ const helloHandler = async (
): Promise<Response> => {
const { pathname } = request.nextUrl

if (typeof WebSocket === 'undefined') {
throw new Error('missing WebSocket constructor!!')
}

return new Response('hello, world', {
headers: withRequestMeta({
method: request.method,
Expand Down
@@ -1,4 +1,8 @@
export function generateStaticParams() {
if (typeof WebSocket === 'undefined') {
throw new Error('missing WebSocket constructor!!')
}

return [{ lang: 'en' }, { lang: 'fr' }]
}

Expand Down
4 changes: 4 additions & 0 deletions test/e2e/app-dir/app/app/dashboard/deployments/info/page.js
@@ -1,4 +1,8 @@
export default function DeploymentsInfoPage(props) {
if (typeof WebSocket === 'undefined') {
throw new Error('missing WebSocket constructor!!')
}

return (
<>
<p>hello from app/dashboard/deployments/info</p>
Expand Down
3 changes: 3 additions & 0 deletions test/e2e/app-dir/app/app/dashboard/page.js
@@ -1,6 +1,9 @@
import ClientComp from './client-comp-client'

export default function DashboardPage(props) {
if (typeof WebSocket === 'undefined') {
throw new Error('missing WebSocket constructor!!')
}
return (
<>
<p id="from-dashboard" className="p">
Expand Down

0 comments on commit 7bfd582

Please sign in to comment.