@softvisio/webpack
Version:
Webpack wrapper
190 lines (155 loc) • 5.13 kB
JavaScript
import fs from "node:fs";
import net from "node:net";
import path from "node:path";
import stream from "node:stream";
import tls from "node:tls";
import WebpackDevServer from "webpack-dev-server";
import * as certificates from "#core/certificates";
import { mergeObjects } from "#core/utils";
const defaultOptions = {
"server": {
"type": "https",
"options": {
"cert": null,
"key": null,
"dhparam": null,
},
},
"host": "0.0.0.0",
"port": 443,
"allowedHosts": "all",
"hot": true,
"compress": false,
"historyApiFallback": true,
"setupExitSignals": true,
"client": {
"logging": "none",
"progress": true,
"overlay": {
"errors": true,
"warnings": false,
"runtimeErrors": true,
},
},
"headers": {
"Service-Worker-Allowed": "/",
},
};
export default class DevServer {
#webpack;
#options;
#httpPort;
#proxyUrl;
#httpUrl;
#httpsUrl;
#httpsDomainUrl;
#httpsDomain;
constructor ( webpack, { listen, httpPort, httpsPort, proxyUrl } = {} ) {
this.#webpack = webpack;
this.#httpPort = httpPort ?? 80;
this.#proxyUrl = proxyUrl;
this.#options = mergeObjects( {}, defaultOptions, {
"host": listen || "0.0.0.0",
"port": httpsPort ?? 443,
"client": {
"overlay": {
"runtimeErrors": !this.#webpack.isProduction,
},
},
} );
}
// proporties
get httpUrl () {
this.#httpUrl ??= new URL( `http://${ this.#options.host }:${ this.#httpPort }/` ).href;
return this.#httpUrl;
}
get httpsUrl () {
this.#httpsUrl ??= new URL( `https://${ this.#options.host }:${ this.#options.port }/` ).href;
return this.#httpsUrl;
}
get httpsDomainUrl () {
if ( this.#httpsDomainUrl === undefined ) {
this.#httpsDomainUrl = null;
if ( this.#httpsDomain ) {
this.#httpsDomainUrl = new URL( `https://${ this.#httpsDomain }:${ this.#options.port }/` ).href;
}
}
return this.#httpsDomainUrl;
}
get proxyUrl () {
return this.#proxyUrl;
}
// public
async start ( compiler, { proxyUrl, https = {} } = {} ) {
var { domain, cert, key } = https;
this.#proxyUrl ??= proxyUrl;
this.#httpsDomain = domain;
// proxy
if ( this.#proxyUrl ) {
this.#proxyUrl = new URL( this.#proxyUrl ).href;
this.#options.proxy = [
{
"ws": true,
"target": this.#proxyUrl,
context ( path ) {
if ( path === "/ws" ) return false;
return true;
},
},
];
}
try {
if ( cert && key ) {
cert = path.resolve( this.#webpack.context, cert );
if ( !fs.existsSync( cert ) ) throw new Error();
key = path.resolve( this.#webpack.context, key );
if ( !fs.existsSync( key ) ) throw new Error();
this.#options.server.options.cert = cert;
this.#options.server.options.key = key;
}
else {
throw new Error();
}
}
catch {
this.#httpsDomain = certificates.localDomain;
this.#options.server.options.cert = certificates.localCertificatePath;
this.#options.server.options.key = certificates.localPrivateKeyPath;
this.#options.server.options.dhparam = certificates.dhParamsPath;
}
// start https server
const httpsServer = new WebpackDevServer( this.#options, compiler );
await httpsServer.start();
// start http proxy
await this.#startHttpProxy();
}
// private
#startHttpProxy () {
const server = new net.Server();
server.on( "connection", async localSocket => {
const remoteSocket = await this.#proxyConnect();
if ( !remoteSocket ) return localSocket.destroy();
stream.pipeline( localSocket, remoteSocket, () => {} );
stream.pipeline( remoteSocket, localSocket, () => {} );
} );
// listen
return new Promise( resolve => {
server.listen( this.#httpPort, this.#options.host, resolve );
} );
}
async #proxyConnect () {
return new Promise( resolve => {
const socket = tls.connect( {
"host": this.#options.host,
"port": this.#options.port,
"rejectUnauthorized": false,
} );
socket.once( "error", e => resolve );
socket.once( "secureConnect", () => {
socket.off( "error", resolve );
socket.once( "error", e => {} );
resolve( socket );
} );
} );
}
}