From 2acfae1d08e6cef11154683bbb67735090568fb7 Mon Sep 17 00:00:00 2001 From: Matt Kane Date: Thu, 2 Feb 2023 12:29:44 +0000 Subject: [PATCH 1/2] refactor: extract shims into separate file --- packages/runtime/src/helpers/edge.ts | 63 ++------------------ packages/runtime/src/templates/edge/shims.ts | 59 ++++++++++++++++++ 2 files changed, 65 insertions(+), 57 deletions(-) create mode 100644 packages/runtime/src/templates/edge/shims.ts diff --git a/packages/runtime/src/helpers/edge.ts b/packages/runtime/src/helpers/edge.ts index 026bb4f995..ec787f343c 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.ts'), 'utf8')
+
+  const chunks: Array = [shims]
 
   chunks.push(`export const _DEFINITION = ${JSON.stringify(edgeFunctionDefinition)}`)
 
diff --git a/packages/runtime/src/templates/edge/shims.ts b/packages/runtime/src/templates/edge/shims.ts
new file mode 100644
index 0000000000..2fa40b2868
--- /dev/null
+++ b/packages/runtime/src/templates/edge/shims.ts
@@ -0,0 +1,59 @@
+// 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.
+ */
+
+declare global {
+  var process: {
+    env: Record
+  }
+  var EdgeRuntime: string
+  var AsyncLocalStorage: typeof ALSCompat
+  var _ASSETS: Record
+}
+
+// Deno defines "window", but naughty libraries think this means it's a browser
+delete (globalThis as Omit & Pick, 'window'>).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)) {
+  ;(Headers as any).prototype.getAll = function getAll(name: string) {
+    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: 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 }

From ba29db0846bbb0151dea0fb5ffe14e9798abc690 Mon Sep 17 00:00:00 2001
From: Matt Kane 
Date: Thu, 2 Feb 2023 13:55:53 +0000
Subject: [PATCH 2/2] chore: needs to be js

---
 packages/runtime/src/helpers/edge.ts           |  2 +-
 .../src/templates/edge/{shims.ts => shims.js}  | 18 ++++++------------
 2 files changed, 7 insertions(+), 13 deletions(-)
 rename packages/runtime/src/templates/edge/{shims.ts => shims.js} (81%)

diff --git a/packages/runtime/src/helpers/edge.ts b/packages/runtime/src/helpers/edge.ts
index ec787f343c..86c081a524 100644
--- a/packages/runtime/src/helpers/edge.ts
+++ b/packages/runtime/src/helpers/edge.ts
@@ -105,7 +105,7 @@ const getMiddlewareBundle = async ({
 }): Promise => {
   const { publish } = netlifyConfig.build
 
-  const shims = await fs.readFile(getEdgeTemplatePath('shims.ts'), 'utf8')
+  const shims = await fs.readFile(getEdgeTemplatePath('shims.js'), 'utf8')
 
   const chunks: Array = [shims]
 
diff --git a/packages/runtime/src/templates/edge/shims.ts b/packages/runtime/src/templates/edge/shims.js
similarity index 81%
rename from packages/runtime/src/templates/edge/shims.ts
rename to packages/runtime/src/templates/edge/shims.js
index 2fa40b2868..04a02389f9 100644
--- a/packages/runtime/src/templates/edge/shims.ts
+++ b/packages/runtime/src/templates/edge/shims.js
@@ -1,3 +1,4 @@
+// @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'
@@ -7,17 +8,9 @@ import { AsyncLocalStorage as ALSCompat } from 'https://deno.land/std@0.175.0/no
  * This file isn't imported, but is instead inlined along with other chunks into the edge bundle.
  */
 
-declare global {
-  var process: {
-    env: Record
-  }
-  var EdgeRuntime: string
-  var AsyncLocalStorage: typeof ALSCompat
-  var _ASSETS: Record
-}
-
 // Deno defines "window", but naughty libraries think this means it's a browser
-delete (globalThis as Omit & Pick, 'window'>).window
+// @ts-ignore
+delete globalThis.window
 globalThis.process = {
   env: { ...Deno.env.toObject(), NEXT_RUNTIME: 'edge', NEXT_PRIVATE_MINIMAL_MODE: '1' },
 }
@@ -29,7 +22,8 @@ globalThis.AsyncLocalStorage = ALSCompat
 
 // Next.js uses this extension to the Headers API implemented by Cloudflare workerd
 if (!('getAll' in Headers.prototype)) {
-  ;(Headers as any).prototype.getAll = function getAll(name: string) {
+  // @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')
@@ -39,7 +33,7 @@ if (!('getAll' in Headers.prototype)) {
 }
 //  Next uses blob: urls to refer to local assets, so we need to intercept these
 const _fetch = globalThis.fetch
-const fetch: typeof globalThis.fetch = async (url, init) => {
+const fetch /* type {typeof globalThis.fetch} */ = async (url, init) => {
   try {
     if (url instanceof URL && url.href?.startsWith('blob:')) {
       const key = url.href.slice(5)