vue-simple-range-slider
Version:
Change Your numeric value or numeric range value with dragging handles
1,593 lines (1,397 loc) • 100 kB
JavaScript
"use strict";
const os = require("os");
const path = require("path");
const url = require("url");
const util = require("util");
const fs = require("graceful-fs");
const ipaddr = require("ipaddr.js");
const defaultGateway = require("default-gateway");
const express = require("express");
const { validate } = require("schema-utils");
const schema = require("./options.json");
/** @typedef {import("schema-utils/declarations/validate").Schema} Schema */
/** @typedef {import("webpack").Compiler} Compiler */
/** @typedef {import("webpack").MultiCompiler} MultiCompiler */
/** @typedef {import("webpack").Configuration} WebpackConfiguration */
/** @typedef {import("webpack").StatsOptions} StatsOptions */
/** @typedef {import("webpack").StatsCompilation} StatsCompilation */
/** @typedef {import("webpack").Stats} Stats */
/** @typedef {import("webpack").MultiStats} MultiStats */
/** @typedef {import("os").NetworkInterfaceInfo} NetworkInterfaceInfo */
/** @typedef {import("express").Request} Request */
/** @typedef {import("express").Response} Response */
/** @typedef {import("express").NextFunction} NextFunction */
/** @typedef {import("express").RequestHandler} ExpressRequestHandler */
/** @typedef {import("express").ErrorRequestHandler} ExpressErrorRequestHandler */
/** @typedef {import("chokidar").WatchOptions} WatchOptions */
/** @typedef {import("chokidar").FSWatcher} FSWatcher */
/** @typedef {import("connect-history-api-fallback").Options} ConnectHistoryApiFallbackOptions */
/** @typedef {import("bonjour-service").Bonjour} Bonjour */
/** @typedef {import("bonjour-service").Service} BonjourOptions */
/** @typedef {import("http-proxy-middleware").RequestHandler} RequestHandler */
/** @typedef {import("http-proxy-middleware").Options} HttpProxyMiddlewareOptions */
/** @typedef {import("http-proxy-middleware").Filter} HttpProxyMiddlewareOptionsFilter */
/** @typedef {import("serve-index").Options} ServeIndexOptions */
/** @typedef {import("serve-static").ServeStaticOptions} ServeStaticOptions */
/** @typedef {import("ipaddr.js").IPv4} IPv4 */
/** @typedef {import("ipaddr.js").IPv6} IPv6 */
/** @typedef {import("net").Socket} Socket */
/** @typedef {import("http").IncomingMessage} IncomingMessage */
/** @typedef {import("open").Options} OpenOptions */
/** @typedef {import("https").ServerOptions & { spdy?: { plain?: boolean | undefined, ssl?: boolean | undefined, 'x-forwarded-for'?: string | undefined, protocol?: string | undefined, protocols?: string[] | undefined }}} ServerOptions */
/**
* @template Request, Response
* @typedef {import("webpack-dev-middleware").Options<Request, Response>} DevMiddlewareOptions
*/
/**
* @template Request, Response
* @typedef {import("webpack-dev-middleware").Context<Request, Response>} DevMiddlewareContext
*/
/**
* @typedef {"local-ip" | "local-ipv4" | "local-ipv6" | string} Host
*/
/**
* @typedef {number | string | "auto"} Port
*/
/**
* @typedef {Object} WatchFiles
* @property {string | string[]} paths
* @property {WatchOptions & { aggregateTimeout?: number, ignored?: WatchOptions["ignored"], poll?: number | boolean }} [options]
*/
/**
* @typedef {Object} Static
* @property {string} [directory]
* @property {string | string[]} [publicPath]
* @property {boolean | ServeIndexOptions} [serveIndex]
* @property {ServeStaticOptions} [staticOptions]
* @property {boolean | WatchOptions & { aggregateTimeout?: number, ignored?: WatchOptions["ignored"], poll?: number | boolean }} [watch]
*/
/**
* @typedef {Object} NormalizedStatic
* @property {string} directory
* @property {string[]} publicPath
* @property {false | ServeIndexOptions} serveIndex
* @property {ServeStaticOptions} staticOptions
* @property {false | WatchOptions} watch
*/
/**
* @typedef {Object} ServerConfiguration
* @property {"http" | "https" | "spdy" | string} [type]
* @property {ServerOptions} [options]
*/
/**
* @typedef {Object} WebSocketServerConfiguration
* @property {"sockjs" | "ws" | string | Function} [type]
* @property {Record<string, any>} [options]
*/
/**
* @typedef {(import("ws").WebSocket | import("sockjs").Connection & { send: import("ws").WebSocket["send"], terminate: import("ws").WebSocket["terminate"], ping: import("ws").WebSocket["ping"] }) & { isAlive?: boolean }} ClientConnection
*/
/**
* @typedef {import("ws").WebSocketServer | import("sockjs").Server & { close: import("ws").WebSocketServer["close"] }} WebSocketServer
*/
/**
* @typedef {{ implementation: WebSocketServer, clients: ClientConnection[] }} WebSocketServerImplementation
*/
/**
* @callback ByPass
* @param {Request} req
* @param {Response} res
* @param {ProxyConfigArrayItem} proxyConfig
*/
/**
* @typedef {{ path?: HttpProxyMiddlewareOptionsFilter | undefined, context?: HttpProxyMiddlewareOptionsFilter | undefined } & { bypass?: ByPass } & HttpProxyMiddlewareOptions } ProxyConfigArrayItem
*/
/**
* @typedef {(ProxyConfigArrayItem | ((req?: Request | undefined, res?: Response | undefined, next?: NextFunction | undefined) => ProxyConfigArrayItem))[]} ProxyConfigArray
*/
/**
* @typedef {{ [url: string]: string | ProxyConfigArrayItem }} ProxyConfigMap
*/
/**
* @typedef {Object} OpenApp
* @property {string} [name]
* @property {string[]} [arguments]
*/
/**
* @typedef {Object} Open
* @property {string | string[] | OpenApp} [app]
* @property {string | string[]} [target]
*/
/**
* @typedef {Object} NormalizedOpen
* @property {string} target
* @property {import("open").Options} options
*/
/**
* @typedef {Object} WebSocketURL
* @property {string} [hostname]
* @property {string} [password]
* @property {string} [pathname]
* @property {number | string} [port]
* @property {string} [protocol]
* @property {string} [username]
*/
/**
* @typedef {Object} ClientConfiguration
* @property {"log" | "info" | "warn" | "error" | "none" | "verbose"} [logging]
* @property {boolean | { warnings?: boolean, errors?: boolean }} [overlay]
* @property {boolean} [progress]
* @property {boolean | number} [reconnect]
* @property {"ws" | "sockjs" | string} [webSocketTransport]
* @property {string | WebSocketURL} [webSocketURL]
*/
/**
* @typedef {Array<{ key: string; value: string }> | Record<string, string | string[]>} Headers
*/
/**
* @typedef {{ name?: string, path?: string, middleware: ExpressRequestHandler | ExpressErrorRequestHandler } | ExpressRequestHandler | ExpressErrorRequestHandler} Middleware
*/
/**
* @typedef {Object} Configuration
* @property {boolean | string} [ipc]
* @property {Host} [host]
* @property {Port} [port]
* @property {boolean | "only"} [hot]
* @property {boolean} [liveReload]
* @property {DevMiddlewareOptions<Request, Response>} [devMiddleware]
* @property {boolean} [compress]
* @property {boolean} [magicHtml]
* @property {"auto" | "all" | string | string[]} [allowedHosts]
* @property {boolean | ConnectHistoryApiFallbackOptions} [historyApiFallback]
* @property {boolean} [setupExitSignals]
* @property {boolean | Record<string, never> | BonjourOptions} [bonjour]
* @property {string | string[] | WatchFiles | Array<string | WatchFiles>} [watchFiles]
* @property {boolean | string | Static | Array<string | Static>} [static]
* @property {boolean | ServerOptions} [https]
* @property {boolean} [http2]
* @property {"http" | "https" | "spdy" | string | ServerConfiguration} [server]
* @property {boolean | "sockjs" | "ws" | string | WebSocketServerConfiguration} [webSocketServer]
* @property {ProxyConfigMap | ProxyConfigArrayItem | ProxyConfigArray} [proxy]
* @property {boolean | string | Open | Array<string | Open>} [open]
* @property {boolean} [setupExitSignals]
* @property {boolean | ClientConfiguration} [client]
* @property {Headers | ((req: Request, res: Response, context: DevMiddlewareContext<Request, Response>) => Headers)} [headers]
* @property {(devServer: Server) => void} [onAfterSetupMiddleware]
* @property {(devServer: Server) => void} [onBeforeSetupMiddleware]
* @property {(devServer: Server) => void} [onListening]
* @property {(middlewares: Middleware[], devServer: Server) => Middleware[]} [setupMiddlewares]
*/
if (!process.env.WEBPACK_SERVE) {
// TODO fix me in the next major release
// @ts-ignore
process.env.WEBPACK_SERVE = true;
}
class Server {
/**
* @param {Configuration | Compiler | MultiCompiler} options
* @param {Compiler | MultiCompiler | Configuration} compiler
*/
constructor(options = {}, compiler) {
// TODO: remove this after plugin support is published
if (/** @type {Compiler | MultiCompiler} */ (options).hooks) {
util.deprecate(
() => {},
"Using 'compiler' as the first argument is deprecated. Please use 'options' as the first argument and 'compiler' as the second argument.",
"DEP_WEBPACK_DEV_SERVER_CONSTRUCTOR"
)();
[options = {}, compiler] = [compiler, options];
}
validate(/** @type {Schema} */ (schema), options, {
name: "Dev Server",
baseDataPath: "options",
});
this.compiler = /** @type {Compiler | MultiCompiler} */ (compiler);
/**
* @type {ReturnType<Compiler["getInfrastructureLogger"]>}
* */
this.logger = this.compiler.getInfrastructureLogger("webpack-dev-server");
this.options = /** @type {Configuration} */ (options);
/**
* @type {FSWatcher[]}
*/
this.staticWatchers = [];
/**
* @private
* @type {{ name: string | symbol, listener: (...args: any[]) => void}[] }}
*/
this.listeners = [];
// Keep track of websocket proxies for external websocket upgrade.
/**
* @private
* @type {RequestHandler[]}
*/
this.webSocketProxies = [];
/**
* @type {Socket[]}
*/
this.sockets = [];
/**
* @private
* @type {string | undefined}
*/
// eslint-disable-next-line no-undefined
this.currentHash = undefined;
}
// TODO compatibility with webpack v4, remove it after drop
static get cli() {
return {
get getArguments() {
return () => require("../bin/cli-flags");
},
get processArguments() {
return require("../bin/process-arguments");
},
};
}
static get schema() {
return schema;
}
/**
* @private
* @returns {StatsOptions}
* @constructor
*/
static get DEFAULT_STATS() {
return {
all: false,
hash: true,
warnings: true,
errors: true,
errorDetails: false,
};
}
/**
* @param {string} URL
* @returns {boolean}
*/
static isAbsoluteURL(URL) {
// Don't match Windows paths `c:\`
if (/^[a-zA-Z]:\\/.test(URL)) {
return false;
}
// Scheme: https://tools.ietf.org/html/rfc3986#section-3.1
// Absolute URL: https://tools.ietf.org/html/rfc3986#section-4.3
return /^[a-zA-Z][a-zA-Z\d+\-.]*:/.test(URL);
}
/**
* @param {string} gateway
* @returns {string | undefined}
*/
static findIp(gateway) {
const gatewayIp = ipaddr.parse(gateway);
// Look for the matching interface in all local interfaces.
for (const addresses of Object.values(os.networkInterfaces())) {
for (const { cidr } of /** @type {NetworkInterfaceInfo[]} */ (
addresses
)) {
const net = ipaddr.parseCIDR(/** @type {string} */ (cidr));
if (
net[0] &&
net[0].kind() === gatewayIp.kind() &&
gatewayIp.match(net)
) {
return net[0].toString();
}
}
}
}
/**
* @param {"v4" | "v6"} family
* @returns {Promise<string | undefined>}
*/
static async internalIP(family) {
try {
const { gateway } = await defaultGateway[family]();
return Server.findIp(gateway);
} catch {
// ignore
}
}
/**
* @param {"v4" | "v6"} family
* @returns {string | undefined}
*/
static internalIPSync(family) {
try {
const { gateway } = defaultGateway[family].sync();
return Server.findIp(gateway);
} catch {
// ignore
}
}
/**
* @param {Host} hostname
* @returns {Promise<string>}
*/
static async getHostname(hostname) {
if (hostname === "local-ip") {
return (
(await Server.internalIP("v4")) ||
(await Server.internalIP("v6")) ||
"0.0.0.0"
);
} else if (hostname === "local-ipv4") {
return (await Server.internalIP("v4")) || "0.0.0.0";
} else if (hostname === "local-ipv6") {
return (await Server.internalIP("v6")) || "::";
}
return hostname;
}
/**
* @param {Port} port
* @param {string} host
* @returns {Promise<number | string>}
*/
static async getFreePort(port, host) {
if (typeof port !== "undefined" && port !== null && port !== "auto") {
return port;
}
const pRetry = require("p-retry");
const getPort = require("./getPort");
const basePort =
typeof process.env.WEBPACK_DEV_SERVER_BASE_PORT !== "undefined"
? parseInt(process.env.WEBPACK_DEV_SERVER_BASE_PORT, 10)
: 8080;
// Try to find unused port and listen on it for 3 times,
// if port is not specified in options.
const defaultPortRetry =
typeof process.env.WEBPACK_DEV_SERVER_PORT_RETRY !== "undefined"
? parseInt(process.env.WEBPACK_DEV_SERVER_PORT_RETRY, 10)
: 3;
return pRetry(() => getPort(basePort, host), {
retries: defaultPortRetry,
});
}
/**
* @returns {string}
*/
static findCacheDir() {
const cwd = process.cwd();
/**
* @type {string | undefined}
*/
let dir = cwd;
for (;;) {
try {
if (fs.statSync(path.join(dir, "package.json")).isFile()) break;
// eslint-disable-next-line no-empty
} catch (e) {}
const parent = path.dirname(dir);
if (dir === parent) {
// eslint-disable-next-line no-undefined
dir = undefined;
break;
}
dir = parent;
}
if (!dir) {
return path.resolve(cwd, ".cache/webpack-dev-server");
} else if (process.versions.pnp === "1") {
return path.resolve(dir, ".pnp/.cache/webpack-dev-server");
} else if (process.versions.pnp === "3") {
return path.resolve(dir, ".yarn/.cache/webpack-dev-server");
}
return path.resolve(dir, "node_modules/.cache/webpack-dev-server");
}
/**
* @private
* @param {Compiler} compiler
*/
addAdditionalEntries(compiler) {
/**
* @type {string[]}
*/
const additionalEntries = [];
const isWebTarget = compiler.options.externalsPresets
? compiler.options.externalsPresets.web
: [
"web",
"webworker",
"electron-preload",
"electron-renderer",
"node-webkit",
// eslint-disable-next-line no-undefined
undefined,
null,
].includes(/** @type {string} */ (compiler.options.target));
// TODO maybe empty empty client
if (this.options.client && isWebTarget) {
let webSocketURLStr = "";
if (this.options.webSocketServer) {
const webSocketURL =
/** @type {WebSocketURL} */
(
/** @type {ClientConfiguration} */
(this.options.client).webSocketURL
);
const webSocketServer =
/** @type {{ type: WebSocketServerConfiguration["type"], options: NonNullable<WebSocketServerConfiguration["options"]> }} */
(this.options.webSocketServer);
const searchParams = new URLSearchParams();
/** @type {string} */
let protocol;
// We are proxying dev server and need to specify custom `hostname`
if (typeof webSocketURL.protocol !== "undefined") {
protocol = webSocketURL.protocol;
} else {
protocol =
/** @type {ServerConfiguration} */
(this.options.server).type === "http" ? "ws:" : "wss:";
}
searchParams.set("protocol", protocol);
if (typeof webSocketURL.username !== "undefined") {
searchParams.set("username", webSocketURL.username);
}
if (typeof webSocketURL.password !== "undefined") {
searchParams.set("password", webSocketURL.password);
}
/** @type {string} */
let hostname;
// SockJS is not supported server mode, so `hostname` and `port` can't specified, let's ignore them
// TODO show warning about this
const isSockJSType = webSocketServer.type === "sockjs";
// We are proxying dev server and need to specify custom `hostname`
if (typeof webSocketURL.hostname !== "undefined") {
hostname = webSocketURL.hostname;
}
// Web socket server works on custom `hostname`, only for `ws` because `sock-js` is not support custom `hostname`
else if (
typeof webSocketServer.options.host !== "undefined" &&
!isSockJSType
) {
hostname = webSocketServer.options.host;
}
// The `host` option is specified
else if (typeof this.options.host !== "undefined") {
hostname = this.options.host;
}
// The `port` option is not specified
else {
hostname = "0.0.0.0";
}
searchParams.set("hostname", hostname);
/** @type {number | string} */
let port;
// We are proxying dev server and need to specify custom `port`
if (typeof webSocketURL.port !== "undefined") {
port = webSocketURL.port;
}
// Web socket server works on custom `port`, only for `ws` because `sock-js` is not support custom `port`
else if (
typeof webSocketServer.options.port !== "undefined" &&
!isSockJSType
) {
port = webSocketServer.options.port;
}
// The `port` option is specified
else if (typeof this.options.port === "number") {
port = this.options.port;
}
// The `port` option is specified using `string`
else if (
typeof this.options.port === "string" &&
this.options.port !== "auto"
) {
port = Number(this.options.port);
}
// The `port` option is not specified or set to `auto`
else {
port = "0";
}
searchParams.set("port", String(port));
/** @type {string} */
let pathname = "";
// We are proxying dev server and need to specify custom `pathname`
if (typeof webSocketURL.pathname !== "undefined") {
pathname = webSocketURL.pathname;
}
// Web socket server works on custom `path`
else if (
typeof webSocketServer.options.prefix !== "undefined" ||
typeof webSocketServer.options.path !== "undefined"
) {
pathname =
webSocketServer.options.prefix || webSocketServer.options.path;
}
searchParams.set("pathname", pathname);
const client = /** @type {ClientConfiguration} */ (this.options.client);
if (typeof client.logging !== "undefined") {
searchParams.set("logging", client.logging);
}
if (typeof client.reconnect !== "undefined") {
searchParams.set(
"reconnect",
typeof client.reconnect === "number"
? String(client.reconnect)
: "10"
);
}
webSocketURLStr = searchParams.toString();
}
additionalEntries.push(
`${require.resolve("../client/index.js")}?${webSocketURLStr}`
);
}
if (this.options.hot === "only") {
additionalEntries.push(require.resolve("webpack/hot/only-dev-server"));
} else if (this.options.hot) {
additionalEntries.push(require.resolve("webpack/hot/dev-server"));
}
const webpack = compiler.webpack || require("webpack");
// use a hook to add entries if available
if (typeof webpack.EntryPlugin !== "undefined") {
for (const additionalEntry of additionalEntries) {
new webpack.EntryPlugin(compiler.context, additionalEntry, {
// eslint-disable-next-line no-undefined
name: undefined,
}).apply(compiler);
}
}
// TODO remove after drop webpack v4 support
else {
/**
* prependEntry Method for webpack 4
* @param {any} originalEntry
* @param {any} newAdditionalEntries
* @returns {any}
*/
const prependEntry = (originalEntry, newAdditionalEntries) => {
if (typeof originalEntry === "function") {
return () =>
Promise.resolve(originalEntry()).then((entry) =>
prependEntry(entry, newAdditionalEntries)
);
}
if (
typeof originalEntry === "object" &&
!Array.isArray(originalEntry)
) {
/** @type {Object<string,string>} */
const clone = {};
Object.keys(originalEntry).forEach((key) => {
// entry[key] should be a string here
const entryDescription = originalEntry[key];
clone[key] = prependEntry(entryDescription, newAdditionalEntries);
});
return clone;
}
// in this case, entry is a string or an array.
// make sure that we do not add duplicates.
/** @type {any} */
const entriesClone = additionalEntries.slice(0);
[].concat(originalEntry).forEach((newEntry) => {
if (!entriesClone.includes(newEntry)) {
entriesClone.push(newEntry);
}
});
return entriesClone;
};
compiler.options.entry = prependEntry(
compiler.options.entry || "./src",
additionalEntries
);
compiler.hooks.entryOption.call(
/** @type {string} */ (compiler.options.context),
compiler.options.entry
);
}
}
/**
* @private
* @returns {Compiler["options"]}
*/
getCompilerOptions() {
if (
typeof (/** @type {MultiCompiler} */ (this.compiler).compilers) !==
"undefined"
) {
if (/** @type {MultiCompiler} */ (this.compiler).compilers.length === 1) {
return (
/** @type {MultiCompiler} */
(this.compiler).compilers[0].options
);
}
// Configuration with the `devServer` options
const compilerWithDevServer =
/** @type {MultiCompiler} */
(this.compiler).compilers.find((config) => config.options.devServer);
if (compilerWithDevServer) {
return compilerWithDevServer.options;
}
// Configuration with `web` preset
const compilerWithWebPreset =
/** @type {MultiCompiler} */
(this.compiler).compilers.find(
(config) =>
(config.options.externalsPresets &&
config.options.externalsPresets.web) ||
[
"web",
"webworker",
"electron-preload",
"electron-renderer",
"node-webkit",
// eslint-disable-next-line no-undefined
undefined,
null,
].includes(/** @type {string} */ (config.options.target))
);
if (compilerWithWebPreset) {
return compilerWithWebPreset.options;
}
// Fallback
return /** @type {MultiCompiler} */ (this.compiler).compilers[0].options;
}
return /** @type {Compiler} */ (this.compiler).options;
}
/**
* @private
* @returns {Promise<void>}
*/
async normalizeOptions() {
const { options } = this;
const compilerOptions = this.getCompilerOptions();
// TODO remove `{}` after drop webpack v4 support
const compilerWatchOptions = compilerOptions.watchOptions || {};
/**
* @param {WatchOptions & { aggregateTimeout?: number, ignored?: WatchOptions["ignored"], poll?: number | boolean }} watchOptions
* @returns {WatchOptions}
*/
const getWatchOptions = (watchOptions = {}) => {
const getPolling = () => {
if (typeof watchOptions.usePolling !== "undefined") {
return watchOptions.usePolling;
}
if (typeof watchOptions.poll !== "undefined") {
return Boolean(watchOptions.poll);
}
if (typeof compilerWatchOptions.poll !== "undefined") {
return Boolean(compilerWatchOptions.poll);
}
return false;
};
const getInterval = () => {
if (typeof watchOptions.interval !== "undefined") {
return watchOptions.interval;
}
if (typeof watchOptions.poll === "number") {
return watchOptions.poll;
}
if (typeof compilerWatchOptions.poll === "number") {
return compilerWatchOptions.poll;
}
};
const usePolling = getPolling();
const interval = getInterval();
const { poll, ...rest } = watchOptions;
return {
ignoreInitial: true,
persistent: true,
followSymlinks: false,
atomic: false,
alwaysStat: true,
ignorePermissionErrors: true,
// Respect options from compiler watchOptions
usePolling,
interval,
ignored: watchOptions.ignored,
// TODO: we respect these options for all watch options and allow developers to pass them to chokidar, but chokidar doesn't have these options maybe we need revisit that in future
...rest,
};
};
/**
* @param {string | Static | undefined} [optionsForStatic]
* @returns {NormalizedStatic}
*/
const getStaticItem = (optionsForStatic) => {
const getDefaultStaticOptions = () => {
return {
directory: path.join(process.cwd(), "public"),
staticOptions: {},
publicPath: ["/"],
serveIndex: { icons: true },
watch: getWatchOptions(),
};
};
/** @type {NormalizedStatic} */
let item;
if (typeof optionsForStatic === "undefined") {
item = getDefaultStaticOptions();
} else if (typeof optionsForStatic === "string") {
item = {
...getDefaultStaticOptions(),
directory: optionsForStatic,
};
} else {
const def = getDefaultStaticOptions();
item = {
directory:
typeof optionsForStatic.directory !== "undefined"
? optionsForStatic.directory
: def.directory,
// TODO: do merge in the next major release
staticOptions:
typeof optionsForStatic.staticOptions !== "undefined"
? optionsForStatic.staticOptions
: def.staticOptions,
publicPath:
// eslint-disable-next-line no-nested-ternary
typeof optionsForStatic.publicPath !== "undefined"
? Array.isArray(optionsForStatic.publicPath)
? optionsForStatic.publicPath
: [optionsForStatic.publicPath]
: def.publicPath,
// TODO: do merge in the next major release
serveIndex:
// eslint-disable-next-line no-nested-ternary
typeof optionsForStatic.serveIndex !== "undefined"
? typeof optionsForStatic.serveIndex === "boolean" &&
optionsForStatic.serveIndex
? def.serveIndex
: optionsForStatic.serveIndex
: def.serveIndex,
watch:
// eslint-disable-next-line no-nested-ternary
typeof optionsForStatic.watch !== "undefined"
? // eslint-disable-next-line no-nested-ternary
typeof optionsForStatic.watch === "boolean"
? optionsForStatic.watch
? def.watch
: false
: getWatchOptions(optionsForStatic.watch)
: def.watch,
};
}
if (Server.isAbsoluteURL(item.directory)) {
throw new Error("Using a URL as static.directory is not supported");
}
return item;
};
if (typeof options.allowedHosts === "undefined") {
// AllowedHosts allows some default hosts picked from `options.host` or `webSocketURL.hostname` and `localhost`
options.allowedHosts = "auto";
}
// We store allowedHosts as array when supplied as string
else if (
typeof options.allowedHosts === "string" &&
options.allowedHosts !== "auto" &&
options.allowedHosts !== "all"
) {
options.allowedHosts = [options.allowedHosts];
}
// CLI pass options as array, we should normalize them
else if (
Array.isArray(options.allowedHosts) &&
options.allowedHosts.includes("all")
) {
options.allowedHosts = "all";
}
if (typeof options.bonjour === "undefined") {
options.bonjour = false;
} else if (typeof options.bonjour === "boolean") {
options.bonjour = options.bonjour ? {} : false;
}
if (
typeof options.client === "undefined" ||
(typeof options.client === "object" && options.client !== null)
) {
if (!options.client) {
options.client = {};
}
if (typeof options.client.webSocketURL === "undefined") {
options.client.webSocketURL = {};
} else if (typeof options.client.webSocketURL === "string") {
const parsedURL = new URL(options.client.webSocketURL);
options.client.webSocketURL = {
protocol: parsedURL.protocol,
hostname: parsedURL.hostname,
port: parsedURL.port.length > 0 ? Number(parsedURL.port) : "",
pathname: parsedURL.pathname,
username: parsedURL.username,
password: parsedURL.password,
};
} else if (typeof options.client.webSocketURL.port === "string") {
options.client.webSocketURL.port = Number(
options.client.webSocketURL.port
);
}
// Enable client overlay by default
if (typeof options.client.overlay === "undefined") {
options.client.overlay = true;
} else if (typeof options.client.overlay !== "boolean") {
options.client.overlay = {
errors: true,
warnings: true,
...options.client.overlay,
};
}
if (typeof options.client.reconnect === "undefined") {
options.client.reconnect = 10;
} else if (options.client.reconnect === true) {
options.client.reconnect = Infinity;
} else if (options.client.reconnect === false) {
options.client.reconnect = 0;
}
// Respect infrastructureLogging.level
if (typeof options.client.logging === "undefined") {
options.client.logging = compilerOptions.infrastructureLogging
? compilerOptions.infrastructureLogging.level
: "info";
}
}
if (typeof options.compress === "undefined") {
options.compress = true;
}
if (typeof options.devMiddleware === "undefined") {
options.devMiddleware = {};
}
// No need to normalize `headers`
if (typeof options.historyApiFallback === "undefined") {
options.historyApiFallback = false;
} else if (
typeof options.historyApiFallback === "boolean" &&
options.historyApiFallback
) {
options.historyApiFallback = {};
}
// No need to normalize `host`
options.hot =
typeof options.hot === "boolean" || options.hot === "only"
? options.hot
: true;
const isHTTPs = Boolean(options.https);
const isSPDY = Boolean(options.http2);
if (isHTTPs) {
// TODO: remove in the next major release
util.deprecate(
() => {},
"'https' option is deprecated. Please use the 'server' option.",
"DEP_WEBPACK_DEV_SERVER_HTTPS"
)();
}
if (isSPDY) {
// TODO: remove in the next major release
util.deprecate(
() => {},
"'http2' option is deprecated. Please use the 'server' option.",
"DEP_WEBPACK_DEV_SERVER_HTTP2"
)();
}
options.server = {
type:
// eslint-disable-next-line no-nested-ternary
typeof options.server === "string"
? options.server
: // eslint-disable-next-line no-nested-ternary
typeof (options.server || {}).type === "string"
? /** @type {ServerConfiguration} */ (options.server).type || "http"
: // eslint-disable-next-line no-nested-ternary
isSPDY
? "spdy"
: isHTTPs
? "https"
: "http",
options: {
.../** @type {ServerOptions} */ (options.https),
.../** @type {ServerConfiguration} */ (options.server || {}).options,
},
};
if (
options.server.type === "spdy" &&
typeof (/** @type {ServerOptions} */ (options.server.options).spdy) ===
"undefined"
) {
/** @type {ServerOptions} */
(options.server.options).spdy = {
protocols: ["h2", "http/1.1"],
};
}
if (options.server.type === "https" || options.server.type === "spdy") {
if (
typeof (
/** @type {ServerOptions} */ (options.server.options).requestCert
) === "undefined"
) {
/** @type {ServerOptions} */
(options.server.options).requestCert = false;
}
const httpsProperties =
/** @type {Array<keyof ServerOptions>} */
(["cacert", "ca", "cert", "crl", "key", "pfx"]);
for (const property of httpsProperties) {
if (
typeof (
/** @type {ServerOptions} */ (options.server.options)[property]
) === "undefined"
) {
// eslint-disable-next-line no-continue
continue;
}
// @ts-ignore
if (property === "cacert") {
// TODO remove the `cacert` option in favor `ca` in the next major release
util.deprecate(
() => {},
"The 'cacert' option is deprecated. Please use the 'ca' option.",
"DEP_WEBPACK_DEV_SERVER_CACERT"
)();
}
/** @type {any} */
const value =
/** @type {ServerOptions} */
(options.server.options)[property];
/**
* @param {string | Buffer | undefined} item
* @returns {string | Buffer | undefined}
*/
const readFile = (item) => {
if (
Buffer.isBuffer(item) ||
(typeof item === "object" && item !== null && !Array.isArray(item))
) {
return item;
}
if (item) {
let stats = null;
try {
stats = fs.lstatSync(fs.realpathSync(item)).isFile();
} catch (error) {
// Ignore error
}
// It is file
return stats ? fs.readFileSync(item) : item;
}
};
/** @type {any} */
(options.server.options)[property] = Array.isArray(value)
? value.map((item) => readFile(item))
: readFile(value);
}
let fakeCert;
if (
!(/** @type {ServerOptions} */ (options.server.options).key) ||
/** @type {ServerOptions} */ (!options.server.options).cert
) {
const certificateDir = Server.findCacheDir();
const certificatePath = path.join(certificateDir, "server.pem");
let certificateExists;
try {
const certificate = await fs.promises.stat(certificatePath);
certificateExists = certificate.isFile();
} catch {
certificateExists = false;
}
if (certificateExists) {
const certificateTtl = 1000 * 60 * 60 * 24;
const certificateStat = await fs.promises.stat(certificatePath);
const now = Number(new Date());
// cert is more than 30 days old, kill it with fire
if ((now - Number(certificateStat.ctime)) / certificateTtl > 30) {
const { promisify } = require("util");
const rimraf = require("rimraf");
const del = promisify(rimraf);
this.logger.info(
"SSL certificate is more than 30 days old. Removing..."
);
await del(certificatePath);
certificateExists = false;
}
}
if (!certificateExists) {
this.logger.info("Generating SSL certificate...");
// @ts-ignore
const selfsigned = require("selfsigned");
const attributes = [{ name: "commonName", value: "localhost" }];
const pems = selfsigned.generate(attributes, {
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",
},
],
},
],
});
await fs.promises.mkdir(certificateDir, { recursive: true });
await fs.promises.writeFile(
certificatePath,
pems.private + pems.cert,
{
encoding: "utf8",
}
);
}
fakeCert = await fs.promises.readFile(certificatePath);
this.logger.info(`SSL certificate: ${certificatePath}`);
}
if (
/** @type {ServerOptions & { cacert?: ServerOptions["ca"] }} */ (
options.server.options
).cacert
) {
if (/** @type {ServerOptions} */ (options.server.options).ca) {
this.logger.warn(
"Do not specify 'ca' and 'cacert' options together, the 'ca' option will be used."
);
} else {
/** @type {ServerOptions} */
(options.server.options).ca =
/** @type {ServerOptions & { cacert?: ServerOptions["ca"] }} */
(options.server.options).cacert;
}
delete (
/** @type {ServerOptions & { cacert?: ServerOptions["ca"] }} */ (
options.server.options
).cacert
);
}
/** @type {ServerOptions} */
(options.server.options).key =
/** @type {ServerOptions} */
(options.server.options).key || fakeCert;
/** @type {ServerOptions} */
(options.server.options).cert =
/** @type {ServerOptions} */
(options.server.options).cert || fakeCert;
}
if (typeof options.ipc === "boolean") {
const isWindows = process.platform === "win32";
const pipePrefix = isWindows ? "\\\\.\\pipe\\" : os.tmpdir();
const pipeName = "webpack-dev-server.sock";
options.ipc = path.join(pipePrefix, pipeName);
}
options.liveReload =
typeof options.liveReload !== "undefined" ? options.liveReload : true;
options.magicHtml =
typeof options.magicHtml !== "undefined" ? options.magicHtml : true;
// https://github.com/webpack/webpack-dev-server/issues/1990
const defaultOpenOptions = { wait: false };
/**
* @param {any} target
* @returns {NormalizedOpen[]}
*/
// TODO: remove --open-app in favor of --open-app-name
const getOpenItemsFromObject = ({ target, ...rest }) => {
const normalizedOptions = { ...defaultOpenOptions, ...rest };
if (typeof normalizedOptions.app === "string") {
normalizedOptions.app = {
name: normalizedOptions.app,
};
}
const normalizedTarget = typeof target === "undefined" ? "<url>" : target;
if (Array.isArray(normalizedTarget)) {
return normalizedTarget.map((singleTarget) => {
return { target: singleTarget, options: normalizedOptions };
});
}
return [{ target: normalizedTarget, options: normalizedOptions }];
};
if (typeof options.open === "undefined") {
/** @type {NormalizedOpen[]} */
(options.open) = [];
} else if (typeof options.open === "boolean") {
/** @type {NormalizedOpen[]} */
(options.open) = options.open
? [
{
target: "<url>",
options: /** @type {OpenOptions} */ (defaultOpenOptions),
},
]
: [];
} else if (typeof options.open === "string") {
/** @type {NormalizedOpen[]} */
(options.open) = [{ target: options.open, options: defaultOpenOptions }];
} else if (Array.isArray(options.open)) {
/**
* @type {NormalizedOpen[]}
*/
const result = [];
options.open.forEach((item) => {
if (typeof item === "string") {
result.push({ target: item, options: defaultOpenOptions });
return;
}
result.push(...getOpenItemsFromObject(item));
});
/** @type {NormalizedOpen[]} */
(options.open) = result;
} else {
/** @type {NormalizedOpen[]} */
(options.open) = [...getOpenItemsFromObject(options.open)];
}
if (options.onAfterSetupMiddleware) {
// TODO: remove in the next major release
util.deprecate(
() => {},
"'onAfterSetupMiddleware' option is deprecated. Please use the 'setupMiddlewares' option.",
`DEP_WEBPACK_DEV_SERVER_ON_AFTER_SETUP_MIDDLEWARE`
)();
}
if (options.onBeforeSetupMiddleware) {
// TODO: remove in the next major release
util.deprecate(
() => {},
"'onBeforeSetupMiddleware' option is deprecated. Please use the 'setupMiddlewares' option.",
`DEP_WEBPACK_DEV_SERVER_ON_BEFORE_SETUP_MIDDLEWARE`
)();
}
if (typeof options.port === "string" && options.port !== "auto") {
options.port = Number(options.port);
}
/**
* Assume a proxy configuration specified as:
* proxy: {
* 'context': { options }
* }
* OR
* proxy: {
* 'context': 'target'
* }
*/
if (typeof options.proxy !== "undefined") {
// TODO remove in the next major release, only accept `Array`
if (!Array.isArray(options.proxy)) {
if (
Object.prototype.hasOwnProperty.call(options.proxy, "target") ||
Object.prototype.hasOwnProperty.call(options.proxy, "router")
) {
/** @type {ProxyConfigArray} */
(options.proxy) = [/** @type {ProxyConfigMap} */ (options.proxy)];
} else {
/** @type {ProxyConfigArray} */
(options.proxy) = Object.keys(options.proxy).map(
/**
* @param {string} context
* @returns {HttpProxyMiddlewareOptions}
*/
(context) => {
let proxyOptions;
// For backwards compatibility reasons.
const correctedContext = context
.replace(/^\*$/, "**")
.replace(/\/\*$/, "");
if (
typeof (
/** @type {ProxyConfigMap} */ (options.proxy)[context]
) === "string"
) {
proxyOptions = {
context: correctedContext,
target:
/** @type {ProxyConfigMap} */
(options.proxy)[context],
};
} else {
proxyOptions = {
// @ts-ignore
.../** @type {ProxyConfigMap} */ (options.proxy)[context],
};
proxyOptions.context = correctedContext;
}
return proxyOptions;
}
);
}
}
/** @type {ProxyConfigArray} */
(options.proxy) =
/** @type {ProxyConfigArray} */
(options.proxy).map((item) => {
if (typeof item === "function") {
return item;
}
/**
* @param {"info" | "warn" | "error" | "debug" | "silent" | undefined | "none" | "log" | "verbose"} level
* @returns {"info" | "warn" | "error" | "debug" | "silent" | undefined}
*/
const getLogLevelForProxy = (level) => {
if (level === "none") {
return "silent";
}
if (level === "log") {
return "info";
}
if (level === "verbose") {
return "debug";
}
return level;
};
if (typeof item.logLevel === "undefined") {
item.logLevel = getLogLevelForProxy(
compilerOptions.infrastructureLogging
? compilerOptions.infrastructureLogging.level
: "info"
);
}
if (typeof item.logProvider === "undefined") {
item.logProvider = () => this.logger;
}
return item;
});
}
if (typeof options.setupExitSignals === "undefined") {
options.setupExitSignals = true;
}
if (typeof options.static === "undefined") {
options.static = [getStaticItem()];
} else if (typeof options.static === "boolean") {
options.static = options.static ? [getStaticItem()] : false;
} else if (typeof options.static === "string") {
options.static = [getStaticItem(options.static)];
} else if (Array.isArray(options.static)) {
options.static = options.static.map((item) => getStaticItem(item));
} else {
options.static = [getStaticItem(options.static)];
}
if (typeof options.watchFiles === "string") {
options.watchFiles = [
{ paths: options.watchFiles, options: getWatchOptions() },
];
} else if (
typeof options.watchFiles === "object" &&
options.watchFiles !== null &&
!Array.isArray(options.watchFiles)
) {
options.watchFiles = [
{
paths: options.watchFiles.paths,
options: getWatchOptions(options.watchFiles.options || {}),
},
];
} else if (Array.isArray(options.watchFiles)) {
options.watchFiles = options.watchFiles.map((item) => {
if (typeof item === "string") {
return { paths: item, options: getWatchOptions() };
}
return {
paths: item.paths,
options: getWatchOptions(item.options || {}),
};
});
} else {
options.watchFiles = [];
}
const defaultWebSocketServerType = "ws";
const defaultWebSocketServerOptions = { path: "/ws" };
if (typeof options.webSocketServer === "undefined") {
options.webSocketServer = {
type: defaultWebSocketServerType,
options: defaultWebSocketServerOptions,
};
} else if (
typeof options.webSocketServer === "boolean" &&
!options.webSocketServer
) {
options.webSocketServer = false;
} else if (
typeof options.webSocketServer === "string" ||
typeof options.webSocketServer === "function"
) {
options.webSocketServer = {
type: options.webSocketServer,
options: defaultWebSocketServerOptions,
};
} else {
options.webSocketServer = {
type:
/** @type {WebSocketServerConfiguration} */
(options.webSocketServer).type || defaultWebSocketServerType,
options: {
...defaultWebSocketServerOptions,
.../** @type {WebSocketServerConfiguration} */
(options.webSocketServer).options,
},
};
const webSocketServer =
/** @type {{ type: WebSocketServerConfiguration["type"], options: NonNullable<WebSocketServerConfiguration["options"]> }} */
(options.webSocketServer);
if (typeof webSocketServer.options.port === "string") {
webSocketServer.options.port = Number(webSocketServer.options.port);
}
}
}
/**
* @private
* @returns {string}
*/
getClientTransport() {
let clientImplementation;
let clientImplementationFound = true;
const isKnownWebSocketServerImplementation =
this.options.webSocketServer &&
typeof (
/** @type {WebSocketServerConfiguration} */
(this.options.webSocketServer).type
) === "string" &&
// @ts-ignore
(this.options.webSocketServer.type === "ws" ||
/** @type {WebSocketServerConfiguration} */
(this.options.webSocketServer).type === "sockjs");
let clientTransport;
if (this.options.client) {
if (
typeof (
/** @type {ClientConfiguration} */
(this.options.client).webSocketTransport
) !== "undefined"
) {
clientTransport =
/** @type {ClientConfiguration} */
(this.options.client).webSocketTransport;
} else if (isKnownWebSocketServerImplementation) {
clientTransport =
/** @type {WebSocketServerConfiguration} */
(this.options.webSocketSe