diff --git a/packages/runtime/src/helpers/edge.ts b/packages/runtime/src/helpers/edge.ts index 026bb4f995..86c081a524 100644 --- a/packages/runtime/src/helpers/edge.ts +++ b/packages/runtime/src/helpers/edge.ts @@ -4,7 +4,7 @@ import { resolve, join } from 'path' import type { NetlifyConfig, NetlifyPluginConstants } from '@netlify/build' import { greenBright } from 'chalk' import destr from 'destr' -import { copy, copyFile, emptyDir, ensureDir, readJSON, readJson, writeJSON, writeJson } from 'fs-extra' +import { copy, copyFile, emptyDir, ensureDir, readJSON, writeJSON, writeJson } from 'fs-extra' import type { PrerenderManifest } from 'next/dist/build' import type { MiddlewareManifest } from 'next/dist/build/webpack/plugins/middleware-plugin' import type { RouteHas } from 'next/dist/lib/load-custom-routes' @@ -68,7 +68,7 @@ export interface FunctionManifest { const maybeLoadJson = (path: string): Promise | null => { if (existsSync(path)) { - return readJson(path) + return readJSON(path) } } export const isAppDirRoute = (route: string, appPathRoutesManifest: Record | null): boolean => @@ -88,60 +88,6 @@ export const loadPrerenderManifest = (netlifyConfig: NetlifyConfig): Promise
 `next_${name.replace(/\W/g, '_')}`
 
-/**
- * Initialization added to the top of the edge function bundle
- */
-const preamble = /* js */ `
-import {
-  decode as _base64Decode,
-} from "https://deno.land/std@0.175.0/encoding/base64.ts";
-
-import { AsyncLocalStorage } from "https://deno.land/std@0.175.0/node/async_hooks.ts";
-
-// Deno defines "window", but naughty libraries think this means it's a browser
-delete globalThis.window
-globalThis.process = { env: {...Deno.env.toObject(), NEXT_RUNTIME: 'edge', 'NEXT_PRIVATE_MINIMAL_MODE': '1' } }
-globalThis.EdgeRuntime = "netlify-edge"
-let _ENTRIES = {}
-
-// Next.js expects this as a global
-globalThis.AsyncLocalStorage = AsyncLocalStorage
-
-// Next.js uses this extension to the Headers API implemented by Cloudflare workerd
-if(!('getAll' in Headers.prototype)) {
-  Headers.prototype.getAll = function getAll(name) {
-    name = name.toLowerCase();
-    if (name !== "set-cookie") {
-      throw new Error("Headers.getAll is only supported for Set-Cookie");
-    }
-    return [...this.entries()]
-      .filter(([key]) => key === name)
-      .map(([, value]) => value);
-  };
-}
-//  Next uses blob: urls to refer to local assets, so we need to intercept these
-const _fetch = globalThis.fetch
-const fetch = async (url, init) => {
-  try {
-    if (typeof url === 'object' && url.href?.startsWith('blob:')) {
-      const key = url.href.slice(5)
-      if (key in _ASSETS) {
-        return new Response(_base64Decode(_ASSETS[key]))
-      }
-    }
-    return await _fetch(url, init)
-  } catch (error) {
-    console.error(error)
-    throw error
-  }
-}
-
-// Next edge runtime uses "self" as a function-scoped global-like object, but some of the older polyfills expect it to equal globalThis
-// See https://nextjs.org/docs/basic-features/supported-browsers-features#polyfills
-const self = { ...globalThis, fetch }
-
-`
-
 // Slightly different spacing in different versions!
 const IMPORT_UNSUPPORTED = [
   `Object.defineProperty(globalThis,"__import_unsupported"`,
@@ -158,7 +104,10 @@ const getMiddlewareBundle = async ({
   netlifyConfig: NetlifyConfig
 }): Promise => {
   const { publish } = netlifyConfig.build
-  const chunks: Array = [preamble]
+
+  const shims = await fs.readFile(getEdgeTemplatePath('shims.js'), 'utf8')
+
+  const chunks: Array = [shims]
 
   chunks.push(`export const _DEFINITION = ${JSON.stringify(edgeFunctionDefinition)}`)
 
diff --git a/packages/runtime/src/templates/edge/shims.js b/packages/runtime/src/templates/edge/shims.js
new file mode 100644
index 0000000000..04a02389f9
--- /dev/null
+++ b/packages/runtime/src/templates/edge/shims.js
@@ -0,0 +1,53 @@
+// @ts-check
+// deno-lint-ignore-file no-var prefer-const no-unused-vars no-explicit-any
+import { decode as _base64Decode } from 'https://deno.land/std@0.175.0/encoding/base64.ts'
+import { AsyncLocalStorage as ALSCompat } from 'https://deno.land/std@0.175.0/node/async_hooks.ts'
+
+/**
+ * These are the shims, polyfills and other kludges to make Next.js work in standards-compliant runtime.
+ * This file isn't imported, but is instead inlined along with other chunks into the edge bundle.
+ */
+
+// Deno defines "window", but naughty libraries think this means it's a browser
+// @ts-ignore
+delete globalThis.window
+globalThis.process = {
+  env: { ...Deno.env.toObject(), NEXT_RUNTIME: 'edge', NEXT_PRIVATE_MINIMAL_MODE: '1' },
+}
+globalThis.EdgeRuntime = 'netlify-edge'
+let _ENTRIES = {}
+
+// Next.js expects this as a global
+globalThis.AsyncLocalStorage = ALSCompat
+
+// Next.js uses this extension to the Headers API implemented by Cloudflare workerd
+if (!('getAll' in Headers.prototype)) {
+  // @ts-ignore
+  Headers.prototype.getAll = function getAll(name) {
+    name = name.toLowerCase()
+    if (name !== 'set-cookie') {
+      throw new Error('Headers.getAll is only supported for Set-Cookie')
+    }
+    return [...this.entries()].filter(([key]) => key === name).map(([, value]) => value)
+  }
+}
+//  Next uses blob: urls to refer to local assets, so we need to intercept these
+const _fetch = globalThis.fetch
+const fetch /* type {typeof globalThis.fetch} */ = async (url, init) => {
+  try {
+    if (url instanceof URL && url.href?.startsWith('blob:')) {
+      const key = url.href.slice(5)
+      if (key in _ASSETS) {
+        return new Response(_base64Decode(_ASSETS[key]))
+      }
+    }
+    return await _fetch(url, init)
+  } catch (error) {
+    console.error(error)
+    throw error
+  }
+}
+
+// Next edge runtime uses "self" as a function-scoped global-like object, but some of the older polyfills expect it to equal globalThis
+// See https://nextjs.org/docs/basic-features/supported-browsers-features#polyfills
+const self = { ...globalThis, fetch }