Skip to content

Commit

Permalink
[feat] expose handler to allow use in custom server
Browse files Browse the repository at this point in the history
  • Loading branch information
benmccann committed Aug 27, 2021
1 parent 02f7aba commit 4dcde29
Show file tree
Hide file tree
Showing 9 changed files with 176 additions and 99 deletions.
2 changes: 1 addition & 1 deletion documentation/faq/80-integrations.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ Put the code to query your database in [endpoints](/docs#routing-endpoints) - do

### How do I use middleware?

In dev, you can add middleware to Vite by using a Vite plugin. For example:
`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:

```js
const myPlugin = {
Expand Down
28 changes: 28 additions & 0 deletions packages/adapter-node/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,34 @@ HOST=127.0.0.1 PORT=4000 node build

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

## Middleware

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.

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`.

```
import { assetsMiddleware, prerenderedMiddleware, kitMiddleware } from './build/middlewares.js';
import polka from 'polka';
const app = polka();
const myMiddleware = function(req, res, next) {
console.log('Hello world!');
next();
};
app.use(myMiddleware);
app.get('/no-svelte', (req, res) => {
res.end('This is not Svelte!')
});
app.use(assetsMiddleware, prerenderedMiddleware, kitMiddleware);
app.listen(3000)
```

## Advanced Configuration

### esbuild
Expand Down
33 changes: 30 additions & 3 deletions packages/adapter-node/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ export default function ({
await compress(static_directory);
}

utils.log.minor('Building server');
utils.log.minor('Building SvelteKit middleware');
const files = fileURLToPath(new URL('./files', import.meta.url));
utils.copy(files, '.svelte-kit/node');
writeFileSync(
Expand All @@ -54,10 +54,11 @@ export default function ({
port_env
)}] || (!path && 3000);`
);

/** @type {BuildOptions} */
const defaultOptions = {
entryPoints: ['.svelte-kit/node/index.js'],
outfile: join(out, 'index.js'),
entryPoints: ['.svelte-kit/node/middlewares.js'],
outfile: join(out, 'middlewares.js'),
bundle: true,
external: Object.keys(JSON.parse(readFileSync('package.json', 'utf8')).dependencies || {}),
format: 'esm',
Expand All @@ -71,6 +72,32 @@ export default function ({
const buildOptions = esbuildConfig ? await esbuildConfig(defaultOptions) : defaultOptions;
await esbuild.build(buildOptions);

utils.log.minor('Building SvelteKit reference server');
/** @type {BuildOptions} */
const defaultOptionsRefServer = {
entryPoints: ['.svelte-kit/node/index.js'],
outfile: join(out, 'index.js'),
bundle: true,
external: ['./middlewares.js'], // does not work, eslint does not exclude middlewares from target
format: 'esm',
platform: 'node',
target: 'node12',
// external exclude workaround, see https://github.com/evanw/esbuild/issues/514
plugins: [
{
name: 'fix-middlewares-exclude',
setup(build) {
// Match an import called "./middlewares.js" and mark it as external
build.onResolve({ filter: /^\.\/middlewares\.js$/ }, () => ({ external: true }));
}
}
]
};
const buildOptionsRefServer = esbuildConfig
? await esbuildConfig(defaultOptionsRefServer)
: defaultOptionsRefServer;
await esbuild.build(buildOptionsRefServer);

utils.log.minor('Prerendering static pages');
await utils.prerender({
dest: `${out}/prerendered`
Expand Down
12 changes: 11 additions & 1 deletion packages/adapter-node/rollup.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,16 @@ import commonjs from '@rollup/plugin-commonjs';
import json from '@rollup/plugin-json';

export default [
{
input: 'src/middlewares.js',
output: {
file: 'files/middlewares.js',
format: 'esm',
sourcemap: true
},
plugins: [nodeResolve(), commonjs(), json()],
external: ['../output/server/app.js', ...require('module').builtinModules]
},
{
input: 'src/index.js',
output: {
Expand All @@ -11,7 +21,7 @@ export default [
sourcemap: true
},
plugins: [nodeResolve(), commonjs(), json()],
external: ['../output/server/app.js', './env.js', ...require('module').builtinModules]
external: ['./middlewares.js', './env.js', ...require('module').builtinModules]
},
{
input: 'src/shims.js',
Expand Down
23 changes: 14 additions & 9 deletions packages/adapter-node/src/index.js
Original file line number Diff line number Diff line change
@@ -1,17 +1,22 @@
// TODO hardcoding the relative location makes this brittle
// @ts-ignore
import { init, render } from '../output/server/app.js';
// @ts-ignore
import { path, host, port } from './env.js';
import { createServer } from './server';

init();
import { assetsMiddleware, kitMiddleware, prerenderedMiddleware } from './middlewares.js';
import compression from 'compression';
import polka from 'polka';

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

const listenOpts = { path, host, port };
instance.listen(listenOpts, () => {

server.listen(listenOpts, () => {
console.log(`Listening on ${path ? path : host + ':' + port}`);
});

export { instance };
export { server };
40 changes: 40 additions & 0 deletions packages/adapter-node/src/kit-middleware.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { getRawBody } from '@sveltejs/kit/node';

/**
* @return {import('polka').Middleware}
*/
// TODO: type render function from @sveltejs/kit/adapter
// @ts-ignore
export function create_kit_middleware({ render }) {
return async (req, res) => {
const parsed = new URL(req.url || '', 'http://localhost');

let body;

try {
body = await getRawBody(req);
} catch (err) {
res.statusCode = err.status || 400;
return res.end(err.reason || 'Invalid request body');
}

const rendered = await render({
method: req.method,
headers: req.headers, // TODO: what about repeated headers, i.e. string[]
path: parsed.pathname,
query: parsed.searchParams,
rawBody: body
});

if (rendered) {
res.writeHead(rendered.status, rendered.headers);
if (rendered.body) {
res.write(rendered.body);
}
res.end();
} else {
res.statusCode = 404;
res.end('Not found');
}
};
}
48 changes: 48 additions & 0 deletions packages/adapter-node/src/middlewares.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
// TODO hardcoding the relative location makes this brittle
// Also, we need most of the logic in another file for testing because
// ../output/server/app.js doesn't exist when we run the tests
// @ts-ignore
import { init, render } from '../output/server/app.js';
import { create_kit_middleware } from './kit-middleware.js';

import fs from 'fs';
import { dirname, join } from 'path';
import sirv from 'sirv';
import { fileURLToPath } from 'url';

// App is a dynamic file built from the application layer.

const __dirname = dirname(fileURLToPath(import.meta.url));
/** @type {import('polka').Middleware} */
const noop_handler = (_req, _res, next) => next();
const paths = {
assets: join(__dirname, '/assets'),
prerendered: join(__dirname, '/prerendered')
};

export const prerenderedMiddleware = fs.existsSync(paths.prerendered)
? sirv(paths.prerendered, {
etag: true,
maxAge: 0,
gzip: true,
brotli: true
})
: noop_handler;

export const assetsMiddleware = fs.existsSync(paths.assets)
? sirv(paths.assets, {
setHeaders: (res, pathname) => {
// @ts-expect-error - dynamically replaced with define
if (pathname.startsWith(/* eslint-disable-line no-undef */ APP_DIR)) {
res.setHeader('cache-control', 'public, max-age=31536000, immutable');
}
},
gzip: true,
brotli: true
})
: noop_handler;

export const kitMiddleware = (function () {
init();
return create_kit_middleware({ render });
})();
82 changes: 0 additions & 82 deletions packages/adapter-node/src/server.js

This file was deleted.

7 changes: 4 additions & 3 deletions packages/adapter-node/tests/smoke.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
import { test } from 'uvu';
import { createServer } from '../src/server.js';
import { create_kit_middleware } from '../src/kit-middleware.js';
import * as assert from 'uvu/assert';
import fetch from 'node-fetch';
import polka from 'polka';

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

function startServer(opts = DEFAULT_SERVER_OPTS) {
const server = createServer(opts);
async function startServer(opts = DEFAULT_SERVER_OPTS) {
return new Promise((fulfil, reject) => {
const server = polka().use(create_kit_middleware(opts));
server.listen(PORT, (err) => {
if (err) {
reject(err);
Expand Down

0 comments on commit 4dcde29

Please sign in to comment.