summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--examples/ssr/astro.config.mjs13
-rw-r--r--examples/ssr/package.json4
-rw-r--r--examples/ssr/server/api.mjs100
-rw-r--r--examples/ssr/server/dev-api.mjs17
-rw-r--r--examples/ssr/server/server.mjs33
-rw-r--r--examples/ssr/src/api.ts2
-rw-r--r--examples/ssr/src/models/db.json (renamed from examples/ssr/server/db.json)0
-rw-r--r--examples/ssr/src/models/db.ts9
-rw-r--r--examples/ssr/src/models/session.ts3
-rw-r--r--examples/ssr/src/pages/api/cart.ts47
-rw-r--r--examples/ssr/src/pages/api/products.ts7
-rw-r--r--examples/ssr/src/pages/api/products/[id].ts17
-rw-r--r--examples/ssr/tsconfig.json1
-rw-r--r--packages/astro/src/core/build/vite-plugin-ssr.ts1
-rw-r--r--packages/astro/src/core/request.ts5
-rw-r--r--packages/astro/src/vite-plugin-astro-server/index.ts12
-rw-r--r--packages/astro/test/fixtures/ssr-dynamic/src/pages/[id].astro1
-rw-r--r--packages/astro/test/ssr-dynamic.test.js15
18 files changed, 133 insertions, 154 deletions
diff --git a/examples/ssr/astro.config.mjs b/examples/ssr/astro.config.mjs
index f6aba20ce..448d5829d 100644
--- a/examples/ssr/astro.config.mjs
+++ b/examples/ssr/astro.config.mjs
@@ -6,17 +6,4 @@ import nodejs from '@astrojs/node';
export default defineConfig({
adapter: nodejs(),
integrations: [svelte()],
- vite: {
- server: {
- cors: {
- credentials: true,
- },
- proxy: {
- '/api': {
- target: 'http://127.0.0.1:8085',
- changeOrigin: true,
- },
- },
- },
- },
});
diff --git a/examples/ssr/package.json b/examples/ssr/package.json
index da2856570..401f7e06a 100644
--- a/examples/ssr/package.json
+++ b/examples/ssr/package.json
@@ -3,9 +3,7 @@
"version": "0.0.1",
"private": true,
"scripts": {
- "dev-api": "node server/dev-api.mjs",
- "dev-server": "astro dev --experimental-ssr",
- "dev": "concurrently \"npm run dev-api\" \"astro dev --experimental-ssr\"",
+ "dev": "astro dev --experimental-ssr",
"start": "astro dev",
"build": "astro build --experimental-ssr",
"server": "node server/server.mjs"
diff --git a/examples/ssr/server/api.mjs b/examples/ssr/server/api.mjs
deleted file mode 100644
index 589766ee9..000000000
--- a/examples/ssr/server/api.mjs
+++ /dev/null
@@ -1,100 +0,0 @@
-import fs from 'fs';
-import lightcookie from 'lightcookie';
-
-const dbJSON = fs.readFileSync(new URL('./db.json', import.meta.url));
-const db = JSON.parse(dbJSON);
-const products = db.products;
-const productMap = new Map(products.map((product) => [product.id, product]));
-
-// Normally this would be in a database.
-const userCartItems = new Map();
-
-const routes = [
- {
- match: /\/api\/products\/([0-9])+/,
- async handle(_req, res, [, idStr]) {
- const id = Number(idStr);
- if (productMap.has(id)) {
- const product = productMap.get(id);
- res.writeHead(200, {
- 'Content-Type': 'application/json',
- });
- res.end(JSON.stringify(product));
- } else {
- res.writeHead(404, {
- 'Content-Type': 'text/plain',
- });
- res.end('Not found');
- }
- },
- },
- {
- match: /\/api\/products/,
- async handle(_req, res) {
- res.writeHead(200, {
- 'Content-Type': 'application/json',
- });
- res.end(JSON.stringify(products));
- },
- },
- {
- match: /\/api\/cart/,
- async handle(req, res) {
- res.writeHead(200, {
- 'Content-Type': 'application/json',
- });
- let cookie = req.headers.cookie;
- let userId = cookie ? lightcookie.parse(cookie)['user-id'] : '1'; // default for testing
- if (!userId || !userCartItems.has(userId)) {
- res.end(JSON.stringify({ items: [] }));
- return;
- }
- let items = userCartItems.get(userId);
- let array = Array.from(items.values());
- res.end(JSON.stringify({ items: array }));
- },
- },
- {
- match: /\/api\/add-to-cart/,
- async handle(req, res) {
- let body = '';
- req.on('data', (chunk) => (body += chunk));
- return new Promise((resolve) => {
- req.on('end', () => {
- let cookie = req.headers.cookie;
- let userId = lightcookie.parse(cookie)['user-id'];
- let msg = JSON.parse(body);
-
- if (!userCartItems.has(userId)) {
- userCartItems.set(userId, new Map());
- }
-
- let cart = userCartItems.get(userId);
- if (cart.has(msg.id)) {
- cart.get(msg.id).count++;
- } else {
- cart.set(msg.id, { id: msg.id, name: msg.name, count: 1 });
- }
-
- res.writeHead(200, {
- 'Content-Type': 'application/json',
- });
- res.end(JSON.stringify({ ok: true }));
- });
- });
- },
- },
-];
-
-export async function apiHandler(req, res) {
- for (const route of routes) {
- const match = route.match.exec(req.url);
- if (match) {
- return route.handle(req, res, match);
- }
- }
- res.writeHead(404, {
- 'Content-Type': 'text/plain',
- });
- res.end('Not found');
-}
diff --git a/examples/ssr/server/dev-api.mjs b/examples/ssr/server/dev-api.mjs
deleted file mode 100644
index 305ac609b..000000000
--- a/examples/ssr/server/dev-api.mjs
+++ /dev/null
@@ -1,17 +0,0 @@
-import { createServer } from 'http';
-import { apiHandler } from './api.mjs';
-
-const PORT = process.env.PORT || 8085;
-
-const server = createServer((req, res) => {
- apiHandler(req, res).catch((err) => {
- console.error(err);
- res.writeHead(500, {
- 'Content-Type': 'text/plain',
- });
- res.end(err.toString());
- });
-});
-
-server.listen(PORT);
-console.log(`API running at http://localhost:${PORT}`);
diff --git a/examples/ssr/server/server.mjs b/examples/ssr/server/server.mjs
index bed49b749..9838d7ada 100644
--- a/examples/ssr/server/server.mjs
+++ b/examples/ssr/server/server.mjs
@@ -1,29 +1,28 @@
import { createServer } from 'http';
import fs from 'fs';
import mime from 'mime';
-import { apiHandler } from './api.mjs';
import { handler as ssrHandler } from '../dist/server/entry.mjs';
const clientRoot = new URL('../dist/client/', import.meta.url);
async function handle(req, res) {
- ssrHandler(req, res, async () => {
- // Did not match an SSR route
+ ssrHandler(req, res, async (err) => {
+ if(err) {
+ res.writeHead(500);
+ res.end(err.stack)
+ return;
+ }
- if (/^\/api\//.test(req.url)) {
- return apiHandler(req, res);
- } else {
- let local = new URL('.' + req.url, clientRoot);
- try {
- const data = await fs.promises.readFile(local);
- res.writeHead(200, {
- 'Content-Type': mime.getType(req.url),
- });
- res.end(data);
- } catch {
- res.writeHead(404);
- res.end();
- }
+ let local = new URL('.' + req.url, clientRoot);
+ try {
+ const data = await fs.promises.readFile(local);
+ res.writeHead(200, {
+ 'Content-Type': mime.getType(req.url),
+ });
+ res.end(data);
+ } catch {
+ res.writeHead(404);
+ res.end();
}
});
}
diff --git a/examples/ssr/src/api.ts b/examples/ssr/src/api.ts
index 40058360b..64dfe17d3 100644
--- a/examples/ssr/src/api.ts
+++ b/examples/ssr/src/api.ts
@@ -60,7 +60,7 @@ export async function getCart(): Promise<Cart> {
}
export async function addToUserCart(id: number | string, name: string): Promise<void> {
- await fetch(`${origin}/api/add-to-cart`, {
+ await fetch(`${origin}/api/cart`, {
credentials: 'same-origin',
method: 'POST',
mode: 'no-cors',
diff --git a/examples/ssr/server/db.json b/examples/ssr/src/models/db.json
index 76f9e4da3..76f9e4da3 100644
--- a/examples/ssr/server/db.json
+++ b/examples/ssr/src/models/db.json
diff --git a/examples/ssr/src/models/db.ts b/examples/ssr/src/models/db.ts
new file mode 100644
index 000000000..d9caa8b03
--- /dev/null
+++ b/examples/ssr/src/models/db.ts
@@ -0,0 +1,9 @@
+import db from './db.json';
+
+const products = db.products;
+const productMap = new Map(products.map((product) => [product.id, product]));
+
+export {
+ products,
+ productMap
+};
diff --git a/examples/ssr/src/models/session.ts b/examples/ssr/src/models/session.ts
new file mode 100644
index 000000000..60ca8f1da
--- /dev/null
+++ b/examples/ssr/src/models/session.ts
@@ -0,0 +1,3 @@
+
+// Normally this would be in a database.
+export const userCartItems = new Map();
diff --git a/examples/ssr/src/pages/api/cart.ts b/examples/ssr/src/pages/api/cart.ts
new file mode 100644
index 000000000..5dbe5acbd
--- /dev/null
+++ b/examples/ssr/src/pages/api/cart.ts
@@ -0,0 +1,47 @@
+import lightcookie from 'lightcookie';
+import { userCartItems } from '../../models/session';
+
+export function get(_params: any, request: Request) {
+ let cookie = request.headers.get('cookie');
+ let userId = cookie ? lightcookie.parse(cookie)['user-id'] : '1'; // default for testing
+ if (!userId || !userCartItems.has(userId)) {
+ return {
+ body: JSON.stringify({ items: [] })
+ };
+ }
+ let items = userCartItems.get(userId);
+ let array = Array.from(items.values());
+
+ return {
+ body: JSON.stringify({ items: array })
+ }
+}
+
+interface AddToCartItem {
+ id: number;
+ name: string;
+}
+
+export async function post(_params: any, request: Request) {
+ const item: AddToCartItem = await request.json();
+
+ let cookie = request.headers.get('cookie');
+ let userId = lightcookie.parse(cookie)['user-id'];
+
+ if (!userCartItems.has(userId)) {
+ userCartItems.set(userId, new Map());
+ }
+
+ let cart = userCartItems.get(userId);
+ if (cart.has(item.id)) {
+ cart.get(item.id).count++;
+ } else {
+ cart.set(item.id, { id: item.id, name: item.name, count: 1 });
+ }
+
+ return {
+ body: JSON.stringify({
+ ok: true
+ })
+ };
+}
diff --git a/examples/ssr/src/pages/api/products.ts b/examples/ssr/src/pages/api/products.ts
new file mode 100644
index 000000000..533bdef23
--- /dev/null
+++ b/examples/ssr/src/pages/api/products.ts
@@ -0,0 +1,7 @@
+import { products } from '../../models/db';
+
+export function get() {
+ return {
+ body: JSON.stringify(products)
+ };
+}
diff --git a/examples/ssr/src/pages/api/products/[id].ts b/examples/ssr/src/pages/api/products/[id].ts
new file mode 100644
index 000000000..6a3a83722
--- /dev/null
+++ b/examples/ssr/src/pages/api/products/[id].ts
@@ -0,0 +1,17 @@
+import { productMap } from '../../../models/db';
+
+export function get({ id: idStr }) {
+ const id = Number(idStr);
+ if (productMap.has(id)) {
+ const product = productMap.get(id);
+
+ return {
+ body: JSON.stringify(product)
+ };
+ } else {
+ return new Response(null, {
+ status: 400,
+ statusText: 'Not found'
+ });
+ }
+}
diff --git a/examples/ssr/tsconfig.json b/examples/ssr/tsconfig.json
index e0065a323..ee0432bb3 100644
--- a/examples/ssr/tsconfig.json
+++ b/examples/ssr/tsconfig.json
@@ -3,6 +3,7 @@
"lib": ["ES2015", "DOM"],
"module": "ES2022",
"moduleResolution": "node",
+ "resolveJsonModule": true,
"types": ["astro/env"]
}
}
diff --git a/packages/astro/src/core/build/vite-plugin-ssr.ts b/packages/astro/src/core/build/vite-plugin-ssr.ts
index efa54cc01..f057dc2ce 100644
--- a/packages/astro/src/core/build/vite-plugin-ssr.ts
+++ b/packages/astro/src/core/build/vite-plugin-ssr.ts
@@ -18,6 +18,7 @@ const manifestReplace = '@@ASTRO_MANIFEST_REPLACE@@';
export function vitePluginSSR(buildOpts: StaticBuildOptions, internals: BuildInternals, adapter: AstroAdapter): VitePlugin {
return {
name: '@astrojs/vite-plugin-astro-ssr',
+ enforce: 'post',
options(opts) {
return addRollupInput(opts, [virtualModuleId]);
},
diff --git a/packages/astro/src/core/request.ts b/packages/astro/src/core/request.ts
index 4b9766b5b..6594621a1 100644
--- a/packages/astro/src/core/request.ts
+++ b/packages/astro/src/core/request.ts
@@ -3,20 +3,23 @@ import type { LogOptions } from './logger';
import { warn } from './logger.js';
type HeaderType = Headers | Record<string, any> | IncomingHttpHeaders;
+type RequestBody = ArrayBuffer | Blob | ReadableStream | URLSearchParams | FormData;
export interface CreateRequestOptions {
url: URL | string;
headers: HeaderType;
method?: string;
+ body?: RequestBody | undefined;
logging: LogOptions;
}
-export function createRequest({ url, headers, method = 'GET', logging }: CreateRequestOptions): Request {
+export function createRequest({ url, headers, method = 'GET', body = undefined, logging }: CreateRequestOptions): Request {
let headersObj = headers instanceof Headers ? headers : new Headers(Object.entries(headers as Record<string, any>));
const request = new Request(url.toString(), {
method: method,
headers: headersObj,
+ body
});
Object.defineProperties(request, {
diff --git a/packages/astro/src/vite-plugin-astro-server/index.ts b/packages/astro/src/vite-plugin-astro-server/index.ts
index 747765fec..250122d7b 100644
--- a/packages/astro/src/vite-plugin-astro-server/index.ts
+++ b/packages/astro/src/vite-plugin-astro-server/index.ts
@@ -128,11 +128,23 @@ async function handleRequest(
}
}
+ let body: ArrayBuffer | undefined = undefined;
+ if(!(req.method === 'GET' || req.method === 'HEAD')) {
+ let bytes: string[] = [];
+ await new Promise(resolve => {
+ req.setEncoding('utf-8');
+ req.on('data', bts => bytes.push(bts));
+ req.on('close', resolve);
+ });
+ body = new TextEncoder().encode(bytes.join('')).buffer;
+ }
+
// Headers are only available when using SSR.
const request = createRequest({
url,
headers: buildingToSSR ? req.headers : new Headers(),
method: req.method,
+ body,
logging,
});
diff --git a/packages/astro/test/fixtures/ssr-dynamic/src/pages/[id].astro b/packages/astro/test/fixtures/ssr-dynamic/src/pages/[id].astro
index e64626172..8ba5cc82e 100644
--- a/packages/astro/test/fixtures/ssr-dynamic/src/pages/[id].astro
+++ b/packages/astro/test/fixtures/ssr-dynamic/src/pages/[id].astro
@@ -4,6 +4,7 @@ const val = Number(Astro.params.id);
<html>
<head>
<title>Test app</title>
+ <style>body { font-size: 11px; }</style>
</head>
<body>
<h1>Item { val }</h1>
diff --git a/packages/astro/test/ssr-dynamic.test.js b/packages/astro/test/ssr-dynamic.test.js
index 843243425..d938e5c95 100644
--- a/packages/astro/test/ssr-dynamic.test.js
+++ b/packages/astro/test/ssr-dynamic.test.js
@@ -19,12 +19,23 @@ describe('Dynamic pages in SSR', () => {
await fixture.build();
});
- it('Do not have to implement getStaticPaths', async () => {
+ async function fetchHTML(path) {
const app = await fixture.loadTestAdapterApp();
- const request = new Request('http://example.com/123');
+ const request = new Request('http://example.com' + path);
const response = await app.render(request);
const html = await response.text();
+ return html;
+ }
+
+ it('Do not have to implement getStaticPaths', async () => {
+ const html = await fetchHTML('/123');
const $ = cheerioLoad(html);
expect($('h1').text()).to.equal('Item 123');
});
+
+ it('Includes page styles', async () => {
+ const html = await fetchHTML('/123');
+ const $ = cheerioLoad(html);
+ expect($('link').length).to.equal(1);
+ });
});