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:
parent
f9babc38b4
commit
67ccec9e16
11 changed files with 121 additions and 4 deletions
5
.changeset/shy-cats-heal.md
Normal file
5
.changeset/shy-cats-heal.md
Normal file
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
'@astrojs/node': patch
|
||||
---
|
||||
|
||||
Fixes support for prerendering and query params
|
|
@ -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",
|
||||
|
|
|
@ -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)));
|
||||
});
|
||||
},
|
||||
};
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -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()
|
||||
};
|
||||
}
|
||||
|
|
9
packages/integrations/node/test/fixtures/prerender/package.json
vendored
Normal file
9
packages/integrations/node/test/fixtures/prerender/package.json
vendored
Normal file
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"name": "@test/nodejs-encoded",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"astro": "workspace:*",
|
||||
"@astrojs/node": "workspace:*"
|
||||
}
|
||||
}
|
10
packages/integrations/node/test/fixtures/prerender/src/pages/one.astro
vendored
Normal file
10
packages/integrations/node/test/fixtures/prerender/src/pages/one.astro
vendored
Normal file
|
@ -0,0 +1,10 @@
|
|||
---
|
||||
---
|
||||
<html>
|
||||
<head>
|
||||
<title>One</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>One</h1>
|
||||
</body>
|
||||
</html>
|
11
packages/integrations/node/test/fixtures/prerender/src/pages/two.astro
vendored
Normal file
11
packages/integrations/node/test/fixtures/prerender/src/pages/two.astro
vendored
Normal file
|
@ -0,0 +1,11 @@
|
|||
---
|
||||
export const prerender = true;
|
||||
---
|
||||
<html>
|
||||
<head>
|
||||
<title>Two</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Two</h1>
|
||||
</body>
|
||||
</html>
|
60
packages/integrations/node/test/prerender.test.js
Normal file
60
packages/integrations/node/test/prerender.test.js
Normal 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');
|
||||
});
|
||||
});
|
|
@ -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:*
|
||||
|
|
Loading…
Reference in a new issue