diff --git a/documentation/faq/80-integrations.md b/documentation/faq/80-integrations.md index bd3a69011e415..5b38583802cc3 100644 --- a/documentation/faq/80-integrations.md +++ b/documentation/faq/80-integrations.md @@ -65,9 +65,9 @@ onMount(() => { Put the code to query your database in [endpoints](/docs#routing-endpoints) - don't query the database in .svelte files. You can create a `db.js` or similar that sets up a connection immediately and makes the client accessible throughout the app as a singleton. You can execute any one-time setup code in `hooks.js` and import your database helpers into any endpoint that needs them. -### How use middleware? +### How do I use middleware? -In dev, you can add middleware to Vite by using a Vite plugin. For example: +You can add middleware to `adapter-node` for production mode. In dev, you can add middleware to Vite by using a Vite plugin. For example: ```js const myPlugin = { diff --git a/packages/adapter-node/README.md b/packages/adapter-node/README.md index 1b90a76740f0c..86d4ad81e3269 100644 --- a/packages/adapter-node/README.md +++ b/packages/adapter-node/README.md @@ -45,6 +45,38 @@ 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 [Express](https://github.com/expressjs/expressjs.com) / +[Polka](https://github.com/lukeed/polka) compatible middleware function `(req, res, next) => {}` +as well as a reference server implementation using this 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 `createHandler()`, your ready-to-use SvelteKit bundle as middleware, from `./build/handler.js`. + +``` +import { createHandler } from './build/handler.js'; +import express from 'express'; + +const app = express(); + +var myMiddleware = function (req, res, next) { + console.log('Hello world!'); + next(); +}; + +app.use(myMiddleware); + +app.get('/no-svelte', (req, res) => { + res.send('This is not Svelte!') +}); + +// SvelteKit +app.get('*', createHandler()); + +app.listen(3000) +``` + ## Advanced Configuration ### esbuild diff --git a/packages/adapter-node/index.js b/packages/adapter-node/index.js index bbcef9d35be1c..c0731e77e0573 100644 --- a/packages/adapter-node/index.js +++ b/packages/adapter-node/index.js @@ -53,7 +53,7 @@ export default function ({ await compress(static_directory); } - utils.log.minor('Building server'); + utils.log.minor('Building SvelteKit request handler'); const files = fileURLToPath(new URL('./files', import.meta.url)); utils.copy(files, '.svelte-kit/node'); writeFileSync( @@ -66,10 +66,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/handler.js'], + outfile: join(out, 'handler.js'), bundle: true, external: Object.keys(JSON.parse(readFileSync('package.json', 'utf8')).dependencies || {}), format: 'esm', @@ -83,6 +84,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: ['./handler.js'], // does not work, eslint does not exclude handler from target + format: 'esm', + platform: 'node', + target: 'node12', + // external exclude workaround, see https://github.com/evanw/esbuild/issues/514 + plugins: [ + { + name: 'fix-handler-exclude', + setup(build) { + // Match an import called "./handler.js" and mark it as external + build.onResolve({ filter: /^\.\/handler\.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` diff --git a/packages/adapter-node/rollup.config.js b/packages/adapter-node/rollup.config.js index 51c98aa0f0831..8388f01e3eae2 100644 --- a/packages/adapter-node/rollup.config.js +++ b/packages/adapter-node/rollup.config.js @@ -3,6 +3,16 @@ import commonjs from '@rollup/plugin-commonjs'; import json from '@rollup/plugin-json'; export default [ + { + input: 'src/handler.js', + output: { + file: 'files/handler.js', + format: 'esm', + sourcemap: true + }, + plugins: [nodeResolve(), commonjs(), json()], + external: ['../output/server/app.js', ...require('module').builtinModules] + }, { input: 'src/index.js', output: { @@ -11,7 +21,7 @@ export default [ sourcemap: true }, plugins: [nodeResolve(), commonjs(), json()], - external: ['../output/server/app.js', './env.js', ...require('module').builtinModules] + external: ['./handler.js', './env.js', ...require('module').builtinModules] }, { input: 'src/shims.js', diff --git a/packages/adapter-node/src/handler.js b/packages/adapter-node/src/handler.js new file mode 100644 index 0000000000000..0e2aab86da268 --- /dev/null +++ b/packages/adapter-node/src/handler.js @@ -0,0 +1,8 @@ +// TODO hardcoding the relative location makes this brittle +import { init, render } from '../output/server/app.js'; // eslint-disable-line import/no-unresolved +import { createPolkaHandler } from './polka-handler.js'; + +export function createHandler() { + init(); + return createPolkaHandler({ render }); +} diff --git a/packages/adapter-node/src/index.js b/packages/adapter-node/src/index.js index b7d7c27dc5e4a..8513ff911afd0 100644 --- a/packages/adapter-node/src/index.js +++ b/packages/adapter-node/src/index.js @@ -1,15 +1,11 @@ -// TODO hardcoding the relative location makes this brittle -import { init, render } from '../output/server/app.js'; // eslint-disable-line import/no-unresolved import { path, host, port } from './env.js'; // eslint-disable-line import/no-unresolved -import { createServer } from './server'; - -init(); - -const instance = createServer({ render }); +import { createHandler } from './handler.js'; +import { createServer } from 'http'; +const server = createServer(createHandler()); const listenOpts = { path, host, port }; -instance.listen(listenOpts, () => { +server.listen(listenOpts, () => { console.log(`Listening on ${path ? path : host + ':' + port}`); }); -export { instance }; +export { server }; diff --git a/packages/adapter-node/src/server.js b/packages/adapter-node/src/polka-handler.js similarity index 94% rename from packages/adapter-node/src/server.js rename to packages/adapter-node/src/polka-handler.js index 4dc6c69aa209b..6b65b20dbfaf9 100644 --- a/packages/adapter-node/src/server.js +++ b/packages/adapter-node/src/polka-handler.js @@ -15,7 +15,7 @@ const paths = { prerendered: join(__dirname, '/prerendered') }; -export function createServer({ render }) { +export function createPolkaHandler({ render }) { const prerendered_handler = fs.existsSync(paths.prerendered) ? sirv(paths.prerendered, { etag: true, @@ -38,7 +38,7 @@ export function createServer({ render }) { }) : noop_handler; - const server = polka().use( + const polka_instance = polka().use( compression({ threshold: 0 }), assets_handler, prerendered_handler, @@ -73,5 +73,5 @@ export function createServer({ render }) { } ); - return server; + return polka_instance.handler; } diff --git a/packages/adapter-node/tests/smoke.js b/packages/adapter-node/tests/smoke.js index 6a631ccdc9e8b..3340d0ec4e727 100644 --- a/packages/adapter-node/tests/smoke.js +++ b/packages/adapter-node/tests/smoke.js @@ -1,14 +1,17 @@ import { test } from 'uvu'; -import { createServer } from '../src/server.js'; +import { createPolkaHandler } from '../src/polka-handler.js'; import * as assert from 'uvu/assert'; import fetch from 'node-fetch'; +import { createServer } from 'http'; const { PORT = 3000 } = process.env; const DEFAULT_SERVER_OPTS = { render: () => {} }; function startServer(opts = DEFAULT_SERVER_OPTS) { - const server = createServer(opts); + const handler = createPolkaHandler(opts); + return new Promise((fulfil, reject) => { + const server = createServer(handler); server.listen(PORT, (err) => { if (err) { reject(err); @@ -21,14 +24,14 @@ function startServer(opts = DEFAULT_SERVER_OPTS) { test('starts a server', async () => { const server = await startServer(); assert.ok('server started'); - server.server.close(); + server.close(); }); test('serves a 404', async () => { const server = await startServer(); const res = await fetch(`http://localhost:${PORT}/nothing`); assert.equal(res.status, 404); - server.server.close(); + server.close(); }); test('responses with the rendered status code', async () => { @@ -43,7 +46,7 @@ test('responses with the rendered status code', async () => { }); const res = await fetch(`http://localhost:${PORT}/wow`); assert.equal(res.status, 203); - server.server.close(); + server.close(); }); test('passes through umlaut as encoded path', async () => {