diff --git a/packages/adapter-node/package.json b/packages/adapter-node/package.json index 3cfb7059ace5a..cf8d5a5141332 100644 --- a/packages/adapter-node/package.json +++ b/packages/adapter-node/package.json @@ -2,12 +2,14 @@ "name": "@sveltejs/adapter-node", "version": "1.0.0-next.11", "main": "index.js", + "type": "module", "files": [ "files" ], "scripts": { "dev": "rollup -cw", "build": "rollup -c", + "test": "uvu tests", "lint": "eslint --ignore-path .gitignore \"**/*.{ts,js,svelte}\" && npm run check-format", "format": "prettier --write . --config ../../.prettierrc --ignore-path .gitignore", "check-format": "prettier --check . --config ../../.prettierrc --ignore-path .gitignore", @@ -17,9 +19,11 @@ "@rollup/plugin-json": "^4.1.0", "@sveltejs/kit": "workspace:*", "compression": "^1.7.4", + "node-fetch": "^2.6.1", "polka": "^1.0.0-next.13", "rollup": "^2.41.1", "sirv": "^1.0.11", - "typescript": "^4.2.3" + "typescript": "^4.2.3", + "uva": "^0.1.1" } } diff --git a/packages/adapter-node/src/app.js b/packages/adapter-node/src/app.js new file mode 100644 index 0000000000000..4b1f5ecce5de4 --- /dev/null +++ b/packages/adapter-node/src/app.js @@ -0,0 +1,2 @@ + + export function render() {} diff --git a/packages/adapter-node/src/server.js b/packages/adapter-node/src/server.js index 15947db4918e1..c4993b5aeef57 100644 --- a/packages/adapter-node/src/server.js +++ b/packages/adapter-node/src/server.js @@ -1,57 +1,13 @@ -import compression from 'compression'; -import fs from 'fs'; -import polka from 'polka'; -import { dirname, join } from 'path'; -import sirv from 'sirv'; -import { URL, fileURLToPath } from 'url'; -// eslint-disable-next-line import/no-unresolved -import { get_body } from '@sveltejs/kit/http'; -// App is a dynamic file built from the application layer. -/*eslint import/no-unresolved: [2, { ignore: ['\.\/app\.js$'] }]*/ -import * as app from './app.js'; - -const __dirname = dirname(fileURLToPath(import.meta.url)); +import { createServer } from './server_base.js'; const { PORT = 3000 } = process.env; // TODO configure via svelte.config.js -const mutable = (dir) => - sirv(dir, { - etag: true, - maxAge: 0 - }); - -const noop_handler = (_req, _res, next) => next(); - -const prerendered_handler = fs.existsSync('prerendered') ? mutable('prerendered') : noop_handler; - -const assets_handler = sirv(join(__dirname, '/assets'), { - maxAge: 31536000, - immutable: true +const instance = createServer().listen(PORT, (err) => { + if (err) { + console.log('error', err); + } else { + console.log(`Listening on port ${PORT}`); + } }); -polka() - .use(compression({ threshold: 0 }), assets_handler, prerendered_handler, async (req, res) => { - const parsed = new URL(req.url || '', 'http://localhost'); - const rendered = await app.render({ - method: req.method, - headers: req.headers, // TODO: what about repeated headers, i.e. string[] - path: parsed.pathname, - body: await get_body(req), - query: parsed.searchParams - }); - - if (rendered) { - res.writeHead(rendered.status, rendered.headers); - res.end(rendered.body); - } else { - res.statusCode = 404; - res.end('Not found'); - } - }) - .listen(PORT, (err) => { - if (err) { - console.log('error', err); - } else { - console.log(`Listening on port ${PORT}`); - } - }); +export { instance }; diff --git a/packages/adapter-node/src/server_base.js b/packages/adapter-node/src/server_base.js new file mode 100644 index 0000000000000..d1702af0b1fed --- /dev/null +++ b/packages/adapter-node/src/server_base.js @@ -0,0 +1,56 @@ +import compression from 'compression'; +import fs from 'fs'; +import polka from 'polka'; +import { dirname, join } from 'path'; +import sirv from 'sirv'; +import { URL, fileURLToPath } from 'url'; +// eslint-disable-next-line import/no-unresolved +import { get_body } from '@sveltejs/kit/http'; +// App is a dynamic file built from the application layer. +/*eslint import/no-unresolved: [2, { ignore: ['\.\/app\.js$'] }]*/ +import * as app from './app.js'; + +const __dirname = dirname(fileURLToPath(import.meta.url)); + +const mutable = (dir) => + sirv(dir, { + etag: true, + maxAge: 0 + }); + +const noop_handler = (_req, _res, next) => next(); + +const prerendered_handler = fs.existsSync('prerendered') ? mutable('prerendered') : noop_handler; + +const assets_handler = sirv(join(__dirname, '/assets'), { + maxAge: 31536000, + immutable: true +}); + +export function createServer() { + const server = polka().use( + compression({ threshold: 0 }), + assets_handler, + prerendered_handler, + async (req, res) => { + const parsed = new URL(req.url || '', 'http://localhost'); + const rendered = await app.render({ + method: req.method, + headers: req.headers, // TODO: what about repeated headers, i.e. string[] + path: parsed.pathname, + body: await get_body(req), + query: parsed.searchParams + }); + + if (rendered) { + res.writeHead(rendered.status, rendered.headers); + res.end(rendered.body); + } else { + res.statusCode = 404; + res.end('Not found'); + } + } + ); + + return server; +} diff --git a/packages/adapter-node/tests/setup.js b/packages/adapter-node/tests/setup.js new file mode 100644 index 0000000000000..d6878ec881d5f --- /dev/null +++ b/packages/adapter-node/tests/setup.js @@ -0,0 +1,16 @@ +import * as path from 'path'; +const __dirname = path.dirname(fileURLToPath(import.meta.url)); +import * as fs from 'fs'; +import { fileURLToPath } from 'url'; +const appPath = path.resolve(path.join(__dirname, '../src/app.js')); +const assetsPath = path.resolve(path.join(__dirname, '../src/assets')); + +fs.writeFileSync(appPath, ` + export function render() {} +`); + +try { + fs.mkdirSync(assetsPath); +} catch (e) { + // it is ok if this is already there. +} \ No newline at end of file diff --git a/packages/adapter-node/tests/smoke.js b/packages/adapter-node/tests/smoke.js new file mode 100644 index 0000000000000..14870929777f8 --- /dev/null +++ b/packages/adapter-node/tests/smoke.js @@ -0,0 +1,35 @@ +import { test } from 'uvu'; +import { createServer } from '../src/server_base.js'; +import * as assert from 'uvu/assert'; +import fetch from 'node-fetch'; + +const { PORT = 3000 } = process.env; + + +function startServer() { + const server = createServer(); + return new Promise((res, rej) => { + server.listen(PORT, err => { + if (err) { + rej(err); + } + res(server); + }); + }); +} + +test('starts a server', async () => { + const server = await startServer(); + assert.ok('server started'); + server.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(); +}); + + +test.run(); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8bbb385eeab29..859b0ebdfdc7a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -116,18 +116,22 @@ importers: '@rollup/plugin-json': 4.1.0_rollup@2.41.1 '@sveltejs/kit': link:../kit compression: 1.7.4 + node-fetch: 2.6.1 polka: 1.0.0-next.13 rollup: 2.41.1 sirv: 1.0.11 typescript: 4.2.3 + uva: 0.1.1 specifiers: '@rollup/plugin-json': ^4.1.0 '@sveltejs/kit': workspace:* compression: ^1.7.4 + node-fetch: ^2.6.1 polka: ^1.0.0-next.13 rollup: ^2.41.1 sirv: ^1.0.11 typescript: ^4.2.3 + uva: ^0.1.1 packages/adapter-static: devDependencies: '@sveltejs/kit': link:../kit @@ -962,6 +966,10 @@ packages: node: '>=4' resolution: integrity: sha512-pbnl5XzGBdrFU/wT4jqmJVPn2B6UHPBOhzMQkY/SPUPB6QtUXtmBHBIwCbXJol93mOpGMnQyP/+BB19q04xj7g== + /bind-ponyfill/0.1.0: + dev: true + resolution: + integrity: sha1-8zi6TYMFWbRVTweyagm5VpcvpmU= /boxen/1.3.0: dependencies: ansi-align: 2.0.0 @@ -1020,6 +1028,12 @@ packages: dev: true resolution: integrity: sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA== + /callback-store/0.2.1: + dependencies: + bind-ponyfill: 0.1.0 + dev: true + resolution: + integrity: sha1-cs1eLaFn5AbluXhdH68GVsodIBs= /callsites/3.1.0: dev: true engines: @@ -2250,6 +2264,10 @@ packages: graceful-fs: 4.2.6 resolution: integrity: sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss= + /kamikaze/0.1.0: + dev: true + resolution: + integrity: sha1-92iC0NSJuqW3l7QTTLbPVa93Sas= /kind-of/6.0.3: dev: true engines: @@ -2492,7 +2510,6 @@ packages: resolution: integrity: sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw== /node-fetch/2.6.1: - dev: false engines: node: 4.x || >=6.0.0 resolution: @@ -3255,6 +3272,10 @@ packages: node: '>=10' resolution: integrity: sha512-gL//fkxfWUsIlFL2Tl42Cl6+HFALEaB1FU76I/Fy+oZjRreP7OPMXFlGbxM7NQsI0ZpUfw76sHnv0WNYuTb7Iw== + /state-emitter/0.2.0: + dev: true + resolution: + integrity: sha1-LkPYAIjA9PybH1yw4z0Cd3GNyrk= /stream-transform/2.0.4: dependencies: mixme: 0.4.0 @@ -3545,6 +3566,15 @@ packages: dev: true resolution: integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg== + /uva/0.1.1: + dependencies: + callback-store: 0.2.1 + debug: 2.6.9 + kamikaze: 0.1.0 + state-emitter: 0.2.0 + dev: true + resolution: + integrity: sha1-0O+qXJlwjg1JAnUXVephmfyytoc= /uvu/0.5.1: dependencies: dequal: 2.0.2