diff --git a/package.json b/package.json index 2c45f26d110ca1..36394b07730fec 100644 --- a/package.json +++ b/package.json @@ -91,6 +91,7 @@ "rollup": "^2.7.2", "rollup-plugin-terser": "^5.3.0", "rollup-plugin-vue": "^6.0.0-beta.1", + "selfsigned": "^1.10.7", "slash": "^3.0.0", "vue": "^3.0.0-beta.14", "ws": "^7.2.3" diff --git a/src/node/cli.ts b/src/node/cli.ts index 12494619614deb..51cdf32d5acad1 100644 --- a/src/node/cli.ts +++ b/src/node/cli.ts @@ -108,15 +108,11 @@ async function resolveOptions() { return argv } -async function runServe( - options: UserConfig & { - port?: number - open?: boolean - } -) { +async function runServe(options: UserConfig) { const server = require('../dist').createServer(options) let port = options.port || 3000 + const protocol = options.https ? 'https' : 'http' server.on('error', (e: Error & { code?: string }) => { if (e.code === 'EADDRINUSE') { console.log(`Port ${port} is in use, trying another one...`) @@ -146,7 +142,7 @@ async function runServe( } }) .forEach(({ type, host }) => { - const url = `http://${host}:${chalk.bold(port)}/` + const url = `${protocol}://${host}:${chalk.bold(port)}/` console.log(` > ${type} ${chalk.cyan(url)}`) }) }) diff --git a/src/node/config.ts b/src/node/config.ts index 0e1e5d44846a06..895247d84e0995 100644 --- a/src/node/config.ts +++ b/src/node/config.ts @@ -14,6 +14,7 @@ import Rollup, { import { Transform } from './transform' import { DepOptimizationOptions } from './depOptimizer' import { IKoaProxiesOptions } from 'koa-proxies' +import { ServerOptions } from 'https' export { Resolver, Transform } @@ -84,6 +85,13 @@ export interface SharedConfig { } export interface ServerConfig extends SharedConfig { + port?: number + open?: boolean + /** + * Configure https. + */ + https?: boolean + httpsOption?: ServerOptions /** * Configure custom proxy rules for the dev server. Uses * [`koa-proxies`](https://github.com/vagusX/koa-proxies) which in turn uses diff --git a/src/node/server/index.ts b/src/node/server/index.ts index 97aec42b1d570b..f72bfdd599cd89 100644 --- a/src/node/server/index.ts +++ b/src/node/server/index.ts @@ -1,4 +1,5 @@ -import http, { Server } from 'http' +import { RequestListener, Server } from 'http' +import { ServerOptions } from 'https' import Koa from 'koa' import chokidar from 'chokidar' import { createResolver, InternalResolver } from '../resolver' @@ -15,7 +16,9 @@ import { ServerConfig } from '../config' import { createServerTransformPlugin } from '../transform' import { serviceWorkerPlugin } from './serverPluginServiceWorker' import { proxyPlugin } from './serverPluginProxy' - +import { createCertificate } from '../utils/createCertificate' +import fs from 'fs-extra' +import path from 'path' export { rewriteImports } from './serverPluginModuleRewrite' export type ServerPlugin = (ctx: ServerPluginContext) => void @@ -40,7 +43,7 @@ export function createServer(config: ServerConfig = {}): Server { } = config const app = new Koa() - const server = http.createServer(app.callback()) + const server = resolveServer(config, app.callback()) const watcher = chokidar.watch(root, { ignored: [/node_modules/] }) as HMRWatcher @@ -82,3 +85,42 @@ export function createServer(config: ServerConfig = {}): Server { return server } + +function resolveServer( + { https = false, httpsOption = {} }: ServerConfig, + requestListener: RequestListener +) { + if (https) { + return require('https').createServer( + resolveHttpsConfig(httpsOption), + requestListener + ) + } else { + return require('http').createServer(requestListener) + } +} + +function resolveHttpsConfig(httpsOption: ServerOptions) { + const { ca, cert, key, pfx } = httpsOption + Object.assign(httpsOption, { + ca: readFileIfExits(ca), + cert: readFileIfExits(cert), + key: readFileIfExits(key), + pfx: readFileIfExits(pfx) + }) + if (!httpsOption.key || !httpsOption.cert) { + httpsOption.cert = httpsOption.key = createCertificate() + } + return httpsOption +} + +function readFileIfExits(value?: string | Buffer | any) { + if (value && !Buffer.isBuffer(value)) { + try { + return fs.readFileSync(path.resolve(value as string)) + } catch (e) { + return value + } + } + return value +} diff --git a/src/node/utils/createCertificate.ts b/src/node/utils/createCertificate.ts new file mode 100644 index 00000000000000..7eedb495bb7007 --- /dev/null +++ b/src/node/utils/createCertificate.ts @@ -0,0 +1,65 @@ +// https://github.com/webpack/webpack-dev-server/blob/master/lib/utils/createCertificate.js +export function createCertificate() { + const pems = require('selfsigned').generate(null, { + algorithm: 'sha256', + days: 30, + keySize: 2048, + extensions: [ + // { + // name: 'basicConstraints', + // cA: true, + // }, + { + 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 +} diff --git a/yarn.lock b/yarn.lock index fb3f7508df03bf..b50e7f806098d5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4858,6 +4858,11 @@ nice-try@^1.0.4: resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366" integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ== +node-forge@0.9.0: + version "0.9.0" + resolved "http://snpm.cnsuning.com/node-forge/-/node-forge-0.9.0.tgz#d624050edbb44874adca12bb9a52ec63cb782579" + integrity sha1-1iQFDtu0SHStyhK7mlLsY8t4JXk= + node-int64@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b" @@ -6376,6 +6381,13 @@ saxes@^3.1.9: dependencies: xmlchars "^2.1.1" +selfsigned@^1.10.7: + version "1.10.7" + resolved "http://snpm.cnsuning.com/selfsigned/-/selfsigned-1.10.7.tgz#da5819fd049d5574f28e88a9bcc6dbc6e6f3906b" + integrity sha1-2lgZ/QSdVXTyjoipvMbbxubzkGs= + dependencies: + node-forge "0.9.0" + semver-compare@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/semver-compare/-/semver-compare-1.0.0.tgz#0dee216a1c941ab37e9efb1788f6afc5ff5537fc"