From f3ab822e328725c3905b0adad9889ad37653c24a Mon Sep 17 00:00:00 2001 From: Matthew Phillips 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 000000000000..7f758e4b4b8d --- /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 6a94327e8e23..54cf23d2c5d3 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 9b2f197add6d..ff30831e46d8 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 e43a7f482f86..3ef39eecbc40 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 1b23a94f743b..498b7e09e639 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 000000000000..d4c61fb1f9cb --- /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 000000000000..c55135e43b7a --- /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 000000000000..201a243d08bd --- /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 000000000000..3e7d6e64d448 --- /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 000000000000..8d8ef929a003 --- /dev/null +++ b/packages/integrations/netlify/test/edge-functions/fixtures/dynimport/src/components/Thing.astro @@ -0,0 +1,4 @@ +--- + +--- +
testing
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 000000000000..852cb62019a0 --- /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'); +--- + + + testing + + + + + diff --git a/packages/integrations/netlify/test/edge-functions/test-utils.ts b/packages/integrations/netlify/test/edge-functions/test-utils.ts index 826f64d37cd5..72b411d38347 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 e3ed897d2ece..b726e6ca02b3 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:*