summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.changeset/twelve-parrots-report.md5
-rw-r--r--.gitignore1
-rw-r--r--.prettierignore1
-rw-r--r--examples/blog-multiple-authors/.gitignore3
-rw-r--r--examples/blog/.gitignore3
-rw-r--r--examples/component/.gitignore3
-rw-r--r--examples/docs/.gitignore3
-rw-r--r--examples/env-vars/.gitignore3
-rw-r--r--examples/framework-alpine/.gitignore3
-rw-r--r--examples/framework-lit/.gitignore3
-rw-r--r--examples/framework-multiple/.gitignore3
-rw-r--r--examples/framework-preact/.gitignore3
-rw-r--r--examples/framework-react/.gitignore3
-rw-r--r--examples/framework-solid/.gitignore3
-rw-r--r--examples/framework-svelte/.gitignore3
-rw-r--r--examples/framework-vue/.gitignore3
-rw-r--r--examples/integrations-playground/.gitignore3
-rw-r--r--examples/minimal/.gitignore3
-rw-r--r--examples/non-html-pages/.gitignore3
-rw-r--r--examples/portfolio/.gitignore3
-rw-r--r--examples/starter/.gitignore3
-rw-r--r--examples/subpath/.gitignore3
-rw-r--r--examples/with-markdown-plugins/.gitignore3
-rw-r--r--examples/with-markdown-shiki/.gitignore3
-rw-r--r--examples/with-markdown/.gitignore3
-rw-r--r--examples/with-nanostores/.gitignore3
-rw-r--r--examples/with-tailwindcss/.gitignore3
-rw-r--r--examples/with-vite-plugin-pwa/.gitignore3
-rw-r--r--packages/astro/src/core/build/vite-plugin-ssr.ts1
-rw-r--r--packages/integrations/vercel/README.md22
-rw-r--r--packages/integrations/vercel/package.json33
-rw-r--r--packages/integrations/vercel/src/index.ts66
-rw-r--r--packages/integrations/vercel/src/request-transform.ts95
-rw-r--r--packages/integrations/vercel/src/server-entrypoint.ts34
-rw-r--r--packages/integrations/vercel/tsconfig.json10
-rw-r--r--pnpm-lock.yaml13
36 files changed, 331 insertions, 25 deletions
diff --git a/.changeset/twelve-parrots-report.md b/.changeset/twelve-parrots-report.md
new file mode 100644
index 000000000..6531e60b4
--- /dev/null
+++ b/.changeset/twelve-parrots-report.md
@@ -0,0 +1,5 @@
+---
+'@astrojs/vercel': patch
+---
+
+Add a Vercel adapter for SSR
diff --git a/.gitignore b/.gitignore
index 2185b7f84..6a837da1d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,5 +1,6 @@
node_modules/
dist/
+.output/
*.tsbuildinfo
.DS_Store
.vercel
diff --git a/.prettierignore b/.prettierignore
index 7b6398b3e..7c89fbaf5 100644
--- a/.prettierignore
+++ b/.prettierignore
@@ -4,6 +4,7 @@
# Deep Directories
**/dist
+**/.output
**/smoke
**/node_modules
**/fixtures
diff --git a/examples/blog-multiple-authors/.gitignore b/examples/blog-multiple-authors/.gitignore
index 7742642c1..7329a851d 100644
--- a/examples/blog-multiple-authors/.gitignore
+++ b/examples/blog-multiple-authors/.gitignore
@@ -1,5 +1,6 @@
# build output
-dist
+dist/
+.output/
# dependencies
node_modules/
diff --git a/examples/blog/.gitignore b/examples/blog/.gitignore
index 7742642c1..7329a851d 100644
--- a/examples/blog/.gitignore
+++ b/examples/blog/.gitignore
@@ -1,5 +1,6 @@
# build output
-dist
+dist/
+.output/
# dependencies
node_modules/
diff --git a/examples/component/.gitignore b/examples/component/.gitignore
index 7742642c1..7329a851d 100644
--- a/examples/component/.gitignore
+++ b/examples/component/.gitignore
@@ -1,5 +1,6 @@
# build output
-dist
+dist/
+.output/
# dependencies
node_modules/
diff --git a/examples/docs/.gitignore b/examples/docs/.gitignore
index 7742642c1..7329a851d 100644
--- a/examples/docs/.gitignore
+++ b/examples/docs/.gitignore
@@ -1,5 +1,6 @@
# build output
-dist
+dist/
+.output/
# dependencies
node_modules/
diff --git a/examples/env-vars/.gitignore b/examples/env-vars/.gitignore
index 7742642c1..7329a851d 100644
--- a/examples/env-vars/.gitignore
+++ b/examples/env-vars/.gitignore
@@ -1,5 +1,6 @@
# build output
-dist
+dist/
+.output/
# dependencies
node_modules/
diff --git a/examples/framework-alpine/.gitignore b/examples/framework-alpine/.gitignore
index 7742642c1..7329a851d 100644
--- a/examples/framework-alpine/.gitignore
+++ b/examples/framework-alpine/.gitignore
@@ -1,5 +1,6 @@
# build output
-dist
+dist/
+.output/
# dependencies
node_modules/
diff --git a/examples/framework-lit/.gitignore b/examples/framework-lit/.gitignore
index 7742642c1..7329a851d 100644
--- a/examples/framework-lit/.gitignore
+++ b/examples/framework-lit/.gitignore
@@ -1,5 +1,6 @@
# build output
-dist
+dist/
+.output/
# dependencies
node_modules/
diff --git a/examples/framework-multiple/.gitignore b/examples/framework-multiple/.gitignore
index 7742642c1..7329a851d 100644
--- a/examples/framework-multiple/.gitignore
+++ b/examples/framework-multiple/.gitignore
@@ -1,5 +1,6 @@
# build output
-dist
+dist/
+.output/
# dependencies
node_modules/
diff --git a/examples/framework-preact/.gitignore b/examples/framework-preact/.gitignore
index 7742642c1..7329a851d 100644
--- a/examples/framework-preact/.gitignore
+++ b/examples/framework-preact/.gitignore
@@ -1,5 +1,6 @@
# build output
-dist
+dist/
+.output/
# dependencies
node_modules/
diff --git a/examples/framework-react/.gitignore b/examples/framework-react/.gitignore
index 7742642c1..7329a851d 100644
--- a/examples/framework-react/.gitignore
+++ b/examples/framework-react/.gitignore
@@ -1,5 +1,6 @@
# build output
-dist
+dist/
+.output/
# dependencies
node_modules/
diff --git a/examples/framework-solid/.gitignore b/examples/framework-solid/.gitignore
index 7742642c1..7329a851d 100644
--- a/examples/framework-solid/.gitignore
+++ b/examples/framework-solid/.gitignore
@@ -1,5 +1,6 @@
# build output
-dist
+dist/
+.output/
# dependencies
node_modules/
diff --git a/examples/framework-svelte/.gitignore b/examples/framework-svelte/.gitignore
index 7742642c1..7329a851d 100644
--- a/examples/framework-svelte/.gitignore
+++ b/examples/framework-svelte/.gitignore
@@ -1,5 +1,6 @@
# build output
-dist
+dist/
+.output/
# dependencies
node_modules/
diff --git a/examples/framework-vue/.gitignore b/examples/framework-vue/.gitignore
index 7742642c1..7329a851d 100644
--- a/examples/framework-vue/.gitignore
+++ b/examples/framework-vue/.gitignore
@@ -1,5 +1,6 @@
# build output
-dist
+dist/
+.output/
# dependencies
node_modules/
diff --git a/examples/integrations-playground/.gitignore b/examples/integrations-playground/.gitignore
index 7742642c1..7329a851d 100644
--- a/examples/integrations-playground/.gitignore
+++ b/examples/integrations-playground/.gitignore
@@ -1,5 +1,6 @@
# build output
-dist
+dist/
+.output/
# dependencies
node_modules/
diff --git a/examples/minimal/.gitignore b/examples/minimal/.gitignore
index 7742642c1..7329a851d 100644
--- a/examples/minimal/.gitignore
+++ b/examples/minimal/.gitignore
@@ -1,5 +1,6 @@
# build output
-dist
+dist/
+.output/
# dependencies
node_modules/
diff --git a/examples/non-html-pages/.gitignore b/examples/non-html-pages/.gitignore
index 7742642c1..7329a851d 100644
--- a/examples/non-html-pages/.gitignore
+++ b/examples/non-html-pages/.gitignore
@@ -1,5 +1,6 @@
# build output
-dist
+dist/
+.output/
# dependencies
node_modules/
diff --git a/examples/portfolio/.gitignore b/examples/portfolio/.gitignore
index 7742642c1..7329a851d 100644
--- a/examples/portfolio/.gitignore
+++ b/examples/portfolio/.gitignore
@@ -1,5 +1,6 @@
# build output
-dist
+dist/
+.output/
# dependencies
node_modules/
diff --git a/examples/starter/.gitignore b/examples/starter/.gitignore
index 7742642c1..7329a851d 100644
--- a/examples/starter/.gitignore
+++ b/examples/starter/.gitignore
@@ -1,5 +1,6 @@
# build output
-dist
+dist/
+.output/
# dependencies
node_modules/
diff --git a/examples/subpath/.gitignore b/examples/subpath/.gitignore
index 7742642c1..7329a851d 100644
--- a/examples/subpath/.gitignore
+++ b/examples/subpath/.gitignore
@@ -1,5 +1,6 @@
# build output
-dist
+dist/
+.output/
# dependencies
node_modules/
diff --git a/examples/with-markdown-plugins/.gitignore b/examples/with-markdown-plugins/.gitignore
index 7742642c1..7329a851d 100644
--- a/examples/with-markdown-plugins/.gitignore
+++ b/examples/with-markdown-plugins/.gitignore
@@ -1,5 +1,6 @@
# build output
-dist
+dist/
+.output/
# dependencies
node_modules/
diff --git a/examples/with-markdown-shiki/.gitignore b/examples/with-markdown-shiki/.gitignore
index 7742642c1..7329a851d 100644
--- a/examples/with-markdown-shiki/.gitignore
+++ b/examples/with-markdown-shiki/.gitignore
@@ -1,5 +1,6 @@
# build output
-dist
+dist/
+.output/
# dependencies
node_modules/
diff --git a/examples/with-markdown/.gitignore b/examples/with-markdown/.gitignore
index 7742642c1..7329a851d 100644
--- a/examples/with-markdown/.gitignore
+++ b/examples/with-markdown/.gitignore
@@ -1,5 +1,6 @@
# build output
-dist
+dist/
+.output/
# dependencies
node_modules/
diff --git a/examples/with-nanostores/.gitignore b/examples/with-nanostores/.gitignore
index 7742642c1..7329a851d 100644
--- a/examples/with-nanostores/.gitignore
+++ b/examples/with-nanostores/.gitignore
@@ -1,5 +1,6 @@
# build output
-dist
+dist/
+.output/
# dependencies
node_modules/
diff --git a/examples/with-tailwindcss/.gitignore b/examples/with-tailwindcss/.gitignore
index 7742642c1..7329a851d 100644
--- a/examples/with-tailwindcss/.gitignore
+++ b/examples/with-tailwindcss/.gitignore
@@ -1,5 +1,6 @@
# build output
-dist
+dist/
+.output/
# dependencies
node_modules/
diff --git a/examples/with-vite-plugin-pwa/.gitignore b/examples/with-vite-plugin-pwa/.gitignore
index 7742642c1..7329a851d 100644
--- a/examples/with-vite-plugin-pwa/.gitignore
+++ b/examples/with-vite-plugin-pwa/.gitignore
@@ -1,5 +1,6 @@
# build output
-dist
+dist/
+.output/
# dependencies
node_modules/
diff --git a/packages/astro/src/core/build/vite-plugin-ssr.ts b/packages/astro/src/core/build/vite-plugin-ssr.ts
index c0243f717..93c53b99b 100644
--- a/packages/astro/src/core/build/vite-plugin-ssr.ts
+++ b/packages/astro/src/core/build/vite-plugin-ssr.ts
@@ -46,6 +46,7 @@ ${
adapter.exports
? `const _exports = adapter.createExports(_manifest, _args);
${adapter.exports.map((name) => `export const ${name} = _exports['${name}'];`).join('\n')}
+${adapter.exports.includes('_default') ? `export default _default` : ''}
`
: ''
}
diff --git a/packages/integrations/vercel/README.md b/packages/integrations/vercel/README.md
new file mode 100644
index 000000000..7b8aae035
--- /dev/null
+++ b/packages/integrations/vercel/README.md
@@ -0,0 +1,22 @@
+# @astrojs/vercel
+
+Deploy your server-side rendered (SSR) Astro app to [Vercel](https://www.vercel.com/).
+
+Use this integration in your Astro configuration file:
+
+```js
+import { defineConfig } from 'astro/config';
+import vercel from '@astrojs/vercel';
+
+export default defineConfig({
+ adapter: vercel()
+});
+```
+
+After you build your site the `.output/` folder will contain your server-side rendered app. Since this feature is still in beta, you'll **need to add this Enviroment Variable to your Vercel project**: `ENABLE_FILE_SYSTEM_API=1`
+
+Now you can deploy!
+
+```shell
+vercel
+```
diff --git a/packages/integrations/vercel/package.json b/packages/integrations/vercel/package.json
new file mode 100644
index 000000000..229a48b8e
--- /dev/null
+++ b/packages/integrations/vercel/package.json
@@ -0,0 +1,33 @@
+{
+ "name": "@astrojs/vercel",
+ "description": "Deploy your site to Vercel",
+ "version": "0.0.1",
+ "type": "module",
+ "types": "./dist/index.d.ts",
+ "author": "withastro",
+ "license": "MIT",
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/withastro/astro.git",
+ "directory": "packages/integrations/vercel"
+ },
+ "bugs": "https://github.com/withastro/astro/issues",
+ "homepage": "https://astro.build",
+ "exports": {
+ ".": "./dist/index.js",
+ "./server-entrypoint": "./dist/server-entrypoint.js",
+ "./package.json": "./package.json"
+ },
+ "scripts": {
+ "build": "astro-scripts build \"src/**/*.ts\" && tsc",
+ "dev": "astro-scripts dev \"src/**/*.ts\""
+ },
+ "dependencies": {
+ "@astrojs/webapi": "^0.11.0",
+ "esbuild": "0.14.25"
+ },
+ "devDependencies": {
+ "astro": "workspace:*",
+ "astro-scripts": "workspace:*"
+ }
+}
diff --git a/packages/integrations/vercel/src/index.ts b/packages/integrations/vercel/src/index.ts
new file mode 100644
index 000000000..49a924b2d
--- /dev/null
+++ b/packages/integrations/vercel/src/index.ts
@@ -0,0 +1,66 @@
+import type { AstroAdapter, AstroConfig, AstroIntegration } from 'astro';
+import type { PathLike } from 'fs';
+import fs from 'fs/promises';
+import esbuild from 'esbuild';
+import { fileURLToPath } from 'url';
+
+const writeJson = (path: PathLike, data: any) =>
+ fs.writeFile(path, JSON.stringify(data), { encoding: 'utf-8' });
+
+const ENTRYFILE = '__astro_entry';
+
+export function getAdapter(): AstroAdapter {
+ return {
+ name: '@astrojs/vercel',
+ serverEntrypoint: '@astrojs/vercel/server-entrypoint',
+ exports: ['_default'],
+ };
+}
+
+export default function vercel(): AstroIntegration {
+ let _config: AstroConfig;
+ return {
+ name: '@astrojs/vercel',
+ hooks: {
+ 'astro:config:setup': ({ config }) => {
+ config.outDir = new URL('./.output/', config.outDir);
+ config.build.format = 'directory';
+ },
+ 'astro:config:done': ({ setAdapter, config }) => {
+ setAdapter(getAdapter());
+ _config = config;
+ },
+ 'astro:build:start': async ({ buildConfig }) => {
+ buildConfig.serverEntry = `${ENTRYFILE}.mjs`;
+ buildConfig.client = new URL('./static/', _config.outDir);
+ buildConfig.server = new URL('./server/pages/', _config.outDir);
+ },
+ 'astro:build:done': async ({ dir, routes }) => {
+ const pagesDir = new URL('./server/pages/', dir);
+
+ // Convert server entry to CommonJS
+ await esbuild.build({
+ entryPoints: [fileURLToPath(new URL(`./${ENTRYFILE}.mjs`, pagesDir))],
+ outfile: fileURLToPath(new URL(`./${ENTRYFILE}.js`, pagesDir)),
+ bundle: true,
+ format: 'cjs',
+ platform: 'node',
+ target: 'node14',
+ });
+ await fs.rm(new URL(`./${ENTRYFILE}.mjs`, pagesDir));
+
+ // Routes Manifest
+ // https://vercel.com/docs/file-system-api#configuration/routes
+ await writeJson(new URL(`./routes-manifest.json`, dir), {
+ version: 3,
+ basePath: '/',
+ pages404: false,
+ rewrites: routes.map((route) => ({
+ source: route.pathname,
+ destination: `/${ENTRYFILE}`,
+ })),
+ });
+ },
+ },
+ };
+}
diff --git a/packages/integrations/vercel/src/request-transform.ts b/packages/integrations/vercel/src/request-transform.ts
new file mode 100644
index 000000000..0a87ca642
--- /dev/null
+++ b/packages/integrations/vercel/src/request-transform.ts
@@ -0,0 +1,95 @@
+import { Readable } from 'stream';
+import type { IncomingMessage, ServerResponse } from 'http';
+
+/*
+ Credits to the SvelteKit team
+ https://github.com/sveltejs/kit/blob/69913e9fda054fa6a62a80e2bb4ee7dca1005796/packages/kit/src/node.js
+*/
+
+function get_raw_body(req: IncomingMessage) {
+ return new Promise<Uint8Array | null>((fulfil, reject) => {
+ const h = req.headers;
+
+ if (!h['content-type']) {
+ return fulfil(null);
+ }
+
+ req.on('error', reject);
+
+ const length = Number(h['content-length']);
+
+ // https://github.com/jshttp/type-is/blob/c1f4388c71c8a01f79934e68f630ca4a15fffcd6/index.js#L81-L95
+ if (isNaN(length) && h['transfer-encoding'] == null) {
+ return fulfil(null);
+ }
+
+ let data = new Uint8Array(length || 0);
+
+ if (length > 0) {
+ let offset = 0;
+ req.on('data', (chunk) => {
+ const new_len = offset + Buffer.byteLength(chunk);
+
+ if (new_len > length) {
+ return reject({
+ status: 413,
+ reason: 'Exceeded "Content-Length" limit',
+ });
+ }
+
+ data.set(chunk, offset);
+ offset = new_len;
+ });
+ } else {
+ req.on('data', (chunk) => {
+ const new_data = new Uint8Array(data.length + chunk.length);
+ new_data.set(data, 0);
+ new_data.set(chunk, data.length);
+ data = new_data;
+ });
+ }
+
+ req.on('end', () => {
+ fulfil(data);
+ });
+ });
+}
+
+export async function getRequest(base: string, req: IncomingMessage): Promise<Request> {
+ let headers = req.headers as Record<string, string>;
+ if (req.httpVersionMajor === 2) {
+ // we need to strip out the HTTP/2 pseudo-headers because node-fetch's
+ // Request implementation doesn't like them
+ headers = Object.assign({}, headers);
+ delete headers[':method'];
+ delete headers[':path'];
+ delete headers[':authority'];
+ delete headers[':scheme'];
+ }
+ return new Request(base + req.url, {
+ method: req.method,
+ headers,
+ body: await get_raw_body(req), // TODO stream rather than buffer
+ });
+}
+
+export async function setResponse(res: ServerResponse, response: Response): Promise<void> {
+ const headers = Object.fromEntries(response.headers);
+
+ if (response.headers.has('set-cookie')) {
+ // @ts-expect-error (headers.raw() is non-standard)
+ headers['set-cookie'] = response.headers.raw()['set-cookie'];
+ }
+
+ res.writeHead(response.status, headers);
+
+ if (response.body instanceof Readable) {
+ response.body.pipe(res);
+ } else {
+ if (response.body) {
+ res.write(await response.arrayBuffer());
+ }
+
+ res.end();
+ }
+}
diff --git a/packages/integrations/vercel/src/server-entrypoint.ts b/packages/integrations/vercel/src/server-entrypoint.ts
new file mode 100644
index 000000000..df01fe32a
--- /dev/null
+++ b/packages/integrations/vercel/src/server-entrypoint.ts
@@ -0,0 +1,34 @@
+import type { SSRManifest } from 'astro';
+import { App } from 'astro/app';
+import { polyfill } from '@astrojs/webapi';
+import type { IncomingMessage, ServerResponse } from 'http';
+
+import { getRequest, setResponse } from './request-transform.js';
+
+polyfill(globalThis, {
+ exclude: 'window document',
+});
+
+export const createExports = (manifest: SSRManifest) => {
+ const app = new App(manifest);
+
+ const _default = async (req: IncomingMessage, res: ServerResponse) => {
+ let request: Request;
+
+ try {
+ request = await getRequest(`https://${req.headers.host}`, req);
+ } catch (err: any) {
+ res.statusCode = err.status || 400;
+ return res.end(err.reason || 'Invalid request body');
+ }
+
+ if (!app.match(request)) {
+ res.statusCode = 404;
+ return res.end('Not found');
+ }
+
+ await setResponse(res, await app.render(request));
+ };
+
+ return { _default };
+};
diff --git a/packages/integrations/vercel/tsconfig.json b/packages/integrations/vercel/tsconfig.json
new file mode 100644
index 000000000..44baf375c
--- /dev/null
+++ b/packages/integrations/vercel/tsconfig.json
@@ -0,0 +1,10 @@
+{
+ "extends": "../../../tsconfig.base.json",
+ "include": ["src"],
+ "compilerOptions": {
+ "allowJs": true,
+ "module": "ES2020",
+ "outDir": "./dist",
+ "target": "ES2020"
+ }
+}
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index e367fbf9a..4bffb8598 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -1369,6 +1369,19 @@ importers:
astro: link:../../astro
astro-scripts: link:../../../scripts
+ packages/integrations/vercel:
+ specifiers:
+ '@astrojs/webapi': ^0.11.0
+ astro: workspace:*
+ astro-scripts: workspace:*
+ esbuild: 0.14.25
+ dependencies:
+ '@astrojs/webapi': link:../../webapi
+ esbuild: 0.14.25
+ devDependencies:
+ astro: link:../../astro
+ astro-scripts: link:../../../scripts
+
packages/integrations/vue:
specifiers:
'@vitejs/plugin-vue': ^2.3.1