diff options
author | 2022-04-19 11:22:15 -0400 | |
---|---|---|
committer | 2022-04-19 11:22:15 -0400 | |
commit | 4cf54c60aa63bd614b242da0602790015005673d (patch) | |
tree | 25fcec1da64890044742a1090b1773076a0d43e9 /packages/integrations/netlify/src | |
parent | c35e94f5443d4ade07ff787d39b042eb3b9004fb (diff) | |
download | astro-4cf54c60aa63bd614b242da0602790015005673d.tar.gz astro-4cf54c60aa63bd614b242da0602790015005673d.tar.zst astro-4cf54c60aa63bd614b242da0602790015005673d.zip |
Netlify Edge function support (#3148)
* Netlify Edge function support
* Update readme with edge function information
* Adds a changeset
* Disable running edge function test in CI for now
Diffstat (limited to 'packages/integrations/netlify/src')
6 files changed, 195 insertions, 112 deletions
diff --git a/packages/integrations/netlify/src/README.md b/packages/integrations/netlify/src/README.md deleted file mode 100644 index 1a77ed598..000000000 --- a/packages/integrations/netlify/src/README.md +++ /dev/null @@ -1,46 +0,0 @@ -# @astrojs/netlify - -Deploy your server-side rendered (SSR) Astro app to [Netlify](https://www.netlify.com/). - -Use this adapter in your Astro configuration file, alongside a valid deployment URL: - -```js -import { defineConfig } from 'astro/config'; -import netlify from '@astrojs/netlify/functions'; - -export default defineConfig({ - adapter: netlify(), -}); -``` - -After you build your site the `netlify/` folder will contain [Netlify Functions](https://docs.netlify.com/functions/overview/) in the `netlify/functions/` folder. - -Now you can deploy! - -```shell -netlify deploy -``` - -## Configuration - -### dist - -We build to a `netlify` directory at the base of your project. To change this, use the `dist` option: - -```js -import { defineConfig } from 'astro/config'; -import netlify from '@astrojs/netlify/functions'; - -export default defineConfig({ - adapter: netlify({ - dist: new URL('./dist/', import.meta.url) - }) -}); -``` - -And then point to the dist in your `netlify.toml`: - -```toml -[functions] - directory = "dist/functions" -``` diff --git a/packages/integrations/netlify/src/edge-shim.ts b/packages/integrations/netlify/src/edge-shim.ts new file mode 100644 index 000000000..1a4a6ee9b --- /dev/null +++ b/packages/integrations/netlify/src/edge-shim.ts @@ -0,0 +1,4 @@ +(globalThis as any).process = { + argv: [], + env: {}, +}; diff --git a/packages/integrations/netlify/src/index.ts b/packages/integrations/netlify/src/index.ts index c473de67a..121495652 100644 --- a/packages/integrations/netlify/src/index.ts +++ b/packages/integrations/netlify/src/index.ts @@ -1,65 +1,8 @@ -import type { AstroAdapter, AstroIntegration, AstroConfig } from 'astro'; -import fs from 'fs'; - -export function getAdapter(): AstroAdapter { - return { - name: '@astrojs/netlify', - serverEntrypoint: '@astrojs/netlify/netlify-functions.js', - exports: ['handler'], - args: {}, - }; -} - -interface NetlifyFunctionsOptions { - dist?: URL; -} - -function netlifyFunctions({ dist }: NetlifyFunctionsOptions = {}): AstroIntegration { - let _config: AstroConfig; - let entryFile: string; - return { - name: '@astrojs/netlify', - hooks: { - 'astro:config:setup': ({ config }) => { - if (dist) { - config.outDir = dist; - } else { - config.outDir = new URL('./netlify/', config.root); - } - }, - 'astro:config:done': ({ config, setAdapter }) => { - setAdapter(getAdapter()); - _config = config; - }, - 'astro:build:start': async ({ buildConfig }) => { - entryFile = buildConfig.serverEntry.replace(/\.m?js/, ''); - buildConfig.client = _config.outDir; - buildConfig.server = new URL('./functions/', _config.outDir); - }, - 'astro:build:done': async ({ routes, dir }) => { - const _redirectsURL = new URL('./_redirects', dir); - - // Create the redirects file that is used for routing. - let _redirects = ''; - for (const route of routes) { - if (route.pathname) { - _redirects += ` -${route.pathname} /.netlify/functions/${entryFile} 200`; - } else { - const pattern = - '/' + route.segments.map(([part]) => (part.dynamic ? '*' : part.content)).join('/'); - _redirects += ` -${pattern} /.netlify/functions/${entryFile} 200`; - } - } - - // Always use appendFile() because the redirects file could already exist, - // e.g. due to a `/public/_redirects` file that got copied to the output dir. - // If the file does not exist yet, appendFile() automatically creates it. - await fs.promises.appendFile(_redirectsURL, _redirects, 'utf-8'); - }, - }, - }; -} - -export { netlifyFunctions, netlifyFunctions as default }; +export { + netlifyFunctions, + netlifyFunctions as default +} from './integration-functions.js'; + +export { + netlifyEdgeFunctions +} from './integration-edge-functions.js'; diff --git a/packages/integrations/netlify/src/integration-edge-functions.ts b/packages/integrations/netlify/src/integration-edge-functions.ts new file mode 100644 index 000000000..fcce820f4 --- /dev/null +++ b/packages/integrations/netlify/src/integration-edge-functions.ts @@ -0,0 +1,98 @@ +import type { AstroAdapter, AstroIntegration, AstroConfig, RouteData } from 'astro'; +import * as fs from 'fs'; + +export function getAdapter(): AstroAdapter { + return { + name: '@astrojs/netlify/edge-functions', + serverEntrypoint: '@astrojs/netlify/netlify-edge-functions.js', + exports: ['default'], + }; +} + +interface NetlifyEdgeFunctionsOptions { + dist?: URL; +} + +interface NetlifyEdgeFunctionManifestFunctionPath { + function: string; + path: string; +} + +interface NetlifyEdgeFunctionManifestFunctionPattern { + function: string; + pattern: string; +} + +type NetlifyEdgeFunctionManifestFunction = NetlifyEdgeFunctionManifestFunctionPath | NetlifyEdgeFunctionManifestFunctionPattern; + +interface NetlifyEdgeFunctionManifest { + functions: NetlifyEdgeFunctionManifestFunction[]; + version: 1; +} + +async function createEdgeManifest(routes: RouteData[], entryFile: string, dir: URL) { + const functions: NetlifyEdgeFunctionManifestFunction[] = []; + for(const route of routes) { + if(route.pathname) { + functions.push({ + function: entryFile, + path: route.pathname + }); + } else { + functions.push({ + function: entryFile, + pattern: route.pattern.source + }); + } + } + + const manifest: NetlifyEdgeFunctionManifest = { + functions, + version: 1 + }; + + const manifestURL = new URL('./manifest.json', dir); + const _manifest = JSON.stringify(manifest, null, ' '); + await fs.promises.writeFile(manifestURL, _manifest, 'utf-8'); +} + +export function netlifyEdgeFunctions({ dist }: NetlifyEdgeFunctionsOptions = {}): AstroIntegration { + let _config: AstroConfig; + let entryFile: string; + return { + name: '@astrojs/netlify/edge-functions', + hooks: { + 'astro:config:setup': ({ config }) => { + if (dist) { + config.outDir = dist; + } else { + config.outDir = new URL('./netlify/', config.root); + } + }, + 'astro:config:done': ({ config, setAdapter }) => { + setAdapter(getAdapter()); + _config = config; + }, + 'astro:build:start': async ({ buildConfig }) => { + entryFile = buildConfig.serverEntry.replace(/\.m?js/, ''); + buildConfig.client = _config.outDir; + buildConfig.server = new URL('./edge-functions/', _config.outDir); + }, + 'astro:build:setup': ({ vite, target }) => { + if (target === 'server') { + vite.ssr = { + noExternal: true, + }; + } + }, + 'astro:build:done': async ({ routes, dir }) => { + + await createEdgeManifest(routes, entryFile, new URL('./edge-functions/', dir)); + }, + }, + }; +} + +export { + netlifyEdgeFunctions as default +} diff --git a/packages/integrations/netlify/src/integration-functions.ts b/packages/integrations/netlify/src/integration-functions.ts index 5b687cda4..2720eb591 100644 --- a/packages/integrations/netlify/src/integration-functions.ts +++ b/packages/integrations/netlify/src/integration-functions.ts @@ -1 +1,65 @@ -export { netlifyFunctions as default } from './index.js'; +import type { AstroAdapter, AstroIntegration, AstroConfig } from 'astro'; +import fs from 'fs'; + +export function getAdapter(): AstroAdapter { + return { + name: '@astrojs/netlify/functions', + serverEntrypoint: '@astrojs/netlify/netlify-functions.js', + exports: ['handler'], + args: {}, + }; +} + +interface NetlifyFunctionsOptions { + dist?: URL; +} + +function netlifyFunctions({ dist }: NetlifyFunctionsOptions = {}): AstroIntegration { + let _config: AstroConfig; + let entryFile: string; + return { + name: '@astrojs/netlify', + hooks: { + 'astro:config:setup': ({ config }) => { + if (dist) { + config.outDir = dist; + } else { + config.outDir = new URL('./netlify/', config.root); + } + }, + 'astro:config:done': ({ config, setAdapter }) => { + setAdapter(getAdapter()); + _config = config; + }, + 'astro:build:start': async ({ buildConfig }) => { + entryFile = buildConfig.serverEntry.replace(/\.m?js/, ''); + buildConfig.client = _config.outDir; + buildConfig.server = new URL('./functions/', _config.outDir); + }, + 'astro:build:done': async ({ routes, dir }) => { + const _redirectsURL = new URL('./_redirects', dir); + + // Create the redirects file that is used for routing. + let _redirects = ''; + for (const route of routes) { + if (route.pathname) { + _redirects += ` +${route.pathname} /.netlify/functions/${entryFile} 200`; + } else { + const pattern = + '/' + route.segments.map(([part]) => (part.dynamic ? '*' : part.content)).join('/'); + _redirects += ` +${pattern} /.netlify/functions/${entryFile} 200`; + } + } + + // Always use appendFile() because the redirects file could already exist, + // e.g. due to a `/public/_redirects` file that got copied to the output dir. + // If the file does not exist yet, appendFile() automatically creates it. + await fs.promises.appendFile(_redirectsURL, _redirects, 'utf-8'); + }, + }, + }; +} + +export { netlifyFunctions, netlifyFunctions as default }; diff --git a/packages/integrations/netlify/src/netlify-edge-functions.ts b/packages/integrations/netlify/src/netlify-edge-functions.ts new file mode 100644 index 000000000..f7000442c --- /dev/null +++ b/packages/integrations/netlify/src/netlify-edge-functions.ts @@ -0,0 +1,20 @@ +import './edge-shim.js'; +import { SSRManifest } from 'astro'; +import { App } from 'astro/app'; + +export function createExports(manifest: SSRManifest) { + const app = new App(manifest); + + const handler = async (request: Request): Promise<Response> => { + if(app.match(request)) { + return app.render(request); + } + + return new Response(null, { + status: 404, + statusText: 'Not found' + }); + }; + + return { 'default': handler }; +} |