From f3ab822e328725c3905b0adad9889ad37653c24a Mon Sep 17 00:00:00 2001
From: Matthew Phillips <matthew@skypack.dev>
Date: Tue, 7 Jun 2022 11:41:32 -0400
Subject: [PATCH] Allow dynamic imports when using Netlify Edge Functions
 (#3535)

* Allow dynamic imports when using Netlify Edge Functions

* Update deno test script and changeset
---
 .changeset/spicy-planes-drum.md               |  5 +++
 packages/integrations/deno/package.json       |  4 +--
 packages/integrations/netlify/package.json    |  3 +-
 .../netlify/src/integration-edge-functions.ts | 35 +++++++++++++++++--
 .../netlify/test/edge-functions/deps.ts       |  1 +
 .../edge-functions/dynamic-import.test.js     | 27 ++++++++++++++
 .../fixtures/dynimport/astro.config.mjs       | 11 ++++++
 .../fixtures/dynimport/package.json           |  9 +++++
 .../edge-functions/fixtures/dynimport/prod.js | 11 ++++++
 .../dynimport/src/components/Thing.astro      |  4 +++
 .../fixtures/dynimport/src/pages/index.astro  | 11 ++++++
 .../netlify/test/edge-functions/test-utils.ts | 20 ++++++++++-
 pnpm-lock.yaml                                | 10 ++++++
 13 files changed, 144 insertions(+), 7 deletions(-)
 create mode 100644 .changeset/spicy-planes-drum.md
 create mode 100644 packages/integrations/netlify/test/edge-functions/dynamic-import.test.js
 create mode 100644 packages/integrations/netlify/test/edge-functions/fixtures/dynimport/astro.config.mjs
 create mode 100644 packages/integrations/netlify/test/edge-functions/fixtures/dynimport/package.json
 create mode 100644 packages/integrations/netlify/test/edge-functions/fixtures/dynimport/prod.js
 create mode 100644 packages/integrations/netlify/test/edge-functions/fixtures/dynimport/src/components/Thing.astro
 create mode 100644 packages/integrations/netlify/test/edge-functions/fixtures/dynimport/src/pages/index.astro

diff --git a/.changeset/spicy-planes-drum.md b/.changeset/spicy-planes-drum.md
new file mode 100644
index 0000000000..7f758e4b4b
--- /dev/null
+++ b/.changeset/spicy-planes-drum.md
@@ -0,0 +1,5 @@
+---
+'@astrojs/netlify': patch
+---
+
+Fixes Netlify Edge Function and Astro.glob
diff --git a/packages/integrations/deno/package.json b/packages/integrations/deno/package.json
index 6a94327e8e..54cf23d2c5 100644
--- a/packages/integrations/deno/package.json
+++ b/packages/integrations/deno/package.json
@@ -22,9 +22,7 @@
     "build": "astro-scripts build \"src/**/*.ts\" && tsc",
     "build:ci": "astro-scripts build \"src/**/*.ts\"",
     "dev": "astro-scripts dev \"src/**/*.ts\"",
-    "test:import": "deno test --allow-run --allow-env --allow-read --allow-net --ignore=test/dynamic-import.test.js ./test/",
-    "test:subprocess": "deno test --allow-run --allow-env --allow-net ./test/dynamic-import.test.js",
-    "test": "npm run test:import && npm run test:subprocess"
+    "test": "deno test --allow-run --allow-env --allow-read --allow-net ./test/"
   },
   "dependencies": {
     "esbuild": "^0.14.42"
diff --git a/packages/integrations/netlify/package.json b/packages/integrations/netlify/package.json
index 9b2f197add..ff30831e46 100644
--- a/packages/integrations/netlify/package.json
+++ b/packages/integrations/netlify/package.json
@@ -30,7 +30,8 @@
     "test": "npm run test-fn"
   },
   "dependencies": {
-    "@astrojs/webapi": "^0.12.0"
+    "@astrojs/webapi": "^0.12.0",
+    "esbuild": "^0.14.42"
   },
   "devDependencies": {
     "@netlify/edge-handler-types": "^0.34.1",
diff --git a/packages/integrations/netlify/src/integration-edge-functions.ts b/packages/integrations/netlify/src/integration-edge-functions.ts
index e43a7f482f..3ef39eecbc 100644
--- a/packages/integrations/netlify/src/integration-edge-functions.ts
+++ b/packages/integrations/netlify/src/integration-edge-functions.ts
@@ -1,6 +1,9 @@
-import type { AstroAdapter, AstroConfig, AstroIntegration, RouteData } from 'astro';
-import * as fs from 'fs';
+import type { AstroAdapter, AstroIntegration, AstroConfig, RouteData, BuildConfig } from 'astro';
 import { createRedirects } from './shared.js';
+import esbuild from 'esbuild';
+import * as fs from 'fs';
+import { fileURLToPath } from 'url';
+import * as npath from 'path';
 
 export function getAdapter(): AstroAdapter {
 	return {
@@ -62,9 +65,34 @@ async function createEdgeManifest(routes: RouteData[], entryFile: string, dir: U
 	await fs.promises.writeFile(manifestURL, _manifest, 'utf-8');
 }
 
+async function bundleServerEntry(buildConfig: BuildConfig, vite: any) {
+	const entryUrl = new URL(buildConfig.serverEntry, buildConfig.server);
+	const pth = fileURLToPath(entryUrl);
+	await esbuild.build({
+		target: 'es2020',
+		platform: 'browser',
+		entryPoints: [pth],
+		outfile: pth,
+		allowOverwrite: true,
+		format: 'esm',
+		bundle: true,
+		external: [ "@astrojs/markdown-remark"]
+	});
+
+	// Remove chunks, if they exist. Since we have bundled via esbuild these chunks are trash.
+	try {
+		const chunkFileNames = vite?.build?.rollupOptions?.output?.chunkFileNames ?? 'chunks/chunk.[hash].mjs';
+		const chunkPath = npath.dirname(chunkFileNames);
+		const chunksDirUrl = new URL(chunkPath + '/', buildConfig.server);
+		await fs.promises.rm(chunksDirUrl, { recursive: true, force: true });
+	} catch {}
+}
+
 export function netlifyEdgeFunctions({ dist }: NetlifyEdgeFunctionsOptions = {}): AstroIntegration {
 	let _config: AstroConfig;
 	let entryFile: string;
+	let _buildConfig: BuildConfig;
+	let _vite: any;
 	return {
 		name: '@astrojs/netlify/edge-functions',
 		hooks: {
@@ -80,6 +108,7 @@ export function netlifyEdgeFunctions({ dist }: NetlifyEdgeFunctionsOptions = {})
 				_config = config;
 			},
 			'astro:build:start': async ({ buildConfig }) => {
+				_buildConfig = buildConfig;
 				entryFile = buildConfig.serverEntry.replace(/\.m?js/, '');
 				buildConfig.client = _config.outDir;
 				buildConfig.server = new URL('./.netlify/edge-functions/', _config.root);
@@ -87,6 +116,7 @@ export function netlifyEdgeFunctions({ dist }: NetlifyEdgeFunctionsOptions = {})
 			},
 			'astro:build:setup': ({ vite, target }) => {
 				if (target === 'server') {
+					_vite = vite;
 					vite.resolve = vite.resolve || {};
 					vite.resolve.alias = vite.resolve.alias || {};
 
@@ -106,6 +136,7 @@ export function netlifyEdgeFunctions({ dist }: NetlifyEdgeFunctionsOptions = {})
 				}
 			},
 			'astro:build:done': async ({ routes, dir }) => {
+				await bundleServerEntry(_buildConfig, _vite);
 				await createEdgeManifest(routes, entryFile, _config.root);
 				await createRedirects(routes, dir, entryFile, true);
 			},
diff --git a/packages/integrations/netlify/test/edge-functions/deps.ts b/packages/integrations/netlify/test/edge-functions/deps.ts
index 1b23a94f74..498b7e09e6 100644
--- a/packages/integrations/netlify/test/edge-functions/deps.ts
+++ b/packages/integrations/netlify/test/edge-functions/deps.ts
@@ -2,3 +2,4 @@
 export { fromFileUrl } from 'https://deno.land/std@0.110.0/path/mod.ts';
 export { assertEquals, assert } from 'https://deno.land/std@0.132.0/testing/asserts.ts';
 export * from 'https://deno.land/x/deno_dom/deno-dom-wasm.ts';
+export * from 'https://deno.land/std@0.142.0/streams/conversion.ts';
diff --git a/packages/integrations/netlify/test/edge-functions/dynamic-import.test.js b/packages/integrations/netlify/test/edge-functions/dynamic-import.test.js
new file mode 100644
index 0000000000..d4c61fb1f9
--- /dev/null
+++ b/packages/integrations/netlify/test/edge-functions/dynamic-import.test.js
@@ -0,0 +1,27 @@
+// @ts-ignore
+import { runBuild, runApp } from './test-utils.ts';
+// @ts-ignore
+import { assertEquals, assert, DOMParser } from './deps.ts';
+
+// @ts-ignore
+Deno.test({
+	name: 'Dynamic imports',
+	async fn() {
+		let close = await runBuild('./fixtures/dynimport/');
+		let stop = await runApp('./fixtures/dynimport/prod.js');
+
+		try {
+			const response = await fetch('http://127.0.0.1:8085/');
+			assertEquals(response.status, 200);
+			const html = await response.text();
+
+			assert(html, 'got some html');
+			const doc = new DOMParser().parseFromString(html, `text/html`);
+			const div = doc.querySelector('#thing');
+			assert(div, 'div exists')
+		} finally {
+			await close();
+			await stop();
+		}
+	},
+});
diff --git a/packages/integrations/netlify/test/edge-functions/fixtures/dynimport/astro.config.mjs b/packages/integrations/netlify/test/edge-functions/fixtures/dynimport/astro.config.mjs
new file mode 100644
index 0000000000..c55135e43b
--- /dev/null
+++ b/packages/integrations/netlify/test/edge-functions/fixtures/dynimport/astro.config.mjs
@@ -0,0 +1,11 @@
+import { defineConfig } from 'astro/config';
+import { netlifyEdgeFunctions } from '@astrojs/netlify';
+
+export default defineConfig({
+	adapter: netlifyEdgeFunctions({
+		dist: new URL('./dist/', import.meta.url),
+	}),
+	experimental: {
+		ssr: true
+	}
+})
diff --git a/packages/integrations/netlify/test/edge-functions/fixtures/dynimport/package.json b/packages/integrations/netlify/test/edge-functions/fixtures/dynimport/package.json
new file mode 100644
index 0000000000..201a243d08
--- /dev/null
+++ b/packages/integrations/netlify/test/edge-functions/fixtures/dynimport/package.json
@@ -0,0 +1,9 @@
+{
+  "name": "@test/netlify-edge-astro-dynimport",
+  "version": "0.0.0",
+  "private": true,
+  "dependencies": {
+    "astro": "workspace:*",
+    "@astrojs/netlify": "workspace:*"
+  }
+}
diff --git a/packages/integrations/netlify/test/edge-functions/fixtures/dynimport/prod.js b/packages/integrations/netlify/test/edge-functions/fixtures/dynimport/prod.js
new file mode 100644
index 0000000000..3e7d6e64d4
--- /dev/null
+++ b/packages/integrations/netlify/test/edge-functions/fixtures/dynimport/prod.js
@@ -0,0 +1,11 @@
+import handler from './.netlify/edge-functions/entry.js';
+import { Server } from 'https://deno.land/std@0.132.0/http/server.ts';
+
+const _server = new Server({
+	port: 8085,
+	hostname: '0.0.0.0',
+	handler,
+});
+
+_server.listenAndServe();
+console.error(`Server running on port 8085`);
diff --git a/packages/integrations/netlify/test/edge-functions/fixtures/dynimport/src/components/Thing.astro b/packages/integrations/netlify/test/edge-functions/fixtures/dynimport/src/components/Thing.astro
new file mode 100644
index 0000000000..8d8ef929a0
--- /dev/null
+++ b/packages/integrations/netlify/test/edge-functions/fixtures/dynimport/src/components/Thing.astro
@@ -0,0 +1,4 @@
+---
+
+---
+<div id="thing">testing</div>
diff --git a/packages/integrations/netlify/test/edge-functions/fixtures/dynimport/src/pages/index.astro b/packages/integrations/netlify/test/edge-functions/fixtures/dynimport/src/pages/index.astro
new file mode 100644
index 0000000000..852cb62019
--- /dev/null
+++ b/packages/integrations/netlify/test/edge-functions/fixtures/dynimport/src/pages/index.astro
@@ -0,0 +1,11 @@
+---
+const { default: Thing } = await import('../components/Thing.astro');
+---
+<html>
+	<head>
+		<title>testing</title>
+	</head>
+	<body>
+		<Thing />
+	</body>
+</html>
diff --git a/packages/integrations/netlify/test/edge-functions/test-utils.ts b/packages/integrations/netlify/test/edge-functions/test-utils.ts
index 826f64d37c..72b411d383 100644
--- a/packages/integrations/netlify/test/edge-functions/test-utils.ts
+++ b/packages/integrations/netlify/test/edge-functions/test-utils.ts
@@ -1,5 +1,5 @@
 // @ts-ignore
-import { fromFileUrl } from './deps.ts';
+import { fromFileUrl, readableStreamFromReader } from './deps.ts';
 const dir = new URL('./', import.meta.url);
 
 export async function runBuild(fixturePath: string) {
@@ -11,3 +11,21 @@ export async function runBuild(fixturePath: string) {
 	await proc.status();
 	return async () => await proc.close();
 }
+
+export async function runApp(entryPath: string) {
+	const entryUrl = new URL(entryPath, dir)
+	let proc = Deno.run({
+		cmd: ['deno', 'run', '--allow-env', '--allow-net', fromFileUrl(entryUrl)],
+		//cwd: fromFileUrl(entryUrl),
+		stderr: 'piped'
+	});
+	const stderr = readableStreamFromReader(proc.stderr);
+	const dec = new TextDecoder();
+	for await(let bytes of stderr) {
+		let msg = dec.decode(bytes);
+		if(msg.includes(`Server running`)) {
+			break;
+		}
+	}
+	return () => proc.close();
+}
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index e3ed897d2e..b726e6ca02 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -1702,14 +1702,24 @@ importers:
       '@netlify/functions': ^1.0.0
       astro: workspace:*
       astro-scripts: workspace:*
+      esbuild: ^0.14.42
     dependencies:
       '@astrojs/webapi': link:../../webapi
+      esbuild: 0.14.42
     devDependencies:
       '@netlify/edge-handler-types': 0.34.1
       '@netlify/functions': 1.0.0
       astro: link:../../astro
       astro-scripts: link:../../../scripts
 
+  packages/integrations/netlify/test/edge-functions/fixtures/dynimport:
+    specifiers:
+      '@astrojs/netlify': workspace:*
+      astro: workspace:*
+    dependencies:
+      '@astrojs/netlify': link:../../../..
+      astro: link:../../../../../../astro
+
   packages/integrations/netlify/test/edge-functions/fixtures/edge-basic:
     specifiers:
       '@astrojs/netlify': workspace:*