Remove the Vercel Edge adapter (#8015)

* Remove the Vercel Edge adapter

* Update tests

* Update .changeset/plenty-keys-add.md

Co-authored-by: Sarah Rainsberger <sarah@rainsberger.ca>

* Show enabling edge middleware

---------

Co-authored-by: Sarah Rainsberger <sarah@rainsberger.ca>
This commit is contained in:
Matthew Phillips 2023-08-10 13:12:13 -04:00 committed by GitHub
parent 34cb200216
commit 9cc4e48e6a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 27 additions and 237 deletions

View file

@ -0,0 +1,23 @@
---
'@astrojs/vercel': major
---
Remove the Vercel Edge adapter
`@astrojs/vercel/serverless` now supports Edge middleware, so a separate adapter for Edge itself (deploying your entire app to the edge) is no longer necessary. Please update your Astro config to reflect this change:
```diff
// astro.config.mjs
import { defineConfig } from 'astro/config';
- import vercel from '@astrojs/vercel/edge';
+ import vercel from '@astrojs/vercel/serverless';
export default defineConfig({
output: 'server',
adapter: vercel({
+ edgeMiddleware: true
}),
});
```
This adapter had several known limitations and compatibility issues that prevented many people from using it in production. To reduce maintenance costs and because we have a better story with Serveless + Edge Middleware, we are removing the Edge adapter.

View file

@ -58,16 +58,12 @@ If you prefer to install the adapter manually instead, complete the following tw
You can deploy to different targets:
- `edge`: SSR inside an [Edge function](https://vercel.com/docs/concepts/functions/edge-functions).
- `serverless`: SSR inside a [Node.js function](https://vercel.com/docs/concepts/functions/serverless-functions).
- `static`: generates a static website following Vercel's output formats, redirects, etc.
> **Note**: deploying to the Edge has [its limitations](https://vercel.com/docs/concepts/functions/edge-functions#known-limitations). An edge function can't be more than 1 MB in size and they don't support native Node.js APIs, among others.
You can change where to target by changing the import:
```js
import vercel from '@astrojs/vercel/edge';
import vercel from '@astrojs/vercel/serverless';
import vercel from '@astrojs/vercel/static';
```
@ -90,7 +86,7 @@ To configure this adapter, pass an object to the `vercel()` function call in `as
### analytics
**Type:** `boolean`<br>
**Available for:** Serverless, Edge, Static<br>
**Available for:** Serverless, Static<br>
**Added in:** `@astrojs/vercel@3.1.0`
You can enable [Vercel Analytics](https://vercel.com/analytics) (including Web Vitals and Audiences) by setting `analytics: true`. This will inject Vercels tracking scripts into all your pages.
@ -111,7 +107,7 @@ export default defineConfig({
### imagesConfig
**Type:** `VercelImageConfig`<br>
**Available for:** Edge, Serverless, Static
**Available for:** Serverless, Static
**Added in:** `@astrojs/vercel@3.3.0`
Configuration options for [Vercel's Image Optimization API](https://vercel.com/docs/concepts/image-optimization). See [Vercel's image configuration documentation](https://vercel.com/docs/build-output-api/v3/configuration#images) for a complete list of supported parameters.
@ -134,7 +130,7 @@ export default defineConfig({
### imageService
**Type:** `boolean`<br>
**Available for:** Edge, Serverless, Static
**Available for:** Serverless, Static
**Added in:** `@astrojs/vercel@3.3.0`
When enabled, an [Image Service](https://docs.astro.build/en/reference/image-service-reference/) powered by the Vercel Image Optimization API will be automatically configured and used in production. In development, a built-in Squoosh-based service will be used instead.
@ -175,7 +171,7 @@ import astroLogo from '../assets/logo.png';
### includeFiles
**Type:** `string[]`<br>
**Available for:** Edge, Serverless
**Available for:** Serverless
Use this property to force files to be bundled with your function. This is helpful when you notice missing files.
@ -192,9 +188,6 @@ export default defineConfig({
});
```
> **Note**
> When building for the Edge, all the dependencies get bundled in a single file to save space. **No extra file will be bundled**. So, if you _need_ some file inside the function, you have to specify it in `includeFiles`.
### excludeFiles
**Type:** `string[]`<br>

View file

@ -1,182 +0,0 @@
import type { AstroAdapter, AstroConfig, AstroIntegration } from 'astro';
import esbuild from 'esbuild';
import { relative as relativePath } from 'node:path';
import { fileURLToPath } from 'node:url';
import {
defaultImageConfig,
getImageConfig,
throwIfAssetsNotEnabled,
type VercelImageConfig,
} from '../image/shared.js';
import { exposeEnv } from '../lib/env.js';
import {
copyFilesToFunction,
getFilesFromFolder,
getVercelOutput,
removeDir,
writeJson,
} from '../lib/fs.js';
import { getRedirects } from '../lib/redirects.js';
const PACKAGE_NAME = '@astrojs/vercel/edge';
function getAdapter(): AstroAdapter {
return {
name: PACKAGE_NAME,
serverEntrypoint: `${PACKAGE_NAME}/entrypoint`,
exports: ['default'],
supportedAstroFeatures: {
hybridOutput: 'stable',
staticOutput: 'stable',
serverOutput: 'stable',
assets: {
supportKind: 'stable',
isSharpCompatible: false,
isSquooshCompatible: false,
},
},
};
}
export interface VercelEdgeConfig {
includeFiles?: string[];
analytics?: boolean;
imageService?: boolean;
imagesConfig?: VercelImageConfig;
}
export default function vercelEdge({
includeFiles = [],
analytics,
imageService,
imagesConfig,
}: VercelEdgeConfig = {}): AstroIntegration {
let _config: AstroConfig;
let buildTempFolder: URL;
let functionFolder: URL;
let serverEntry: string;
return {
name: PACKAGE_NAME,
hooks: {
'astro:config:setup': ({ command, config, updateConfig, injectScript }) => {
if (command === 'build' && analytics) {
injectScript('page', 'import "@astrojs/vercel/analytics"');
}
const outDir = getVercelOutput(config.root);
const viteDefine = exposeEnv(['VERCEL_ANALYTICS_ID']);
updateConfig({
outDir,
build: {
serverEntry: 'entry.mjs',
client: new URL('./static/', outDir),
server: new URL('./dist/', config.root),
},
vite: {
define: viteDefine,
ssr: {
external: ['@vercel/nft'],
},
},
...getImageConfig(imageService, imagesConfig, command),
});
},
'astro:config:done': ({ setAdapter, config }) => {
throwIfAssetsNotEnabled(config, imageService);
setAdapter(getAdapter());
_config = config;
buildTempFolder = config.build.server;
functionFolder = new URL('./functions/render.func/', config.outDir);
serverEntry = config.build.serverEntry;
if (config.output === 'static') {
throw new Error(`
[@astrojs/vercel] \`output: "server"\` or \`output: "hybrid"\` is required to use the edge adapter.
`);
}
},
'astro:build:setup': ({ vite, target }) => {
if (target === 'server') {
vite.resolve ||= {};
vite.resolve.alias ||= {};
const aliases = [{ find: 'react-dom/server', replacement: 'react-dom/server.browser' }];
if (Array.isArray(vite.resolve.alias)) {
vite.resolve.alias = [...vite.resolve.alias, ...aliases];
} else {
for (const alias of aliases) {
(vite.resolve.alias as Record<string, string>)[alias.find] = alias.replacement;
}
}
vite.ssr ||= {};
vite.ssr.target = 'webworker';
// Vercel edge runtime is a special webworker-ish environment that supports process.env,
// but Vite would replace away `process.env` in webworkers, so we set a define here to prevent it
vite.define = {
'process.env': 'process.env',
...vite.define,
};
}
},
'astro:build:done': async ({ routes }) => {
const entry = new URL(serverEntry, buildTempFolder);
const generatedFiles = await getFilesFromFolder(buildTempFolder);
const entryPath = fileURLToPath(entry);
await esbuild.build({
target: 'es2020',
platform: 'browser',
// https://runtime-keys.proposal.wintercg.org/#edge-light
conditions: ['edge-light', 'worker', 'browser'],
entryPoints: [entryPath],
outfile: entryPath,
allowOverwrite: true,
format: 'esm',
bundle: true,
minify: true,
});
// Copy entry and other server files
const commonAncestor = await copyFilesToFunction(
[...generatedFiles, ...includeFiles.map((file) => new URL(file, _config.root))],
functionFolder
);
// Remove temporary folder
await removeDir(buildTempFolder);
// Edge function config
// https://vercel.com/docs/build-output-api/v3#vercel-primitives/edge-functions/configuration
await writeJson(new URL(`./.vc-config.json`, functionFolder), {
runtime: 'edge',
entrypoint: relativePath(commonAncestor, entryPath),
});
// Output configuration
// https://vercel.com/docs/build-output-api/v3#build-output-configuration
await writeJson(new URL(`./config.json`, _config.outDir), {
version: 3,
routes: [
...getRedirects(routes, _config),
{
src: `^/${_config.build.assets}/(.*)$`,
headers: { 'cache-control': 'public, max-age=31536000, immutable' },
continue: true,
},
{ handle: 'filesystem' },
{ src: '/.*', dest: 'render' },
],
...(imageService || imagesConfig
? { images: imagesConfig ? imagesConfig : defaultImageConfig }
: {}),
});
},
},
};
}

View file

@ -1,28 +0,0 @@
// NOTE(fks): Side-effect -- shim.js must run first. This isn't guaranteed by
// the language, but it is a Node.js behavior that we rely on here. Keep this
// separate from the other imports so that it doesn't get organized & reordered.
import './shim.js';
// Normal Imports
import type { SSRManifest } from 'astro';
import { App } from 'astro/app';
const clientAddressSymbol = Symbol.for('astro.clientAddress');
export function createExports(manifest: SSRManifest) {
const app = new App(manifest);
const handler = async (request: Request): Promise<Response> => {
const routeData = app.match(request);
Reflect.set(request, clientAddressSymbol, request.headers.get('x-forwarded-for'));
const response = await app.render(request, routeData);
if (app.setCookieHeaders) {
for (const setCookieHeader of app.setCookieHeaders(response)) {
response.headers.append('Set-Cookie', setCookieHeader);
}
}
return response;
};
return { default: handler };
}

View file

@ -1 +0,0 @@
process.argv = [];

View file

@ -66,19 +66,4 @@ describe('Static Assets', () => {
checkValidCacheControl(assets);
});
});
describe('edge adapter', async () => {
const adapter = await import('@astrojs/vercel/edge');
it('has cache control', async () => {
await build({ adapter });
checkValidCacheControl();
});
it('has cache control other assets', async () => {
const assets = '_foo';
await build({ adapter, assets });
checkValidCacheControl(assets);
});
});
});