Node adapter: handle prerendering and serving with query params (#6110)

* Node adapter: handle prerendering and serving with query params

* Adding a changeset
This commit is contained in:
Matthew Phillips 2023-02-02 19:10:16 -05:00 committed by GitHub
parent f9babc38b4
commit 67ccec9e16
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 121 additions and 4 deletions

View file

@ -0,0 +1,5 @@
---
'@astrojs/node': patch
---
Fixes support for prerendering and query params

View file

@ -31,13 +31,15 @@
},
"dependencies": {
"@astrojs/webapi": "^2.0.0",
"send": "^0.18.0"
"send": "^0.18.0",
"server-destroy": "^1.0.1"
},
"peerDependencies": {
"astro": "workspace:^2.0.6"
},
"devDependencies": {
"@types/send": "^0.17.1",
"@types/server-destroy": "^1.0.1",
"astro": "workspace:*",
"astro-scripts": "workspace:*",
"chai": "^4.3.6",

View file

@ -3,6 +3,7 @@ import http from 'http';
import https from 'https';
import send from 'send';
import { fileURLToPath } from 'url';
import enableDestroy from 'server-destroy';
interface CreateServerOptions {
client: URL;
@ -19,6 +20,7 @@ export function createServer(
if (req.url) {
let pathname = removeBase(req.url);
pathname = pathname[0] === '/' ? pathname : '/' + pathname;
pathname = new URL(pathname, `http://${host}:${port}`).pathname;
const stream = send(req, encodeURI(decodeURI(pathname)), {
root: fileURLToPath(client),
dotfiles: pathname.startsWith('/.well-known/') ? 'allow' : 'deny',
@ -63,6 +65,7 @@ export function createServer(
httpServer = http.createServer(listener);
}
httpServer.listen(port, host);
enableDestroy(httpServer);
// Resolves once the server is closed
const closed = new Promise<void>((resolve, reject) => {
@ -79,7 +82,7 @@ export function createServer(
server: httpServer,
stop: async () => {
await new Promise((resolve, reject) => {
httpServer.close((err) => (err ? reject(err) : resolve(undefined)));
httpServer.destroy((err) => (err ? reject(err) : resolve(undefined)));
});
},
};

View file

@ -6,7 +6,7 @@ export function getAdapter(options: Options): AstroAdapter {
name: '@astrojs/node',
serverEntrypoint: '@astrojs/node/server.js',
previewEntrypoint: '@astrojs/node/preview.js',
exports: ['handler'],
exports: ['handler', 'startServer'],
args: options,
};
}

View file

@ -13,6 +13,7 @@ export function createExports(manifest: SSRManifest, options: Options) {
const app = new NodeApp(manifest);
return {
handler: middleware(app, options.mode),
startServer: () => startServer(app, options)
};
}

View file

@ -55,8 +55,12 @@ export default function startServer(app: NodeApp, options: Options) {
);
const protocol = server.server instanceof https.Server ? 'https' : 'http';
// eslint-disable-next-line no-console
console.log(`Server listening on ${protocol}://${host}:${port}`);
return server.closed();
return {
server,
done: server.closed()
};
}

View file

@ -0,0 +1,9 @@
{
"name": "@test/nodejs-encoded",
"version": "0.0.0",
"private": true,
"dependencies": {
"astro": "workspace:*",
"@astrojs/node": "workspace:*"
}
}

View file

@ -0,0 +1,10 @@
---
---
<html>
<head>
<title>One</title>
</head>
<body>
<h1>One</h1>
</body>
</html>

View file

@ -0,0 +1,11 @@
---
export const prerender = true;
---
<html>
<head>
<title>Two</title>
</head>
<body>
<h1>Two</h1>
</body>
</html>

View file

@ -0,0 +1,60 @@
import nodejs from '../dist/index.js';
import { loadFixture, createRequestAndResponse } from './test-utils.js';
import { expect } from 'chai';
import * as cheerio from 'cheerio';
import { fetch } from 'undici';
describe('Prerendering', () => {
/** @type {import('./test-utils').Fixture} */
let fixture;
let server;
before(async () => {
process.env.ASTRO_NODE_AUTOSTART = 'disabled';
fixture = await loadFixture({
root: './fixtures/prerender/',
output: 'server',
adapter: nodejs({ mode: 'standalone' }),
});
await fixture.build();
const { startServer } = await await load();
let res = startServer();
server = res.server;
});
after(async () => {
await server.stop();
});
async function load() {
const mod = await import('./fixtures/prerender/dist/server/entry.mjs');
return mod;
}
it('Can render SSR route', async () => {
const res = await fetch(`http://${server.host}:${server.port}/one`);
const html = await res.text();
const $ = cheerio.load(html);
expect(res.status).to.equal(200);
expect($('h1').text()).to.equal('One');
});
it('Can render prerendered route', async () => {
const res = await fetch(`http://${server.host}:${server.port}/two`);
const html = await res.text();
const $ = cheerio.load(html);
expect(res.status).to.equal(200);
expect($('h1').text()).to.equal('Two');
});
it('Can render prerendered route with query params', async () => {
const res = await fetch(`http://${server.host}:${server.port}/two?foo=bar`);
const html = await res.text();
const $ = cheerio.load(html);
expect(res.status).to.equal(200);
expect($('h1').text()).to.equal('Two');
});
});

View file

@ -3070,6 +3070,7 @@ importers:
specifiers:
'@astrojs/webapi': ^2.0.0
'@types/send': ^0.17.1
'@types/server-destroy': ^1.0.1
astro: workspace:*
astro-scripts: workspace:*
chai: ^4.3.6
@ -3077,12 +3078,15 @@ importers:
mocha: ^9.2.2
node-mocks-http: ^1.11.0
send: ^0.18.0
server-destroy: ^1.0.1
undici: ^5.14.0
dependencies:
'@astrojs/webapi': link:../../webapi
send: 0.18.0
server-destroy: 1.0.1
devDependencies:
'@types/send': 0.17.1
'@types/server-destroy': 1.0.1
astro: link:../../astro
astro-scripts: link:../../../scripts
chai: 4.3.7
@ -3115,6 +3119,14 @@ importers:
'@astrojs/node': link:../../..
astro: link:../../../../../astro
packages/integrations/node/test/fixtures/prerender:
specifiers:
'@astrojs/node': workspace:*
astro: workspace:*
dependencies:
'@astrojs/node': link:../../..
astro: link:../../../../../astro
packages/integrations/node/test/fixtures/url-protocol:
specifiers:
'@astrojs/node': workspace:*