Skip to content

Commit 49aa215

Browse files
authoredNov 29, 2023
Switch app.render signature (#9199)
* feat(app): app.render optional object * tests * update vercel and node * update changeset * deprecation notice and loggin * clarify changeset * add node, vercel changeset * deduplicate code
1 parent c421a3d commit 49aa215

File tree

8 files changed

+80
-10
lines changed

8 files changed

+80
-10
lines changed
 

‎.changeset/big-cooks-notice.md

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
'@astrojs/vercel': major
3+
'@astrojs/node': major
4+
---
5+
6+
The internals of the integration have been updated to support Astro 4.0. Make sure to upgrade your Astro version as Astro 3.0 is no longer supported.
+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
---
2+
'astro': major
3+
---
4+
5+
This change only affects maintainers of third-party adapters. In the Integration API, the `app.render()` method of the `App` class has been simplified.
6+
7+
Instead of two optional arguments, it now takes a single optional argument that is an object with two optional properties: `routeData` and `locals`.
8+
```diff
9+
app.render(request)
10+
11+
- app.render(request, routeData)
12+
+ app.render(request, { routeData })
13+
14+
- app.render(request, routeData, locals)
15+
+ app.render(request, { routeData, locals })
16+
17+
- app.render(request, undefined, locals)
18+
+ app.render(request, { locals })
19+
```
20+
The current signature is deprecated but will continue to function until next major version.

‎packages/astro/src/core/app/index.ts

+38-2
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,11 @@ const responseSentSymbol = Symbol.for('astro.responseSent');
3535

3636
const STATUS_CODES = new Set([404, 500]);
3737

38+
export interface RenderOptions {
39+
routeData?: RouteData;
40+
locals?: object;
41+
}
42+
3843
export interface RenderErrorOptions {
3944
routeData?: RouteData;
4045
response?: Response;
@@ -59,6 +64,7 @@ export class App {
5964
#baseWithoutTrailingSlash: string;
6065
#pipeline: SSRRoutePipeline;
6166
#adapterLogger: AstroIntegrationLogger;
67+
#renderOptionsDeprecationWarningShown = false;
6268

6369
constructor(manifest: SSRManifest, streaming = true) {
6470
this.#manifest = manifest;
@@ -141,7 +147,32 @@ export class App {
141147
return routeData;
142148
}
143149

144-
async render(request: Request, routeData?: RouteData, locals?: object): Promise<Response> {
150+
async render(request: Request, options?: RenderOptions): Promise<Response>
151+
/**
152+
* @deprecated Instead of passing `RouteData` and locals individually, pass an object with `routeData` and `locals` properties.
153+
* See https://github.com/withastro/astro/pull/9199 for more information.
154+
*/
155+
async render(request: Request, routeData?: RouteData, locals?: object): Promise<Response>
156+
async render(request: Request, routeDataOrOptions?: RouteData | RenderOptions, maybeLocals?: object): Promise<Response> {
157+
let routeData: RouteData | undefined;
158+
let locals: object | undefined;
159+
160+
if (routeDataOrOptions && ('routeData' in routeDataOrOptions || 'locals' in routeDataOrOptions)) {
161+
if ('routeData' in routeDataOrOptions) {
162+
routeData = routeDataOrOptions.routeData;
163+
}
164+
if ('locals' in routeDataOrOptions) {
165+
locals = routeDataOrOptions.locals;
166+
}
167+
}
168+
else {
169+
routeData = routeDataOrOptions as RouteData | undefined;
170+
locals = maybeLocals;
171+
if (routeDataOrOptions || locals) {
172+
this.#logRenderOptionsDeprecationWarning();
173+
}
174+
}
175+
145176
// Handle requests with duplicate slashes gracefully by cloning with a cleaned-up request URL
146177
if (request.url !== collapseDuplicateSlashes(request.url)) {
147178
request = new Request(collapseDuplicateSlashes(request.url), request);
@@ -152,7 +183,6 @@ export class App {
152183
if (!routeData) {
153184
return this.#renderError(request, { status: 404 });
154185
}
155-
156186
Reflect.set(request, clientLocalsSymbol, locals ?? {});
157187
const pathname = this.#getPathnameFromRequest(request);
158188
const defaultStatus = this.#getDefaultStatusCode(routeData, pathname);
@@ -210,6 +240,12 @@ export class App {
210240
return response;
211241
}
212242

243+
#logRenderOptionsDeprecationWarning() {
244+
if (this.#renderOptionsDeprecationWarningShown) return;
245+
this.#logger.warn("deprecated", `The adapter ${this.#manifest.adapterName} is using a deprecated signature of the 'app.render()' method. From Astro 4.0, locals and routeData are provided as properties on an optional object to this method. Using the old signature will cause an error in Astro 5.0. See https://github.com/withastro/astro/pull/9199 for more information.`)
246+
this.#renderOptionsDeprecationWarningShown = true;
247+
}
248+
213249
setCookieHeaders(response: Response) {
214250
return getSetCookiesFromResponse(response);
215251
}

‎packages/astro/src/core/app/node.ts

+10-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import type { RouteData } from '../../@types/astro.js';
22
import type { SerializedSSRManifest, SSRManifest } from './types.js';
3+
import type { RenderOptions } from './index.js';
34

45
import * as fs from 'node:fs';
56
import { IncomingMessage } from 'node:http';
@@ -116,11 +117,18 @@ export class NodeApp extends App {
116117
}
117118
return super.match(req);
118119
}
119-
render(req: NodeIncomingMessage | Request, routeData?: RouteData, locals?: object) {
120+
render(request: NodeIncomingMessage | Request, options?: RenderOptions): Promise<Response>
121+
/**
122+
* @deprecated Instead of passing `RouteData` and locals individually, pass an object with `routeData` and `locals` properties.
123+
* See https://github.com/withastro/astro/pull/9199 for more information.
124+
*/
125+
render(request: NodeIncomingMessage | Request, routeData?: RouteData, locals?: object): Promise<Response>
126+
render(req: NodeIncomingMessage | Request, routeDataOrOptions?: RouteData | RenderOptions, maybeLocals?: object) {
120127
if (!(req instanceof Request)) {
121128
req = createRequestFromNodeRequest(req);
122129
}
123-
return super.render(req, routeData, locals);
130+
// @ts-expect-error The call would have succeeded against the implementation, but implementation signatures of overloads are not externally visible.
131+
return super.render(req, routeDataOrOptions, maybeLocals);
124132
}
125133
}
126134

‎packages/astro/test/middleware.test.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -252,7 +252,7 @@ describe('Middleware API in PROD mode, SSR', () => {
252252
it('should correctly call the middleware function for 404', async () => {
253253
const request = new Request('http://example.com/funky-url');
254254
const routeData = app.match(request);
255-
const response = await app.render(request, routeData);
255+
const response = await app.render(request, { routeData });
256256
const text = await response.text();
257257
expect(text.includes('Error')).to.be.true;
258258
expect(text.includes('bar')).to.be.true;

‎packages/astro/test/ssr-locals.test.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ describe('SSR Astro.locals from server', () => {
2020
const app = await fixture.loadTestAdapterApp();
2121
const request = new Request('http://example.com/foo');
2222
const locals = { foo: 'bar' };
23-
const response = await app.render(request, undefined, locals);
23+
const response = await app.render(request, { locals });
2424
const html = await response.text();
2525

2626
const $ = cheerio.load(html);

‎packages/integrations/node/src/nodeMiddleware.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -30,10 +30,10 @@ export default function (app: NodeApp, mode: Options['mode']) {
3030
}
3131

3232
try {
33-
const route = app.match(req);
34-
if (route) {
33+
const routeData = app.match(req);
34+
if (routeData) {
3535
try {
36-
const response = await app.render(req, route, locals);
36+
const response = await app.render(req, { routeData, locals });
3737
await writeWebResponse(app, res, response);
3838
} catch (err: unknown) {
3939
if (next) {

‎packages/integrations/vercel/src/serverless/entrypoint.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ export const createExports = (manifest: SSRManifest) => {
2929
locals = JSON.parse(localsAsString);
3030
}
3131
}
32-
await setResponse(app, res, await app.render(request, routeData, locals));
32+
await setResponse(app, res, await app.render(request, { routeData, locals }));
3333
};
3434

3535
return { default: handler };

0 commit comments

Comments
 (0)
Please sign in to comment.