Skip to content

Commit

Permalink
Node adapter: handle prerendering and serving with query params (#6110)
Browse files Browse the repository at this point in the history
* Node adapter: handle prerendering and serving with query params

* Adding a changeset
  • Loading branch information
matthewp committed Feb 3, 2023
1 parent f9babc3 commit 67ccec9
Show file tree
Hide file tree
Showing 11 changed files with 121 additions and 4 deletions.
5 changes: 5 additions & 0 deletions .changeset/shy-cats-heal.md
@@ -0,0 +1,5 @@
---
'@astrojs/node': patch
---

Fixes support for prerendering and query params
4 changes: 3 additions & 1 deletion packages/integrations/node/package.json
Expand Up @@ -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",
Expand Down
5 changes: 4 additions & 1 deletion packages/integrations/node/src/http-server.ts
Expand Up @@ -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;
Expand All @@ -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',
Expand Down Expand Up @@ -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) => {
Expand All @@ -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)));
});
},
};
Expand Down
2 changes: 1 addition & 1 deletion packages/integrations/node/src/index.ts
Expand Up @@ -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,
};
}
Expand Down
1 change: 1 addition & 0 deletions packages/integrations/node/src/server.ts
Expand Up @@ -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)
};
}

Expand Down
6 changes: 5 additions & 1 deletion packages/integrations/node/src/standalone.ts
Expand Up @@ -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()
};
}
@@ -0,0 +1,9 @@
{
"name": "@test/nodejs-encoded",
"version": "0.0.0",
"private": true,
"dependencies": {
"astro": "workspace:*",
"@astrojs/node": "workspace:*"
}
}
@@ -0,0 +1,10 @@
---
---
<html>
<head>
<title>One</title>
</head>
<body>
<h1>One</h1>
</body>
</html>
@@ -0,0 +1,11 @@
---
export const prerender = true;
---
<html>
<head>
<title>Two</title>
</head>
<body>
<h1>Two</h1>
</body>
</html>
60 changes: 60 additions & 0 deletions packages/integrations/node/test/prerender.test.js
@@ -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');
});
});
12 changes: 12 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 67ccec9

Please sign in to comment.