Skip to content

Commit 26d9f1b

Browse files
committed
[feat] expose handler to allow use in custom server
1 parent 02f7aba commit 26d9f1b

File tree

9 files changed

+176
-99
lines changed

9 files changed

+176
-99
lines changed

documentation/faq/80-integrations.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ Put the code to query your database in [endpoints](/docs#routing-endpoints) - do
6767

6868
### How do I use middleware?
6969

70-
In dev, you can add middleware to Vite by using a Vite plugin. For example:
70+
`adapter-node` builds a middleware that use can use with your own server for production mode. In dev, you can add middleware to Vite by using a Vite plugin. For example:
7171

7272
```js
7373
const myPlugin = {

packages/adapter-node/README.md

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,34 @@ HOST=127.0.0.1 PORT=4000 node build
4545

4646
You can specify different environment variables if necessary using the `env` option.
4747

48+
## Middleware
49+
50+
The adapter exports a middleware `(req, res, next) => {}` that's compatible with [Express](https://github.com/expressjs/expressjs.com) / [Connect](https://github.com/senchalabs/connect) / [Polka](https://github.com/lukeed/polka). Additionally, it also exports a reference server implementation using this middleware with a plain Node HTTP server.
51+
52+
But you can use your favorite server framework to combine it with other middleware and server logic. You can import `kitMiddleware`, your ready-to-use SvelteKit bundle as middleware, from `./build/middlewares.js`.
53+
54+
```
55+
import { assetsMiddleware, prerenderedMiddleware, kitMiddleware } from './build/middlewares.js';
56+
import polka from 'polka';
57+
58+
const app = polka();
59+
60+
const myMiddleware = function(req, res, next) {
61+
console.log('Hello world!');
62+
next();
63+
};
64+
65+
app.use(myMiddleware);
66+
67+
app.get('/no-svelte', (req, res) => {
68+
res.end('This is not Svelte!')
69+
});
70+
71+
app.use(assetsMiddleware, prerenderedMiddleware, kitMiddleware);
72+
73+
app.listen(3000)
74+
```
75+
4876
## Advanced Configuration
4977

5078
### esbuild

packages/adapter-node/index.js

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ export default function ({
4141
await compress(static_directory);
4242
}
4343

44-
utils.log.minor('Building server');
44+
utils.log.minor('Building SvelteKit middleware');
4545
const files = fileURLToPath(new URL('./files', import.meta.url));
4646
utils.copy(files, '.svelte-kit/node');
4747
writeFileSync(
@@ -54,10 +54,11 @@ export default function ({
5454
port_env
5555
)}] || (!path && 3000);`
5656
);
57+
5758
/** @type {BuildOptions} */
5859
const defaultOptions = {
59-
entryPoints: ['.svelte-kit/node/index.js'],
60-
outfile: join(out, 'index.js'),
60+
entryPoints: ['.svelte-kit/node/middlewares.js'],
61+
outfile: join(out, 'middlewares.js'),
6162
bundle: true,
6263
external: Object.keys(JSON.parse(readFileSync('package.json', 'utf8')).dependencies || {}),
6364
format: 'esm',
@@ -71,6 +72,32 @@ export default function ({
7172
const buildOptions = esbuildConfig ? await esbuildConfig(defaultOptions) : defaultOptions;
7273
await esbuild.build(buildOptions);
7374

75+
utils.log.minor('Building SvelteKit reference server');
76+
/** @type {BuildOptions} */
77+
const defaultOptionsRefServer = {
78+
entryPoints: ['.svelte-kit/node/index.js'],
79+
outfile: join(out, 'index.js'),
80+
bundle: true,
81+
external: ['./middlewares.js'], // does not work, eslint does not exclude middlewares from target
82+
format: 'esm',
83+
platform: 'node',
84+
target: 'node12',
85+
// external exclude workaround, see https://github.com/evanw/esbuild/issues/514
86+
plugins: [
87+
{
88+
name: 'fix-middlewares-exclude',
89+
setup(build) {
90+
// Match an import called "./middlewares.js" and mark it as external
91+
build.onResolve({ filter: /^\.\/middlewares\.js$/ }, () => ({ external: true }));
92+
}
93+
}
94+
]
95+
};
96+
const buildOptionsRefServer = esbuildConfig
97+
? await esbuildConfig(defaultOptionsRefServer)
98+
: defaultOptionsRefServer;
99+
await esbuild.build(buildOptionsRefServer);
100+
74101
utils.log.minor('Prerendering static pages');
75102
await utils.prerender({
76103
dest: `${out}/prerendered`

packages/adapter-node/rollup.config.js

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,16 @@ import commonjs from '@rollup/plugin-commonjs';
33
import json from '@rollup/plugin-json';
44

55
export default [
6+
{
7+
input: 'src/middlewares.js',
8+
output: {
9+
file: 'files/middlewares.js',
10+
format: 'esm',
11+
sourcemap: true
12+
},
13+
plugins: [nodeResolve(), commonjs(), json()],
14+
external: ['../output/server/app.js', ...require('module').builtinModules]
15+
},
616
{
717
input: 'src/index.js',
818
output: {
@@ -11,7 +21,7 @@ export default [
1121
sourcemap: true
1222
},
1323
plugins: [nodeResolve(), commonjs(), json()],
14-
external: ['../output/server/app.js', './env.js', ...require('module').builtinModules]
24+
external: ['./middlewares.js', './env.js', ...require('module').builtinModules]
1525
},
1626
{
1727
input: 'src/shims.js',

packages/adapter-node/src/index.js

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,22 @@
1-
// TODO hardcoding the relative location makes this brittle
2-
// @ts-ignore
3-
import { init, render } from '../output/server/app.js';
41
// @ts-ignore
52
import { path, host, port } from './env.js';
6-
import { createServer } from './server';
7-
8-
init();
3+
import { assetsMiddleware, kitMiddleware, prerenderedMiddleware } from './middlewares.js';
4+
import compression from 'compression';
5+
import polka from 'polka';
96

10-
const instance = createServer({ render });
7+
const server = polka().use(
8+
// https://github.com/lukeed/polka/issues/173
9+
// @ts-ignore - nothing we can do about so just ignore it
10+
compression({ threshold: 0 }),
11+
assetsMiddleware,
12+
kitMiddleware,
13+
prerenderedMiddleware
14+
);
1115

1216
const listenOpts = { path, host, port };
13-
instance.listen(listenOpts, () => {
17+
18+
server.listen(listenOpts, () => {
1419
console.log(`Listening on ${path ? path : host + ':' + port}`);
1520
});
1621

17-
export { instance };
22+
export { server };
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import { getRawBody } from '@sveltejs/kit/node';
2+
3+
/**
4+
* @return {import('polka').Middleware}
5+
*/
6+
// TODO: type render function from @sveltejs/kit/adapter
7+
// @ts-ignore
8+
export function create_kit_middleware({ render }) {
9+
return async (req, res) => {
10+
const parsed = new URL(req.url || '', 'http://localhost');
11+
12+
let body;
13+
14+
try {
15+
body = await getRawBody(req);
16+
} catch (err) {
17+
res.statusCode = err.status || 400;
18+
return res.end(err.reason || 'Invalid request body');
19+
}
20+
21+
const rendered = await render({
22+
method: req.method,
23+
headers: req.headers, // TODO: what about repeated headers, i.e. string[]
24+
path: parsed.pathname,
25+
query: parsed.searchParams,
26+
rawBody: body
27+
});
28+
29+
if (rendered) {
30+
res.writeHead(rendered.status, rendered.headers);
31+
if (rendered.body) {
32+
res.write(rendered.body);
33+
}
34+
res.end();
35+
} else {
36+
res.statusCode = 404;
37+
res.end('Not found');
38+
}
39+
};
40+
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
// TODO hardcoding the relative location makes this brittle
2+
// Also, we need most of the logic in another file for testing because
3+
// ../output/server/app.js doesn't exist when we run the tests
4+
// @ts-ignore
5+
import { init, render } from '../output/server/app.js';
6+
import { create_kit_middleware } from './kit-middleware.js';
7+
8+
import fs from 'fs';
9+
import { dirname, join } from 'path';
10+
import sirv from 'sirv';
11+
import { fileURLToPath } from 'url';
12+
13+
// App is a dynamic file built from the application layer.
14+
15+
const __dirname = dirname(fileURLToPath(import.meta.url));
16+
/** @type {import('polka').Middleware} */
17+
const noop_handler = (_req, _res, next) => next();
18+
const paths = {
19+
assets: join(__dirname, '/assets'),
20+
prerendered: join(__dirname, '/prerendered')
21+
};
22+
23+
export const prerenderedMiddleware = fs.existsSync(paths.prerendered)
24+
? sirv(paths.prerendered, {
25+
etag: true,
26+
maxAge: 0,
27+
gzip: true,
28+
brotli: true
29+
})
30+
: noop_handler;
31+
32+
export const assetsMiddleware = fs.existsSync(paths.assets)
33+
? sirv(paths.assets, {
34+
setHeaders: (res, pathname) => {
35+
// @ts-expect-error - dynamically replaced with define
36+
if (pathname.startsWith(/* eslint-disable-line no-undef */ APP_DIR)) {
37+
res.setHeader('cache-control', 'public, max-age=31536000, immutable');
38+
}
39+
},
40+
gzip: true,
41+
brotli: true
42+
})
43+
: noop_handler;
44+
45+
export const kitMiddleware = (function () {
46+
init();
47+
return create_kit_middleware({ render });
48+
})();

packages/adapter-node/src/server.js

Lines changed: 0 additions & 82 deletions
This file was deleted.

packages/adapter-node/tests/smoke.js

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
11
import { test } from 'uvu';
2-
import { createServer } from '../src/server.js';
2+
import { createKitMiddleware } from '../src/kit-middleware.js';
33
import * as assert from 'uvu/assert';
44
import fetch from 'node-fetch';
5+
import polka from 'polka';
56

67
const { PORT = 3000 } = process.env;
78
const DEFAULT_SERVER_OPTS = { render: () => {} };
89

9-
function startServer(opts = DEFAULT_SERVER_OPTS) {
10-
const server = createServer(opts);
10+
async function startServer(opts = DEFAULT_SERVER_OPTS) {
1111
return new Promise((fulfil, reject) => {
12+
const server = polka().use(createKitMiddleware(opts));
1213
server.listen(PORT, (err) => {
1314
if (err) {
1415
reject(err);

0 commit comments

Comments
 (0)