Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[feat] expose handler to allow use in custom server #2051

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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`.

```
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Other questions the docs should answer: Where do you put this file? Is there some option you need to specify to make it use this vs the default?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure what you mean. Using it this way is kind of advanced stuff for experienced node users. So I am not sure how to verbose to document. I'm not a native speaker, so feel free to come up with better documentations.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I mean if you want to create your own server using the code below does it matter what the file path or name is?

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