Skip to content

Commit

Permalink
support https (#462)
Browse files Browse the repository at this point in the history
* support https

* tidy up, typecheck

* clarify

* changeset

Co-authored-by: Rich Harris <[email protected]>
  • Loading branch information
tanhauhau and Rich-Harris authored Apr 2, 2021
1 parent ba4f9b7 commit 24fab19
Show file tree
Hide file tree
Showing 8 changed files with 197 additions and 70 deletions.
5 changes: 5 additions & 0 deletions .changeset/blue-squids-march.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@sveltejs/kit': patch
---

Add --https flag to dev and start
1 change: 1 addition & 0 deletions packages/kit/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
"port-authority": "^1.1.2",
"rimraf": "^3.0.2",
"rollup": "^2.41.1",
"selfsigned": "^1.10.8",
"sirv": "^1.0.11",
"svelte": "^3.35.0",
"tiny-glob": "^0.2.8",
Expand Down
22 changes: 13 additions & 9 deletions packages/kit/src/cli.js
Original file line number Diff line number Diff line change
Expand Up @@ -70,8 +70,9 @@ prog
.describe('Start a development server')
.option('-p, --port', 'Port', 3000)
.option('-h, --host', 'Host (only use this on trusted networks)', 'localhost')
.option('--https', 'use self-signed HTTPS certificate', false)
.option('-o, --open', 'Open a browser tab', false)
.action(async ({ port, host, open }) => {
.action(async ({ port, host, https, open }) => {
await check_port(port);

process.env.NODE_ENV = 'development';
Expand All @@ -80,7 +81,7 @@ prog
const { dev } = await import('./core/dev/index.js');

try {
const watcher = await dev({ port, host, config });
const watcher = await dev({ port, host, https, config });

watcher.on('stdout', (data) => {
process.stdout.write(data);
Expand All @@ -90,7 +91,7 @@ prog
process.stderr.write(data);
});

welcome({ port, host, open });
welcome({ port, host, https, open });
} catch (error) {
handle_error(error);
}
Expand Down Expand Up @@ -131,8 +132,9 @@ prog
.describe('Serve an already-built app')
.option('-p, --port', 'Port', 3000)
.option('-h, --host', 'Host (only use this on trusted networks)', 'localhost')
.option('--https', 'use self-signed HTTPS certificate', false)
.option('-o, --open', 'Open a browser tab', false)
.action(async ({ port, host, open }) => {
.action(async ({ port, host, https, open }) => {
await check_port(port);

process.env.NODE_ENV = 'production';
Expand All @@ -141,9 +143,9 @@ prog
const { start } = await import('./core/start/index.js');

try {
await start({ port, host, config });
await start({ port, host, config, https });

welcome({ port, host, open });
welcome({ port, host, https, open });
} catch (error) {
handle_error(error);
}
Expand Down Expand Up @@ -180,14 +182,16 @@ async function check_port(port) {
* @param {{
* open: boolean;
* host: string;
* https: boolean;
* port: number;
* }} param0
*/
function welcome({ port, host, open }) {
function welcome({ port, host, https, open }) {
if (open) launch(port);

console.log(colors.bold().cyan(`\n SvelteKit v${'__VERSION__'}\n`));

const protocol = https ? 'https:' : 'http:';
const exposed = host !== 'localhost' && host !== '127.0.0.1';

Object.values(networkInterfaces()).forEach((interfaces) => {
Expand All @@ -196,12 +200,12 @@ function welcome({ port, host, open }) {

// prettier-ignore
if (details.internal) {
console.log(` ${colors.gray('local: ')} http://${colors.bold(`localhost:${port}`)}`);
console.log(` ${colors.gray('local: ')} ${protocol}//${colors.bold(`localhost:${port}`)}`);
} else {
if (details.mac === '00:00:00:00:00:00') return;

if (exposed) {
console.log(` ${colors.gray('network:')} http://${colors.bold(`${details.address}:${port}`)}`);
console.log(` ${colors.gray('network:')} ${protocol}//${colors.bold(`${details.address}:${port}`)}`);
} else {
console.log(` ${colors.gray('network: not exposed')}`);
}
Expand Down
34 changes: 16 additions & 18 deletions packages/kit/src/core/dev/index.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import http from 'http';
import fs from 'fs';
import path from 'path';
import { parse, URLSearchParams } from 'url';
Expand All @@ -13,8 +12,9 @@ import { ssr } from '../../runtime/server/index.js';
import { get_body } from '../http/index.js';
import { copy_assets } from '../utils.js';
import svelte from '@sveltejs/vite-plugin-svelte';
import { get_server } from '../server/index.js';

/** @typedef {{ cwd?: string, port: number, host: string, config: import('../../../types.internal').ValidatedConfig }} Options */
/** @typedef {{ cwd?: string, port: number, host: string, https: boolean | import('https').ServerOptions, config: import('../../../types.internal').ValidatedConfig }} Options */
/** @typedef {import('../../../types.internal').SSRComponent} SSRComponent */

/** @param {Options} opts */
Expand All @@ -24,14 +24,15 @@ export function dev(opts) {

class Watcher extends EventEmitter {
/** @param {Options} opts */
constructor({ cwd = process.cwd(), port, host, config }) {
constructor({ cwd = process.cwd(), port, host, https, config }) {
super();

this.cwd = cwd;
this.dir = path.resolve(cwd, '.svelte/dev');

this.port = port;
this.host = host;
this.https = https;
this.config = config;

process.env.NODE_ENV = 'development';
Expand Down Expand Up @@ -80,7 +81,7 @@ class Watcher extends EventEmitter {
/**
* @type {vite.ViteDevServer}
*/
this.viteDevServer = await vite.createServer({
this.vite = await vite.createServer({
...user_config,
configFile: false,
root: this.cwd,
Expand Down Expand Up @@ -113,8 +114,8 @@ class Watcher extends EventEmitter {

const validator = this.config.kit.amp && (await amp_validator.getInstance());

this.server = http.createServer((req, res) => {
this.viteDevServer.middlewares(req, res, async () => {
this.server = await get_server(this.port, this.host, this.https, (req, res) => {
this.vite.middlewares(req, res, async () => {
try {
const parsed = parse(req.url);

Expand All @@ -123,15 +124,14 @@ class Watcher extends EventEmitter {
// handle dynamic requests - i.e. pages and endpoints
const template = fs.readFileSync(this.config.kit.files.template, 'utf-8');

const hooks = /** @type {import('../../../types.internal').Hooks} */ (await this.viteDevServer
const hooks = /** @type {import('../../../types.internal').Hooks} */ (await this.vite
.ssrLoadModule(`/${this.config.kit.files.hooks}`)
.catch(() => ({})));

let root;

try {
root = (await this.viteDevServer.ssrLoadModule(`/${this.dir}/generated/root.svelte`))
.default;
root = (await this.vite.ssrLoadModule(`/${this.dir}/generated/root.svelte`)).default;
} catch (e) {
res.statusCode = 500;
res.end(e.stack);
Expand Down Expand Up @@ -219,7 +219,7 @@ class Watcher extends EventEmitter {
only_render_prerenderable_pages: false,
get_component_path: (id) => `/${id}?import`,
get_stack: (error) => {
this.viteDevServer.ssrFixStacktrace(error);
this.vite.ssrFixStacktrace(error);
return error.stack;
},
get_static_file: (file) =>
Expand All @@ -239,13 +239,11 @@ class Watcher extends EventEmitter {
res.end('Not found');
}
} catch (e) {
this.viteDevServer.ssrFixStacktrace(e);
this.vite.ssrFixStacktrace(e);
res.end(e.stack);
}
});
});

this.server.listen(this.port, this.host || '0.0.0.0');
}

update() {
Expand Down Expand Up @@ -273,8 +271,8 @@ class Watcher extends EventEmitter {
const load = async (file) => {
const url = path.resolve(this.cwd, file);

const mod = /** @type {SSRComponent} */ (await this.viteDevServer.ssrLoadModule(url));
const node = await this.viteDevServer.moduleGraph.getModuleByUrl(url);
const mod = /** @type {SSRComponent} */ (await this.vite.ssrLoadModule(url));
const node = await this.vite.moduleGraph.getModuleByUrl(url);

const deps = new Set();
find_deps(node, deps);
Expand All @@ -284,7 +282,7 @@ class Watcher extends EventEmitter {
// TODO what about .scss files, etc?
if (dep.file.endsWith('.css')) {
try {
const mod = await this.viteDevServer.ssrLoadModule(dep.url);
const mod = await this.vite.ssrLoadModule(dep.url);
css.add(mod.default);
} catch {
// this can happen with dynamically imported modules, I think
Expand Down Expand Up @@ -367,7 +365,7 @@ class Watcher extends EventEmitter {
params: get_params(route.params),
load: async () => {
const url = path.resolve(this.cwd, route.file);
return await this.viteDevServer.ssrLoadModule(url);
return await this.vite.ssrLoadModule(url);
}
};
})
Expand All @@ -378,7 +376,7 @@ class Watcher extends EventEmitter {
if (this.closed) return;
this.closed = true;

this.viteDevServer.close();
this.vite.close();
this.server.close();
this.cheapwatch.close();
}
Expand Down
72 changes: 72 additions & 0 deletions packages/kit/src/core/server/cert.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
// @ts-ignore
import { generate } from 'selfsigned';

/**
* https://github.com/webpack/webpack-dev-server/blob/master/lib/utils/createCertificate.js
*
* Copyright JS Foundation and other contributors
* This source code is licensed under the MIT license found in the
* LICENSE file at
* https://github.com/webpack/webpack-dev-server/blob/master/LICENSE
*/
export function createCertificate() {
const pems = generate(null, {
algorithm: 'sha256',
days: 30,
keySize: 2048,
extensions: [
{
name: 'keyUsage',
keyCertSign: true,
digitalSignature: true,
nonRepudiation: true,
keyEncipherment: true,
dataEncipherment: true
},
{
name: 'extKeyUsage',
serverAuth: true,
clientAuth: true,
codeSigning: true,
timeStamping: true
},
{
name: 'subjectAltName',
altNames: [
{
// type 2 is DNS
type: 2,
value: 'localhost'
},
{
type: 2,
value: 'localhost.localdomain'
},
{
type: 2,
value: 'lvh.me'
},
{
type: 2,
value: '*.lvh.me'
},
{
type: 2,
value: '[::1]'
},
{
// type 7 is IP
type: 7,
ip: '127.0.0.1'
},
{
type: 7,
ip: 'fe80::1'
}
]
}
]
});

return pems.private + pems.cert;
}
31 changes: 31 additions & 0 deletions packages/kit/src/core/server/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import http from 'http';
import https from 'https';

/**
*
* @param {number} port
* @param {string} host
* @param {boolean | https.ServerOptions} https_options
* @param {(req: http.IncomingMessage, res: http.ServerResponse) => void} handler
* @returns {Promise<http.Server | https.Server>}
*/
export async function get_server(port, host, https_options, handler) {
if (https_options) {
https_options =
typeof https_options === 'boolean' ? /** @type {https.ServerOptions} */ ({}) : https_options;

if (!https_options.key || !https_options.cert) {
https_options.key = https_options.cert = (await import('./cert')).createCertificate();
}
}

return new Promise((fulfil) => {
const server = https_options
? https.createServer(/** @type {https.ServerOptions} */ (https_options), handler)
: http.createServer(handler);

server.listen(port, host || '0.0.0.0', () => {
fulfil(server);
});
});
}
Loading

0 comments on commit 24fab19

Please sign in to comment.