From 8f02c3f3d6131fd37f58ef4d5cbe15578c94a6fd Mon Sep 17 00:00:00 2001 From: Alexander Akait <4567934+alexander-akait@users.noreply.github.com> Date: Tue, 21 Dec 2021 19:19:17 +0300 Subject: [PATCH] feat: added types --- .github/workflows/nodejs.yml | 4 + bin/process-arguments.js | 94 +- bin/webpack-dev-server.js | 3 + client-src/clients/SockJSClient.js | 22 +- client-src/clients/WebSocketClient.js | 12 + client-src/index.js | 16 + client-src/utils/log.js | 5 + client-src/utils/sendMessage.js | 4 + client-src/webpack.config.js | 2 + lib/Server.js | 2090 ++++++++++----- lib/servers/BaseServer.js | 8 + lib/servers/SockJSServer.js | 48 +- lib/servers/WebsocketServer.js | 98 +- package-lock.json | 249 +- package.json | 20 +- scripts/extend-webpack-types.js | 26 + test/e2e/api.test.js | 4 +- tsconfig.json | 14 +- types/bin/cli-flags.d.ts | 934 +++++++ types/bin/process-arguments.d.ts | 50 + types/bin/webpack-dev-server.d.ts | 27 + types/lib/Server.d.ts | 3385 ++++++++++++++++++++++++ types/lib/servers/BaseServer.d.ts | 15 + types/lib/servers/SockJSServer.d.ts | 12 + types/lib/servers/WebsocketServer.d.ts | 13 + 25 files changed, 6500 insertions(+), 655 deletions(-) create mode 100644 scripts/extend-webpack-types.js create mode 100644 types/bin/cli-flags.d.ts create mode 100644 types/bin/process-arguments.d.ts create mode 100644 types/bin/webpack-dev-server.d.ts create mode 100644 types/lib/Server.d.ts create mode 100644 types/lib/servers/BaseServer.d.ts create mode 100644 types/lib/servers/SockJSServer.d.ts create mode 100644 types/lib/servers/WebsocketServer.d.ts diff --git a/.github/workflows/nodejs.yml b/.github/workflows/nodejs.yml index b689c36dc0..6493481abd 100644 --- a/.github/workflows/nodejs.yml +++ b/.github/workflows/nodejs.yml @@ -83,6 +83,10 @@ jobs: if: matrix.os == 'windows-latest' run: npm i -g npm + - name: Use latest NPM on windows + if: matrix.webpack-version == 4 + run: sed -i'.original' 's/"build:types"/"_unused"/g' package.json + - name: Install dependencies run: npm ci diff --git a/bin/process-arguments.js b/bin/process-arguments.js index b961d956e0..93fabf6529 100644 --- a/bin/process-arguments.js +++ b/bin/process-arguments.js @@ -5,8 +5,50 @@ const path = require("path"); // Based on https://github.com/webpack/webpack/blob/master/lib/cli.js // Please do not modify it +/** @typedef {"unknown-argument" | "unexpected-non-array-in-path" | "unexpected-non-object-in-path" | "multiple-values-unexpected" | "invalid-value"} ProblemType */ + +/** + * @typedef {Object} Problem + * @property {ProblemType} type + * @property {string} path + * @property {string} argument + * @property {any=} value + * @property {number=} index + * @property {string=} expected + */ + +/** + * @typedef {Object} LocalProblem + * @property {ProblemType} type + * @property {string} path + * @property {string=} expected + */ + +/** + * @typedef {Object} ArgumentConfig + * @property {string} description + * @property {string} path + * @property {boolean} multiple + * @property {"enum"|"string"|"path"|"number"|"boolean"|"RegExp"|"reset"} type + * @property {any[]=} values + */ + +/** + * @typedef {Object} Argument + * @property {string} description + * @property {"string"|"number"|"boolean"} simpleType + * @property {boolean} multiple + * @property {ArgumentConfig[]} configs + */ + const cliAddedItems = new WeakMap(); +/** + * @param {any} config configuration + * @param {string} schemaPath path in the config + * @param {number | undefined} index index of value when multiple values are provided, otherwise undefined + * @returns {{ problem?: LocalProblem, object?: any, property?: string | number, value?: any }} problem or object with property and value + */ const getObjectAndProperty = (config, schemaPath, index = 0) => { if (!schemaPath) { return { value: config }; @@ -81,10 +123,10 @@ const getObjectAndProperty = (config, schemaPath, index = 0) => { i++; } - const value = current[property]; + const value = current[/** @type {string} */ (property)]; - if (property.endsWith("[]")) { - const name = property.slice(0, -2); + if (/** @type {string} */ (property).endsWith("[]")) { + const name = /** @type {string} */ (property).slice(0, -2); // eslint-disable-next-line no-shadow const value = current[name]; @@ -140,6 +182,11 @@ const getObjectAndProperty = (config, schemaPath, index = 0) => { return { object: current, property, value }; }; +/** + * @param {ArgumentConfig} argConfig processing instructions + * @param {any} value the value + * @returns {any | undefined} parsed value + */ const parseValueForArgumentConfig = (argConfig, value) => { // eslint-disable-next-line default-case switch (argConfig.type) { @@ -194,11 +241,11 @@ const parseValueForArgumentConfig = (argConfig, value) => { break; case "enum": - if (argConfig.values.includes(value)) { + if (/** @type {any[]} */ (argConfig.values).includes(value)) { return value; } - for (const item of argConfig.values) { + for (const item of /** @type {any[]} */ (argConfig.values)) { if (`${item}` === value) return item; } @@ -212,6 +259,10 @@ const parseValueForArgumentConfig = (argConfig, value) => { } }; +/** + * @param {ArgumentConfig} argConfig processing instructions + * @returns {string | undefined} expected message + */ const getExpectedValue = (argConfig) => { switch (argConfig.type) { default: @@ -221,12 +272,21 @@ const getExpectedValue = (argConfig) => { case "RegExp": return "regular expression (example: /ab?c*/)"; case "enum": - return argConfig.values.map((v) => `${v}`).join(" | "); + return /** @type {any[]} */ (argConfig.values) + .map((v) => `${v}`) + .join(" | "); case "reset": return "true (will reset the previous value to an empty array)"; } }; +/** + * @param {any} config configuration + * @param {string} schemaPath path in the config + * @param {any} value parsed value + * @param {number | undefined} index index of value when multiple values are provided, otherwise undefined + * @returns {LocalProblem | null} problem or null for success + */ const setValue = (config, schemaPath, value, index) => { const { problem, object, property } = getObjectAndProperty( config, @@ -238,11 +298,18 @@ const setValue = (config, schemaPath, value, index) => { return problem; } - object[property] = value; + object[/** @type {string} */ (property)] = value; return null; }; +/** + * @param {ArgumentConfig} argConfig processing instructions + * @param {any} config configuration + * @param {any} value the value + * @param {number | undefined} index the index if multiple values provided + * @returns {LocalProblem | null} a problem if any + */ const processArgumentConfig = (argConfig, config, value, index) => { // eslint-disable-next-line no-undefined if (index !== undefined && !argConfig.multiple) { @@ -272,7 +339,16 @@ const processArgumentConfig = (argConfig, config, value, index) => { return null; }; +/** + * @param {Record} args object of arguments + * @param {any} config configuration + * @param {Record} values object with values + * @returns {Problem[] | null} problems or null for success + */ const processArguments = (args, config, values) => { + /** + * @type {Problem[]} + */ const problems = []; for (const key of Object.keys(values)) { @@ -289,6 +365,10 @@ const processArguments = (args, config, values) => { continue; } + /** + * @param {any} value + * @param {number | undefined} i + */ const processValue = (value, i) => { const currentProblems = []; diff --git a/bin/webpack-dev-server.js b/bin/webpack-dev-server.js index c499ea1c49..d5262fffa7 100755 --- a/bin/webpack-dev-server.js +++ b/bin/webpack-dev-server.js @@ -108,6 +108,9 @@ if (!cli.installed) { console.error(notify); + /** + * @type {string} + */ let packageManager; if (fs.existsSync(path.resolve(process.cwd(), "yarn.lock"))) { diff --git a/client-src/clients/SockJSClient.js b/client-src/clients/SockJSClient.js index 02a96409b2..0f90bb6da1 100644 --- a/client-src/clients/SockJSClient.js +++ b/client-src/clients/SockJSClient.js @@ -2,25 +2,41 @@ import SockJS from "../modules/sockjs-client/index.js"; import { log } from "../utils/log.js"; export default class SockJSClient { + /** + * @param {string} url + */ constructor(url) { // SockJS requires `http` and `https` protocols this.sock = new SockJS( url.replace(/^ws:/i, "http:").replace(/^wss:/i, "https:") ); - this.sock.onerror = (error) => { - log.error(error); - }; + this.sock.onerror = + /** + * @param {Error} error + */ + (error) => { + log.error(error); + }; } + /** + * @param {(...args: any[]) => void} f + */ onOpen(f) { this.sock.onopen = f; } + /** + * @param {(...args: any[]) => void} f + */ onClose(f) { this.sock.onclose = f; } // call f with the message string as the first argument + /** + * @param {(...args: any[]) => void} f + */ onMessage(f) { this.sock.onmessage = (e) => { f(e.data); diff --git a/client-src/clients/WebSocketClient.js b/client-src/clients/WebSocketClient.js index 269a98f6f1..cdbd94f360 100644 --- a/client-src/clients/WebSocketClient.js +++ b/client-src/clients/WebSocketClient.js @@ -1,6 +1,9 @@ import { log } from "../utils/log.js"; export default class WebSocketClient { + /** + * @param {string} url + */ constructor(url) { this.client = new WebSocket(url); this.client.onerror = (error) => { @@ -8,15 +11,24 @@ export default class WebSocketClient { }; } + /** + * @param {(...args: any[]) => void} f + */ onOpen(f) { this.client.onopen = f; } + /** + * @param {(...args: any[]) => void} f + */ onClose(f) { this.client.onclose = f; } // call f with the message string as the first argument + /** + * @param {(...args: any[]) => void} f + */ onMessage(f) { this.client.onmessage = (e) => { f(e.data); diff --git a/client-src/index.js b/client-src/index.js index a2386a7e0e..d8f832c987 100644 --- a/client-src/index.js +++ b/client-src/index.js @@ -45,6 +45,9 @@ if (typeof parsedResourceQuery.reconnect !== "undefined") { options.reconnect = Number(parsedResourceQuery.reconnect); } +/** + * @param {string} level + */ function setAllLogLevel(level) { // This is needed because the HMR logger operate separately from dev server logger webpackHotLog.setLogLevel( @@ -90,6 +93,9 @@ const onSocketMessage = { sendMessage("Invalid"); }, + /** + * @param {string} hash + */ hash(hash) { status.previousHash = status.currentHash; status.currentHash = hash; @@ -160,6 +166,10 @@ const onSocketMessage = { self.location.reload(); }, + /** + * @param {Error[]} warnings + * @param {any} params + */ warnings(warnings, params) { log.warn("Warnings while compiling."); @@ -190,6 +200,9 @@ const onSocketMessage = { reloadApp(options, status); }, + /** + * @param {Error[]} errors + */ errors(errors) { log.error("Errors while compiling. Reload prevented."); @@ -214,6 +227,9 @@ const onSocketMessage = { show("error", errors); } }, + /** + * @param {Error} error + */ error(error) { log.error(error); }, diff --git a/client-src/utils/log.js b/client-src/utils/log.js index 6bf930f1e0..aecf104a17 100644 --- a/client-src/utils/log.js +++ b/client-src/utils/log.js @@ -5,6 +5,11 @@ const name = "webpack-dev-server"; // to be set by the CLI or API const defaultLevel = "info"; +// options new options, merge with old options +/** + * @param {false | true | "none" | "error" | "warn" | "info" | "log" | "verbose"} level + * @returns {void} + */ function setLogLevel(level) { logger.configureDefaultLogger({ level }); } diff --git a/client-src/utils/sendMessage.js b/client-src/utils/sendMessage.js index 48378d167a..1fb2d42298 100644 --- a/client-src/utils/sendMessage.js +++ b/client-src/utils/sendMessage.js @@ -1,6 +1,10 @@ /* global __resourceQuery WorkerGlobalScope */ // Send messages to the outside, so plugins can consume it. +/** + * @param {string} type + * @param {any} data + */ function sendMsg(type, data) { if ( typeof self !== "undefined" && diff --git a/client-src/webpack.config.js b/client-src/webpack.config.js index ad6dfc44af..d04ad449f3 100644 --- a/client-src/webpack.config.js +++ b/client-src/webpack.config.js @@ -4,6 +4,7 @@ const path = require("path"); const webpack = require("webpack"); const { merge } = require("webpack-merge"); +// @ts-ignore const library = webpack.webpack ? { library: { @@ -27,6 +28,7 @@ const baseForModules = { optimization: { minimize: false, }, + // @ts-ignore target: webpack.webpack ? ["web", "es5"] : "web", module: { rules: [ diff --git a/lib/Server.js b/lib/Server.js index 4a1ce11a6a..2a2f6b1baf 100644 --- a/lib/Server.js +++ b/lib/Server.js @@ -11,14 +11,214 @@ 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").Bonjour} Bonjour */ +/** @typedef {import("bonjour").BonjourOptions} 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} DevMiddlewareOptions + */ + +/** + * @template Request, Response + * @typedef {import("webpack-dev-middleware").Context} 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?: string | RegExp | string[], 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?: string | RegExp | string[], 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} [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 + */ + +/** + * @typedef {{ [url: string]: string | HttpProxyMiddlewareOptions }} ProxyConfigMap + */ + +/** + * @typedef {HttpProxyMiddlewareOptions[]} ProxyArray + */ + +/** + * @callback ByPass + * @param {Request} req + * @param {Response} res + * @param {ProxyConfigArray} proxyConfig + */ + +/** + * @typedef {{ path?: string | string[] | undefined, context?: string | string[] | HttpProxyMiddlewareOptionsFilter | undefined } & HttpProxyMiddlewareOptions & ByPass} ProxyConfigArray + */ + +/** + * @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} 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} [devMiddleware] + * @property {boolean} [compress] + * @property {boolean} [magicHtml] + * @property {"auto" | "all" | string | string[]} [allowedHosts] + * @property {boolean | ConnectHistoryApiFallbackOptions} [historyApiFallback] + * @property {boolean} [setupExitSignals] + * @property {boolean | BonjourOptions} [bonjour] + * @property {string | string[] | WatchFiles | Array} [watchFiles] + * @property {boolean | string | Static | Array} [static] + * @property {boolean | ServerOptions} [https] + * @property {boolean} [http2] + * @property {"http" | "https" | "spdy" | string | ServerConfiguration} [server] + * @property {boolean | "sockjs" | "ws" | string | WebSocketServerConfiguration} [webSocketServer] + * @property {ProxyConfigMap | ProxyConfigArray | ProxyArray} [proxy] + * @property {boolean | string | Open | Array} [open] + * @property {boolean} [setupExitSignals] + * @property {boolean | ClientConfiguration} [client] + * @property {Headers | ((req: Request, res: Response, context: DevMiddlewareContext) => 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 (options.hooks) { + 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.", @@ -28,18 +228,65 @@ class Server { [options = {}, compiler] = [compiler, options]; } - validate(schema, options, "webpack Dev Server"); + validate(/** @type {Schema} */ (schema), options, { + name: "Dev Server", + baseDataPath: "options", + }); - this.options = options; + this.compiler = /** @type {Compiler | MultiCompiler} */ (compiler); + /** + * @type {ReturnType} + * */ + 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 = []; - this.compiler = compiler; - this.currentHash = null; + /** + * @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, @@ -50,6 +297,10 @@ class Server { }; } + /** + * @param {string} URL + * @returns {boolean} + */ static isAbsoluteURL(URL) { // Don't match Windows paths `c:\` if (/^[a-zA-Z]:\\/.test(URL)) { @@ -61,13 +312,19 @@ class Server { 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 addresses) { - const net = ipaddr.parseCIDR(cidr); + for (const { cidr } of /** @type {NetworkInterfaceInfo[]} */ ( + addresses + )) { + const net = ipaddr.parseCIDR(/** @type {string} */ (cidr)); if ( net[0] && @@ -80,6 +337,10 @@ class Server { } } + /** + * @param {"v4" | "v6"} family + * @returns {Promise} + */ static async internalIP(family) { try { const { gateway } = await defaultGateway[family](); @@ -89,6 +350,10 @@ class Server { } } + /** + * @param {"v4" | "v6"} family + * @returns {string | undefined} + */ static internalIPSync(family) { try { const { gateway } = defaultGateway[family].sync(); @@ -98,6 +363,10 @@ class Server { } } + /** + * @param {Host} hostname + * @returns {Promise} + */ static async getHostname(hostname) { if (hostname === "local-ip") { return ( @@ -114,6 +383,10 @@ class Server { return hostname; } + /** + * @param {Port} port + * @returns {Promise} + */ static async getFreePort(port) { if (typeof port !== "undefined" && port !== null && port !== "auto") { return port; @@ -122,21 +395,32 @@ class Server { const pRetry = require("p-retry"); const portfinder = require("portfinder"); - portfinder.basePort = process.env.WEBPACK_DEV_SERVER_BASE_PORT || 8080; + portfinder.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 = - parseInt(process.env.WEBPACK_DEV_SERVER_PORT_RETRY, 10) || 3; + typeof process.env.WEBPACK_DEV_SERVER_PORT_RETRY !== "undefined" + ? parseInt(process.env.WEBPACK_DEV_SERVER_PORT_RETRY, 10) + : 3; return pRetry(() => portfinder.getPortPromise(), { retries: defaultPortRetry, }); } + /** + * @returns {string} + */ static findCacheDir() { const cwd = process.cwd(); + /** + * @type {string | undefined} + */ let dir = cwd; for (;;) { @@ -167,7 +451,14 @@ class 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 @@ -181,38 +472,44 @@ class Server { // eslint-disable-next-line no-undefined undefined, null, - ].includes(compiler.options.target); + ].includes(/** @type {string} */ (compiler.options.target)); // TODO maybe empty empty client if (this.options.client && isWebTarget) { - let webSocketURL = ""; + let webSocketURLStr = ""; + if (this.options.webSocketServer) { + const webSocketURL = + /** @type {WebSocketURL} */ + ( + /** @type {ClientConfiguration} */ + (this.options.client).webSocketURL + ); + const webSocketServer = + /** @type {{ type: WebSocketServerConfiguration["type"], options: NonNullable }} */ + (this.options.webSocketServer); const searchParams = new URLSearchParams(); - /** @type {"ws:" | "wss:" | "http:" | "https:" | "auto:"} */ + /** @type {string} */ let protocol; // We are proxying dev server and need to specify custom `hostname` - if (typeof this.options.client.webSocketURL.protocol !== "undefined") { - protocol = this.options.client.webSocketURL.protocol; + if (typeof webSocketURL.protocol !== "undefined") { + protocol = webSocketURL.protocol; } else { - protocol = this.options.server.type === "http" ? "ws:" : "wss:"; + protocol = + /** @type {ServerConfiguration} */ + (this.options.server).type === "http" ? "ws:" : "wss:"; } searchParams.set("protocol", protocol); - if (typeof this.options.client.webSocketURL.username !== "undefined") { - searchParams.set( - "username", - this.options.client.webSocketURL.username - ); + if (typeof webSocketURL.username !== "undefined") { + searchParams.set("username", webSocketURL.username); } - if (typeof this.options.client.webSocketURL.password !== "undefined") { - searchParams.set( - "password", - this.options.client.webSocketURL.password - ); + if (typeof webSocketURL.password !== "undefined") { + searchParams.set("password", webSocketURL.password); } /** @type {string} */ @@ -220,18 +517,18 @@ class Server { // SockJS is not supported server mode, so `hostname` and `port` can't specified, let's ignore them // TODO show warning about this - const isSockJSType = this.options.webSocketServer.type === "sockjs"; + const isSockJSType = webSocketServer.type === "sockjs"; // We are proxying dev server and need to specify custom `hostname` - if (typeof this.options.client.webSocketURL.hostname !== "undefined") { - hostname = this.options.client.webSocketURL.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 this.options.webSocketServer.options.host !== "undefined" && + typeof webSocketServer.options.host !== "undefined" && !isSockJSType ) { - hostname = this.options.webSocketServer.options.host; + hostname = webSocketServer.options.host; } // The `host` option is specified else if (typeof this.options.host !== "undefined") { @@ -248,15 +545,15 @@ class Server { let port; // We are proxying dev server and need to specify custom `port` - if (typeof this.options.client.webSocketURL.port !== "undefined") { - port = this.options.client.webSocketURL.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 this.options.webSocketServer.options.port !== "undefined" && + typeof webSocketServer.options.port !== "undefined" && !isSockJSType ) { - port = this.options.webSocketServer.options.port; + port = webSocketServer.options.port; } // The `port` option is specified else if (typeof this.options.port === "number") { @@ -280,47 +577,47 @@ class Server { let pathname = ""; // We are proxying dev server and need to specify custom `pathname` - if (typeof this.options.client.webSocketURL.pathname !== "undefined") { - pathname = this.options.client.webSocketURL.pathname; + if (typeof webSocketURL.pathname !== "undefined") { + pathname = webSocketURL.pathname; } // Web socket server works on custom `path` else if ( - typeof this.options.webSocketServer.options.prefix !== "undefined" || - typeof this.options.webSocketServer.options.path !== "undefined" + typeof webSocketServer.options.prefix !== "undefined" || + typeof webSocketServer.options.path !== "undefined" ) { pathname = - this.options.webSocketServer.options.prefix || - this.options.webSocketServer.options.path; + webSocketServer.options.prefix || webSocketServer.options.path; } searchParams.set("pathname", pathname); - if (typeof this.options.client.logging !== "undefined") { - searchParams.set("logging", this.options.client.logging); + const client = /** @type {ClientConfiguration} */ (this.options.client); + + if (typeof client.logging !== "undefined") { + searchParams.set("logging", client.logging); } - if (typeof this.options.client.reconnect !== "undefined") { - searchParams.set("reconnect", this.options.client.reconnect); + if (typeof client.reconnect !== "undefined") { + searchParams.set( + "reconnect", + typeof client.reconnect === "number" + ? String(client.reconnect) + : "10" + ); } - webSocketURL = searchParams.toString(); + webSocketURLStr = searchParams.toString(); } additionalEntries.push( - `${require.resolve("../client/index.js")}?${webSocketURL}` + `${require.resolve("../client/index.js")}?${webSocketURLStr}` ); } - if (this.options.hot) { - let hotEntry; - - if (this.options.hot === "only") { - hotEntry = require.resolve("webpack/hot/only-dev-server"); - } else if (this.options.hot) { - hotEntry = require.resolve("webpack/hot/dev-server"); - } - - additionalEntries.push(hotEntry); + 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"); @@ -338,9 +635,9 @@ class Server { else { /** * prependEntry Method for webpack 4 - * @param {Entry} originalEntry - * @param {Entry} newAdditionalEntries - * @returns {Entry} + * @param {any} originalEntry + * @param {any} newAdditionalEntries + * @returns {any} */ const prependEntry = (originalEntry, newAdditionalEntries) => { if (typeof originalEntry === "function") { @@ -369,7 +666,7 @@ class Server { // in this case, entry is a string or an array. // make sure that we do not add duplicates. - /** @type {Entry} */ + /** @type {any} */ const entriesClone = additionalEntries.slice(0); [].concat(originalEntry).forEach((newEntry) => { @@ -386,65 +683,80 @@ class Server { additionalEntries ); compiler.hooks.entryOption.call( - compiler.options.context, + /** @type {string} */ (compiler.options.context), compiler.options.entry ); } } + /** + * @private + * @returns {Compiler["options"]} + */ getCompilerOptions() { - if (typeof this.compiler.compilers !== "undefined") { - if (this.compiler.compilers.length === 1) { - return this.compiler.compilers[0].options; + 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 = this.compiler.compilers.find( - (config) => config.options.devServer - ); + const compilerWithDevServer = + /** @type {MultiCompiler} */ + (this.compiler).compilers.find((config) => config.options.devServer); if (compilerWithDevServer) { return compilerWithDevServer.options; } // Configuration with `web` preset - const compilerWithWebPreset = 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(config.options.target) - ); + 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 this.compiler.compilers[0].options; + return /** @type {MultiCompiler} */ (this.compiler).compilers[0].options; } - return this.compiler.options; + return /** @type {Compiler} */ (this.compiler).options; } + /** + * @private + * @returns {Promise} + */ async normalizeOptions() { const { options } = this; - - if (!this.logger) { - this.logger = this.compiler.getInfrastructureLogger("webpack-dev-server"); - } - const compilerOptions = this.getCompilerOptions(); // TODO remove `{}` after drop webpack v4 support const compilerWatchOptions = compilerOptions.watchOptions || {}; + /** + * @param {WatchOptions & WebpackConfiguration["watchOptions"]} watchOptions + * @returns {WatchOptions} + */ const getWatchOptions = (watchOptions = {}) => { const getPolling = () => { if (typeof watchOptions.usePolling !== "undefined") { @@ -494,6 +806,10 @@ class Server { ...rest, }; }; + /** + * @param {string | Static | undefined} [optionsForStatic] + * @returns {NormalizedStatic} + */ const getStaticItem = (optionsForStatic) => { const getDefaultStaticOptions = () => { return { @@ -505,6 +821,7 @@ class Server { }; }; + /** @type {NormalizedStatic} */ let item; if (typeof optionsForStatic === "undefined") { @@ -528,8 +845,11 @@ class Server { ? optionsForStatic.staticOptions : def.staticOptions, publicPath: + // eslint-disable-next-line no-nested-ternary typeof optionsForStatic.publicPath !== "undefined" - ? optionsForStatic.publicPath + ? Array.isArray(optionsForStatic.publicPath) + ? optionsForStatic.publicPath + : [optionsForStatic.publicPath] : def.publicPath, // TODO: do merge in the next major release serveIndex: @@ -557,11 +877,6 @@ class Server { throw new Error("Using a URL as static.directory is not supported"); } - // ensure that publicPath is an array - if (typeof item.publicPath === "string") { - item.publicPath = [item.publicPath]; - } - return item; }; @@ -699,7 +1014,7 @@ class Server { ? options.server : // eslint-disable-next-line no-nested-ternary typeof (options.server || {}).type === "string" - ? options.server.type + ? /** @type {ServerConfiguration} */ (options.server).type || "http" : // eslint-disable-next-line no-nested-ternary isSPDY ? "spdy" @@ -707,31 +1022,47 @@ class Server { ? "https" : "http", options: { - ...options.https, - ...(options.server || {}).options, + .../** @type {ServerOptions} */ (options.https), + .../** @type {ServerConfiguration} */ (options.server || {}).options, }, }; if ( options.server.type === "spdy" && - typeof options.server.options.spdy === "undefined" + typeof (/** @type {ServerOptions} */ (options.server.options).spdy) === + "undefined" ) { - options.server.options.spdy = { + /** @type {ServerOptions} */ + (options.server.options).spdy = { protocols: ["h2", "http/1.1"], }; } if (options.server.type === "https" || options.server.type === "spdy") { - if (typeof options.server.options.requestCert === "undefined") { - options.server.options.requestCert = false; + if ( + typeof ( + /** @type {ServerOptions} */ (options.server.options).requestCert + ) === "undefined" + ) { + /** @type {ServerOptions} */ + (options.server.options).requestCert = false; } - for (const property of ["cacert", "ca", "cert", "crl", "key", "pfx"]) { - if (typeof options.server.options[property] === "undefined") { + const httpsProperties = + /** @type {Array} */ + (["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( @@ -741,7 +1072,14 @@ class Server { )(); } - const value = options.server.options[property]; + /** @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) || @@ -764,14 +1102,18 @@ class Server { } }; - options.server.options[property] = Array.isArray(value) + /** @type {any} */ + (options.server.options)[property] = Array.isArray(value) ? value.map((item) => readFile(item)) : readFile(value); } let fakeCert; - if (!options.server.options.key || !options.server.options.cert) { + 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; @@ -786,11 +1128,10 @@ class Server { if (certificateExists) { const certificateTtl = 1000 * 60 * 60 * 24; const certificateStat = await fs.promises.stat(certificatePath); - - const now = new Date(); + const now = Number(new Date()); // cert is more than 30 days old, kill it with fire - if ((now - certificateStat.ctime) / certificateTtl > 30) { + if ((now - Number(certificateStat.ctime)) / certificateTtl > 30) { const del = require("del"); this.logger.info( @@ -806,6 +1147,7 @@ class Server { 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, { @@ -886,20 +1228,37 @@ class Server { this.logger.info(`SSL certificate: ${certificatePath}`); } - if (options.server.options.cacert) { - if (options.server.options.ca) { + 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 { - options.server.options.ca = options.server.options.cacert; + /** @type {ServerOptions} */ + (options.server.options).ca = + /** @type {ServerOptions & { cacert?: ServerOptions["ca"] }} */ + (options.server.options).cacert; } - delete options.server.options.cacert; + delete ( + /** @type {ServerOptions & { cacert?: ServerOptions["ca"] }} */ ( + options.server.options + ).cacert + ); } - options.server.options.key = options.server.options.key || fakeCert; - options.server.options.cert = options.server.options.cert || fakeCert; + /** @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") { @@ -918,6 +1277,10 @@ class Server { // 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 }; @@ -940,14 +1303,25 @@ class Server { }; if (typeof options.open === "undefined") { - options.open = []; + /** @type {NormalizedOpen[]} */ + (options.open) = []; } else if (typeof options.open === "boolean") { - options.open = options.open - ? [{ target: "", options: defaultOpenOptions }] + /** @type {NormalizedOpen[]} */ + (options.open) = options.open + ? [ + { + target: "", + options: /** @type {OpenOptions} */ (defaultOpenOptions), + }, + ] : []; } else if (typeof options.open === "string") { - options.open = [{ target: options.open, options: defaultOpenOptions }]; + /** @type {NormalizedOpen[]} */ + (options.open) = [{ target: options.open, options: defaultOpenOptions }]; } else if (Array.isArray(options.open)) { + /** + * @type {NormalizedOpen[]} + */ const result = []; options.open.forEach((item) => { @@ -960,9 +1334,11 @@ class Server { result.push(...getOpenItemsFromObject(item)); }); - options.open = result; + /** @type {NormalizedOpen[]} */ + (options.open) = result; } else { - options.open = [...getOpenItemsFromObject(options.open)]; + /** @type {NormalizedOpen[]} */ + (options.open) = [...getOpenItemsFromObject(options.open)]; } if (options.onAfterSetupMiddleware) { @@ -1004,61 +1380,91 @@ class Server { Object.prototype.hasOwnProperty.call(options.proxy, "target") || Object.prototype.hasOwnProperty.call(options.proxy, "router") ) { - options.proxy = [options.proxy]; + /** @type {ProxyArray} */ + (options.proxy) = [/** @type {ProxyConfigMap} */ (options.proxy)]; } else { - options.proxy = Object.keys(options.proxy).map((context) => { - let proxyOptions; - // For backwards compatibility reasons. - const correctedContext = context - .replace(/^\*$/, "**") - .replace(/\/\*$/, ""); - - if (typeof options.proxy[context] === "string") { - proxyOptions = { - context: correctedContext, - target: options.proxy[context], - }; - } else { - proxyOptions = { ...options.proxy[context] }; - proxyOptions.context = correctedContext; + /** @type {ProxyArray} */ + (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; } - - return proxyOptions; - }); + ); } } - options.proxy = options.proxy.map((item) => { - const getLogLevelForProxy = (level) => { - if (level === "none") { - return "silent"; - } + /** @type {ProxyArray} */ + (options.proxy) = + /** @type {ProxyArray} */ + (options.proxy).map( + /** + * @param {HttpProxyMiddlewareOptions} item + * @returns {HttpProxyMiddlewareOptions} + */ + (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 (level === "log") { - return "info"; - } + if (typeof item.logProvider === "undefined") { + item.logProvider = () => this.logger; + } - if (level === "verbose") { - return "debug"; + return item; } - - 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") { @@ -1136,38 +1542,61 @@ class Server { }; } else { options.webSocketServer = { - type: options.webSocketServer.type || defaultWebSocketServerType, + type: + /** @type {WebSocketServerConfiguration} */ + (options.webSocketServer).type || defaultWebSocketServerType, options: { ...defaultWebSocketServerOptions, - ...options.webSocketServer.options, + .../** @type {WebSocketServerConfiguration} */ + (options.webSocketServer).options, }, }; - if (typeof options.webSocketServer.options.port === "string") { - options.webSocketServer.options.port = Number( - options.webSocketServer.options.port - ); + const webSocketServer = + /** @type {{ type: WebSocketServerConfiguration["type"], options: NonNullable }} */ + (options.webSocketServer); + + if (typeof webSocketServer.options.port === "string") { + webSocketServer.options.port = Number(webSocketServer.options.port); } } } + /** + * @private + * @returns {string} + */ getClientTransport() { - let ClientImplementation; + let clientImplementation; let clientImplementationFound = true; const isKnownWebSocketServerImplementation = this.options.webSocketServer && - typeof this.options.webSocketServer.type === "string" && + typeof ( + /** @type {WebSocketServerConfiguration} */ + (this.options.webSocketServer).type + ) === "string" && + // @ts-ignore (this.options.webSocketServer.type === "ws" || - this.options.webSocketServer.type === "sockjs"); + /** @type {WebSocketServerConfiguration} */ + (this.options.webSocketServer).type === "sockjs"); let clientTransport; if (this.options.client) { - if (typeof this.options.client.webSocketTransport !== "undefined") { - clientTransport = this.options.client.webSocketTransport; + if ( + typeof ( + /** @type {ClientConfiguration} */ + (this.options.client).webSocketTransport + ) !== "undefined" + ) { + clientTransport = + /** @type {ClientConfiguration} */ + (this.options.client).webSocketTransport; } else if (isKnownWebSocketServerImplementation) { - clientTransport = this.options.webSocketServer.type; + clientTransport = + /** @type {WebSocketServerConfiguration} */ + (this.options.webSocketServer).type; } else { clientTransport = "ws"; } @@ -1179,16 +1608,16 @@ class Server { case "string": // could be 'sockjs', 'ws', or a path that should be required if (clientTransport === "sockjs") { - ClientImplementation = require.resolve( + clientImplementation = require.resolve( "../client/clients/SockJSClient" ); } else if (clientTransport === "ws") { - ClientImplementation = require.resolve( + clientImplementation = require.resolve( "../client/clients/WebSocketClient" ); } else { try { - ClientImplementation = require.resolve(clientTransport); + clientImplementation = require.resolve(clientTransport); } catch (e) { clientImplementationFound = false; } @@ -1208,31 +1637,52 @@ class Server { ); } - return ClientImplementation; + return /** @type {string} */ (clientImplementation); } + /** + * @private + * @returns {string} + */ getServerTransport() { let implementation; let implementationFound = true; - switch (typeof this.options.webSocketServer.type) { + switch ( + typeof ( + /** @type {WebSocketServerConfiguration} */ + (this.options.webSocketServer).type + ) + ) { case "string": // Could be 'sockjs', in the future 'ws', or a path that should be required - if (this.options.webSocketServer.type === "sockjs") { + if ( + /** @type {WebSocketServerConfiguration} */ ( + this.options.webSocketServer + ).type === "sockjs" + ) { implementation = require("./servers/SockJSServer"); - } else if (this.options.webSocketServer.type === "ws") { + } else if ( + /** @type {WebSocketServerConfiguration} */ ( + this.options.webSocketServer + ).type === "ws" + ) { implementation = require("./servers/WebsocketServer"); } else { try { // eslint-disable-next-line import/no-dynamic-require - implementation = require(this.options.webSocketServer.type); + implementation = require(/** @type {WebSocketServerConfiguration} */ ( + this.options.webSocketServer + ).type); } catch (error) { implementationFound = false; } } break; case "function": - implementation = this.options.webSocketServer.type; + implementation = /** @type {WebSocketServerConfiguration} */ ( + this.options.webSocketServer + ).type; break; default: implementationFound = false; @@ -1249,37 +1699,61 @@ class Server { return implementation; } + /** + * @private + * @returns {void} + */ setupProgressPlugin() { - const { ProgressPlugin } = this.compiler.webpack || require("webpack"); - - new ProgressPlugin((percent, msg, addInfo, pluginName) => { - percent = Math.floor(percent * 100); + const { ProgressPlugin } = + /** @type {MultiCompiler}*/ + (this.compiler).compilers + ? /** @type {MultiCompiler}*/ (this.compiler).compilers[0].webpack + : /** @type {Compiler}*/ (this.compiler).webpack || + // TODO remove me after drop webpack v4 + require("webpack"); + + new ProgressPlugin( + /** + * @param {number} percent + * @param {string} msg + * @param {string} addInfo + * @param {string} pluginName + */ + (percent, msg, addInfo, pluginName) => { + percent = Math.floor(percent * 100); - if (percent === 100) { - msg = "Compilation completed"; - } + if (percent === 100) { + msg = "Compilation completed"; + } - if (addInfo) { - msg = `${msg} (${addInfo})`; - } + if (addInfo) { + msg = `${msg} (${addInfo})`; + } - if (this.webSocketServer) { - this.sendMessage(this.webSocketServer.clients, "progress-update", { - percent, - msg, - pluginName, - }); - } + if (this.webSocketServer) { + this.sendMessage(this.webSocketServer.clients, "progress-update", { + percent, + msg, + pluginName, + }); + } - if (this.server) { - this.server.emit("progress-update", { percent, msg, pluginName }); + if (this.server) { + this.server.emit("progress-update", { percent, msg, pluginName }); + } } - }).apply(this.compiler); + ).apply(this.compiler); } + /** + * @private + * @returns {Promise} + */ async initialize() { if (this.options.webSocketServer) { - const compilers = this.compiler.compilers || [this.compiler]; + const compilers = + /** @type {MultiCompiler} */ + (this.compiler).compilers || [this.compiler]; compilers.forEach((compiler) => { this.addAdditionalEntries(compiler); @@ -1311,7 +1785,10 @@ class Server { } }); - if (this.options.client && this.options.client.progress) { + if ( + this.options.client && + /** @type {ClientConfiguration} */ (this.options.client).progress + ) { this.setupProgressPlugin(); } } @@ -1363,53 +1840,108 @@ class Server { // Proxy WebSocket without the initial http request // https://github.com/chimurai/http-proxy-middleware#external-websocket-upgrade - this.webSocketProxies.forEach((webSocketProxy) => { - this.server.on("upgrade", webSocketProxy.upgrade); + /** @type {RequestHandler[]} */ + (this.webSocketProxies).forEach((webSocketProxy) => { + /** @type {import("http").Server} */ + (this.server).on( + "upgrade", + /** @type {RequestHandler & { upgrade: NonNullable }} */ + (webSocketProxy).upgrade + ); }, this); } + /** + * @private + * @returns {void} + */ setupApp() { - // Init express server + /** @type {import("express").Application | undefined}*/ // eslint-disable-next-line new-cap - this.app = new express(); + this.app = new /** @type {any} */ (express)(); } + /** + * @private + * @param {Stats | MultiStats} statsObj + * @returns {StatsCompilation} + */ getStats(statsObj) { const stats = Server.DEFAULT_STATS; const compilerOptions = this.getCompilerOptions(); + // @ts-ignore if (compilerOptions.stats && compilerOptions.stats.warningsFilter) { + // @ts-ignore stats.warningsFilter = compilerOptions.stats.warningsFilter; } return statsObj.toJson(stats); } + /** + * @private + * @returns {void} + */ setupHooks() { this.compiler.hooks.invalid.tap("webpack-dev-server", () => { if (this.webSocketServer) { this.sendMessage(this.webSocketServer.clients, "invalid"); } }); - this.compiler.hooks.done.tap("webpack-dev-server", (stats) => { - if (this.webSocketServer) { - this.sendStats(this.webSocketServer.clients, this.getStats(stats)); - } + this.compiler.hooks.done.tap( + "webpack-dev-server", + /** + * @param {Stats | MultiStats} stats + */ + (stats) => { + if (this.webSocketServer) { + this.sendStats(this.webSocketServer.clients, this.getStats(stats)); + } - this.stats = stats; - }); + /** + * @private + * @type {Stats | MultiStats} + */ + this.stats = stats; + } + ); } + /** + * @private + * @returns {void} + */ setupHostHeaderCheck() { - this.app.all("*", (req, res, next) => { - if (this.checkHeader(req.headers, "host")) { - return next(); - } + /** @type {import("express").Application} */ + (this.app).all( + "*", + /** + * @param {Request} req + * @param {Response} res + * @param {NextFunction} next + * @returns {void} + */ + (req, res, next) => { + if ( + this.checkHeader( + /** @type {{ [key: string]: string | undefined }} */ + (req.headers), + "host" + ) + ) { + return next(); + } - res.send("Invalid Host header"); - }); + res.send("Invalid Host header"); + } + ); } + /** + * @private + * @returns {void} + */ setupDevMiddleware() { const webpackDevMiddleware = require("webpack-dev-middleware"); @@ -1420,74 +1952,122 @@ class Server { ); } + /** + * @private + * @returns {void} + */ setupBuiltInRoutes() { const { app, middleware } = this; - app.get("/__webpack_dev_server__/sockjs.bundle.js", (req, res) => { - res.setHeader("Content-Type", "application/javascript"); - - const { createReadStream } = fs; - const clientPath = path.join(__dirname, "..", "client"); - - createReadStream( - path.join(clientPath, "modules/sockjs-client/index.js") - ).pipe(res); - }); - - app.get("/webpack-dev-server/invalidate", (_req, res) => { - this.invalidate(); - - res.end(); - }); - - app.get("/webpack-dev-server", (req, res) => { - middleware.waitUntilValid((stats) => { - res.setHeader("Content-Type", "text/html"); - res.write( - '' - ); - - const statsForPrint = - typeof stats.stats !== "undefined" - ? stats.toJson().children - : [stats.toJson()]; - - res.write(`

Assets Report:

`); + /** @type {import("express").Application} */ + (app).get( + "/__webpack_dev_server__/sockjs.bundle.js", + /** + * @param {Request} req + * @param {Response} res + * @returns {void} + */ + (req, res) => { + res.setHeader("Content-Type", "application/javascript"); - statsForPrint.forEach((item, index) => { - res.write("
"); + const { createReadStream } = fs; + const clientPath = path.join(__dirname, "..", "client"); - const name = - item.name || (stats.stats ? `unnamed[${index}]` : "unnamed"); + createReadStream( + path.join(clientPath, "modules/sockjs-client/index.js") + ).pipe(res); + } + ); - res.write(`

Compilation: ${name}

`); - res.write("
    "); + /** @type {import("express").Application} */ + (app).get( + "/webpack-dev-server/invalidate", + /** + * @param {Request} _req + * @param {Response} res + * @returns {void} + */ + (_req, res) => { + this.invalidate(); - const publicPath = item.publicPath === "auto" ? "" : item.publicPath; + res.end(); + } + ); - for (const asset of item.assets) { - const assetName = asset.name; - const assetURL = `${publicPath}${assetName}`; + /** @type {import("express").Application} */ + (app).get( + "/webpack-dev-server", + /** + * @param {Request} req + * @param {Response} res + * @returns {void} + */ + (req, res) => { + /** @type {import("webpack-dev-middleware").API}*/ + (middleware).waitUntilValid((stats) => { + res.setHeader("Content-Type", "text/html"); + res.write( + '' + ); - res.write( - `
  • + const statsForPrint = + typeof (/** @type {MultiStats} */ (stats).stats) !== "undefined" + ? /** @type {MultiStats} */ (stats).toJson().children + : [/** @type {Stats} */ (stats).toJson()]; + + res.write(`

    Assets Report:

    `); + + /** + * @type {StatsCompilation[]} + */ + (statsForPrint).forEach((item, index) => { + res.write("
    "); + + const name = + // eslint-disable-next-line no-nested-ternary + typeof item.name !== "undefined" + ? item.name + : /** @type {MultiStats} */ (stats).stats + ? `unnamed[${index}]` + : "unnamed"; + + res.write(`

    Compilation: ${name}

    `); + res.write("
      "); + + const publicPath = + item.publicPath === "auto" ? "" : item.publicPath; + + for (const asset of /** @type {NonNullable} */ ( + item.assets + )) { + const assetName = asset.name; + const assetURL = `${publicPath}${assetName}`; + + res.write( + `
    • ${assetName}
    • ` - ); - } + ); + } - res.write("
    "); - res.write("
    "); - }); + res.write("
"); + res.write("
"); + }); - res.end(""); - }); - }); + res.end(""); + }); + } + ); } + /** + * @private + * @returns {void} + */ setupWatchStaticFiles() { - if (this.options.static.length > 0) { - this.options.static.forEach((staticOption) => { + if (/** @type {NormalizedStatic[]} */ (this.options.static).length > 0) { + /** @type {NormalizedStatic[]} */ + (this.options.static).forEach((staticOption) => { if (staticOption.watch) { this.watchFiles(staticOption.directory, staticOption.watch); } @@ -1495,17 +2075,29 @@ class Server { } } + /** + * @private + * @returns {void} + */ setupWatchFiles() { const { watchFiles } = this.options; - if (watchFiles.length > 0) { - watchFiles.forEach((item) => { + if (/** @type {WatchFiles[]} */ (watchFiles).length > 0) { + /** @type {WatchFiles[]} */ + (watchFiles).forEach((item) => { this.watchFiles(item.paths, item.options); }); } } + /** + * @private + * @returns {void} + */ setupMiddlewares() { + /** + * @type {Array} + */ let middlewares = []; // compress is placed last and uses unshift so that it will be the first middleware used @@ -1515,7 +2107,7 @@ class Server { middlewares.push({ name: "compression", middleware: compression() }); } - if (this.options.onBeforeSetupMiddleware) { + if (typeof this.options.onBeforeSetupMiddleware === "function") { this.options.onBeforeSetupMiddleware(this); } @@ -1529,19 +2121,28 @@ class Server { middlewares.push({ name: "webpack-dev-middleware", - middleware: this.middleware, + middleware: + /** @type {import("webpack-dev-middleware").Middleware}*/ + (this.middleware), }); if (this.options.proxy) { const { createProxyMiddleware } = require("http-proxy-middleware"); + /** + * @param {ProxyConfigArray} proxyConfig + * @returns {RequestHandler | undefined} + */ const getProxyMiddleware = (proxyConfig) => { // It is possible to use the `bypass` method without a `target` or `router`. // However, the proxy middleware has no use in this case, and will fail to instantiate. if (proxyConfig.target) { const context = proxyConfig.context || proxyConfig.path; - return createProxyMiddleware(context, proxyConfig); + return createProxyMiddleware( + /** @type {string} */ (context), + proxyConfig + ); } if (proxyConfig.router) { @@ -1565,73 +2166,105 @@ class Server { * } * ] */ - this.options.proxy.forEach((proxyConfigOrCallback) => { - let proxyMiddleware; - - let proxyConfig = - typeof proxyConfigOrCallback === "function" - ? proxyConfigOrCallback() - : proxyConfigOrCallback; - - proxyMiddleware = getProxyMiddleware(proxyConfig); - - if (proxyConfig.ws) { - this.webSocketProxies.push(proxyMiddleware); - } - - const handler = async (req, res, next) => { - if (typeof proxyConfigOrCallback === "function") { - const newProxyConfig = proxyConfigOrCallback(req, res, next); + /** @type {ProxyArray} */ + (this.options.proxy).forEach( + /** + * @param {any} proxyConfigOrCallback + */ + (proxyConfigOrCallback) => { + /** + * @type {RequestHandler} + */ + let proxyMiddleware; + + let proxyConfig = + typeof proxyConfigOrCallback === "function" + ? proxyConfigOrCallback() + : proxyConfigOrCallback; + + proxyMiddleware = + /** @type {RequestHandler} */ + (getProxyMiddleware(proxyConfig)); + + if (proxyConfig.ws) { + this.webSocketProxies.push(proxyMiddleware); + } - if (newProxyConfig !== proxyConfig) { - proxyConfig = newProxyConfig; - proxyMiddleware = getProxyMiddleware(proxyConfig); + /** + * @param {Request} req + * @param {Response} res + * @param {NextFunction} next + * @returns {Promise} + */ + const handler = async (req, res, next) => { + if (typeof proxyConfigOrCallback === "function") { + const newProxyConfig = proxyConfigOrCallback(req, res, next); + + if (newProxyConfig !== proxyConfig) { + proxyConfig = newProxyConfig; + proxyMiddleware = + /** @type {RequestHandler} */ + (getProxyMiddleware(proxyConfig)); + } } - } - // - Check if we have a bypass function defined - // - In case the bypass function is defined we'll retrieve the - // bypassUrl from it otherwise bypassUrl would be null - // TODO remove in the next major in favor `context` and `router` options - const isByPassFuncDefined = typeof proxyConfig.bypass === "function"; - const bypassUrl = isByPassFuncDefined - ? await proxyConfig.bypass(req, res, proxyConfig) - : null; - - if (typeof bypassUrl === "boolean") { - // skip the proxy - req.url = null; - next(); - } else if (typeof bypassUrl === "string") { - // byPass to that url - req.url = bypassUrl; - next(); - } else if (proxyMiddleware) { - return proxyMiddleware(req, res, next); - } else { - next(); - } - }; + // - Check if we have a bypass function defined + // - In case the bypass function is defined we'll retrieve the + // bypassUrl from it otherwise bypassUrl would be null + // TODO remove in the next major in favor `context` and `router` options + const isByPassFuncDefined = + typeof proxyConfig.bypass === "function"; + const bypassUrl = isByPassFuncDefined + ? await proxyConfig.bypass(req, res, proxyConfig) + : null; + + if (typeof bypassUrl === "boolean") { + // skip the proxy + // @ts-ignore + req.url = null; + next(); + } else if (typeof bypassUrl === "string") { + // byPass to that url + req.url = bypassUrl; + next(); + } else if (proxyMiddleware) { + return proxyMiddleware(req, res, next); + } else { + next(); + } + }; - middlewares.push({ - name: "http-proxy-middleware", - middleware: handler, - }); - // Also forward error requests to the proxy so it can handle them. - middlewares.push({ - name: "http-proxy-middleware-error-handler", - middleware: (error, req, res, next) => handler(req, res, next), - }); - }); + middlewares.push({ + name: "http-proxy-middleware", + middleware: handler, + }); + // Also forward error requests to the proxy so it can handle them. + middlewares.push({ + name: "http-proxy-middleware-error-handler", + middleware: + /** + * @param {Error} error + * @param {Request} req + * @param {Response} res + * @param {NextFunction} next + * @returns {any} + */ + (error, req, res, next) => handler(req, res, next), + }); + } + ); middlewares.push({ name: "webpack-dev-middleware", - middleware: this.middleware, + middleware: + /** @type {import("webpack-dev-middleware").Middleware}*/ + (this.middleware), }); } - if (this.options.static.length > 0) { - this.options.static.forEach((staticOption) => { + if (/** @type {NormalizedStatic[]} */ (this.options.static).length > 0) { + /** @type {NormalizedStatic[]} */ + (this.options.static).forEach((staticOption) => { staticOption.publicPath.forEach((publicPath) => { middlewares.push({ name: "express-static", @@ -1650,9 +2283,16 @@ class Server { const { historyApiFallback } = this.options; if ( - typeof historyApiFallback.logger === "undefined" && - !historyApiFallback.verbose + typeof ( + /** @type {ConnectHistoryApiFallbackOptions} */ + (historyApiFallback).logger + ) === "undefined" && + !( + /** @type {ConnectHistoryApiFallbackOptions} */ + (historyApiFallback).verbose + ) ) { + // @ts-ignore historyApiFallback.logger = this.logger.log.bind( this.logger, "[connect-history-api-fallback]" @@ -1662,18 +2302,24 @@ class Server { // Fall back to /index.html if nothing else matches. middlewares.push({ name: "connect-history-api-fallback", - middleware: connectHistoryApiFallback(historyApiFallback), + middleware: connectHistoryApiFallback( + /** @type {ConnectHistoryApiFallbackOptions} */ + (historyApiFallback) + ), }); // include our middleware to ensure // it is able to handle '/index.html' request after redirect middlewares.push({ name: "webpack-dev-middleware", - middleware: this.middleware, + middleware: + /** @type {import("webpack-dev-middleware").Middleware}*/ + (this.middleware), }); - if (this.options.static.length > 0) { - this.options.static.forEach((staticOption) => { + if (/** @type {NormalizedStatic[]} */ (this.options.static).length > 0) { + /** @type {NormalizedStatic[]} */ + (this.options.static).forEach((staticOption) => { staticOption.publicPath.forEach((publicPath) => { middlewares.push({ name: "express-static", @@ -1688,26 +2334,33 @@ class Server { } } - if (this.options.static.length > 0) { + if (/** @type {NormalizedStatic[]} */ (this.options.static).length > 0) { const serveIndex = require("serve-index"); - this.options.static.forEach((staticOption) => { + /** @type {NormalizedStatic[]} */ + (this.options.static).forEach((staticOption) => { staticOption.publicPath.forEach((publicPath) => { if (staticOption.serveIndex) { middlewares.push({ name: "serve-index", path: publicPath, + /** + * @param {Request} req + * @param {Response} res + * @param {NextFunction} next + * @returns {void} + */ middleware: (req, res, next) => { // serve-index doesn't fallthrough non-get/head request to next middleware if (req.method !== "GET" && req.method !== "HEAD") { return next(); } - serveIndex(staticOption.directory, staticOption.serveIndex)( - req, - res, - next - ); + serveIndex( + staticOption.directory, + /** @type {ServeIndexOptions} */ + (staticOption.serveIndex) + )(req, res, next); }, }); } @@ -1722,7 +2375,7 @@ class Server { }); } - if (this.options.onAfterSetupMiddleware) { + if (typeof this.options.onAfterSetupMiddleware === "function") { this.options.onAfterSetupMiddleware(this); } @@ -1731,104 +2384,184 @@ class Server { } middlewares.forEach((middleware) => { - if (typeof middleware.path !== "undefined") { - this.app.use(middleware.path, middleware.middleware); - } else if (typeof middleware === "function") { - this.app.use(middleware); + if (typeof middleware === "function") { + /** @type {import("express").Application} */ + (this.app).use(middleware); + } else if (typeof middleware.path !== "undefined") { + /** @type {import("express").Application} */ + (this.app).use(middleware.path, middleware.middleware); } else { - this.app.use(middleware.middleware); + /** @type {import("express").Application} */ + (this.app).use(middleware.middleware); } }); } + /** + * @private + * @returns {void} + */ createServer() { + const { type, options } = /** @type {ServerConfiguration} */ ( + this.options.server + ); + + /** @type {import("http").Server | undefined | null} */ // eslint-disable-next-line import/no-dynamic-require - this.server = require(this.options.server.type).createServer( - this.options.server.options, + this.server = require(/** @type {string} */ (type)).createServer( + options, this.app ); - this.server.on("connection", (socket) => { - // Add socket to list - this.sockets.push(socket); + /** @type {import("http").Server} */ + (this.server).on( + "connection", + /** + * @param {Socket} socket + */ + (socket) => { + // Add socket to list + this.sockets.push(socket); - socket.once("close", () => { - // Remove socket from list - this.sockets.splice(this.sockets.indexOf(socket), 1); - }); - }); + socket.once("close", () => { + // Remove socket from list + this.sockets.splice(this.sockets.indexOf(socket), 1); + }); + } + ); - this.server.on("error", (error) => { - throw error; - }); + /** @type {import("http").Server} */ + (this.server).on( + "error", + /** + * @param {Error} error + */ + (error) => { + throw error; + } + ); } + /** + * @private + * @returns {void} + */ // TODO: remove `--web-socket-server` in favor of `--web-socket-server-type` createWebSocketServer() { - this.webSocketServer = new (this.getServerTransport())(this); - this.webSocketServer.implementation.on("connection", (client, request) => { - const headers = - // eslint-disable-next-line no-nested-ternary - typeof request !== "undefined" - ? request.headers - : typeof client.headers !== "undefined" - ? client.headers - : // eslint-disable-next-line no-undefined - undefined; - - if (!headers) { - this.logger.warn( - 'webSocketServer implementation must pass headers for the "connection" event' - ); - } + /** @type {WebSocketServerImplementation | undefined | null} */ + this.webSocketServer = new /** @type {any} */ (this.getServerTransport())( + this + ); + /** @type {WebSocketServerImplementation} */ + (this.webSocketServer).implementation.on( + "connection", + /** + * @param {ClientConnection} client + * @param {IncomingMessage} request + */ + (client, request) => { + /** @type {{ [key: string]: string | undefined } | undefined} */ + const headers = + // eslint-disable-next-line no-nested-ternary + typeof request !== "undefined" + ? /** @type {{ [key: string]: string | undefined }} */ + (request.headers) + : typeof ( + /** @type {import("sockjs").Connection} */ (client).headers + ) !== "undefined" + ? /** @type {import("sockjs").Connection} */ (client).headers + : // eslint-disable-next-line no-undefined + undefined; + + if (!headers) { + this.logger.warn( + 'webSocketServer implementation must pass headers for the "connection" event' + ); + } - if ( - !headers || - !this.checkHeader(headers, "host") || - !this.checkHeader(headers, "origin") - ) { - this.sendMessage([client], "error", "Invalid Host/Origin header"); + if ( + !headers || + !this.checkHeader(headers, "host") || + !this.checkHeader(headers, "origin") + ) { + this.sendMessage([client], "error", "Invalid Host/Origin header"); - // With https enabled, the sendMessage above is encrypted asynchronously so not yet sent - // Terminate would prevent it sending, so use close to allow it to be sent - client.close(); + // With https enabled, the sendMessage above is encrypted asynchronously so not yet sent + // Terminate would prevent it sending, so use close to allow it to be sent + client.close(); - return; - } + return; + } - if (this.options.hot === true || this.options.hot === "only") { - this.sendMessage([client], "hot"); - } + if (this.options.hot === true || this.options.hot === "only") { + this.sendMessage([client], "hot"); + } - if (this.options.liveReload) { - this.sendMessage([client], "liveReload"); - } + if (this.options.liveReload) { + this.sendMessage([client], "liveReload"); + } - if (this.options.client && this.options.client.progress) { - this.sendMessage([client], "progress", this.options.client.progress); - } + if ( + this.options.client && + /** @type {ClientConfiguration} */ + (this.options.client).progress + ) { + this.sendMessage( + [client], + "progress", + /** @type {ClientConfiguration} */ + (this.options.client).progress + ); + } - if (this.options.client && this.options.client.reconnect) { - this.sendMessage([client], "reconnect", this.options.client.reconnect); - } + if ( + this.options.client && + /** @type {ClientConfiguration} */ (this.options.client).reconnect + ) { + this.sendMessage( + [client], + "reconnect", + /** @type {ClientConfiguration} */ + (this.options.client).reconnect + ); + } - if (this.options.client && this.options.client.overlay) { - this.sendMessage([client], "overlay", this.options.client.overlay); - } + if ( + this.options.client && + /** @type {ClientConfiguration} */ + (this.options.client).overlay + ) { + this.sendMessage( + [client], + "overlay", + /** @type {ClientConfiguration} */ + (this.options.client).overlay + ); + } - if (!this.stats) { - return; - } + if (!this.stats) { + return; + } - this.sendStats([client], this.getStats(this.stats), true); - }); + this.sendStats([client], this.getStats(this.stats), true); + } + ); } + /** + * @private + * @param {string} defaultOpenTarget + * @returns {void} + */ openBrowser(defaultOpenTarget) { const open = require("open"); Promise.all( - this.options.open.map((item) => { + /** @type {NormalizedOpen[]} */ + (this.options.open).map((item) => { + /** + * @type {string} + */ let openTarget; if (item.target === "") { @@ -1843,11 +2576,16 @@ class Server { this.logger.warn( `Unable to open "${openTarget}" page${ item.options.app - ? ` in "${item.options.app.name}" app${ - item.options.app.arguments - ? ` with "${item.options.app.arguments.join( - " " - )}" arguments` + ? ` in "${ + /** @type {import("open").App} */ + (item.options.app).name + }" app${ + /** @type {import("open").App} */ + (item.options.app).arguments + ? ` with "${ + /** @type {import("open").App} */ + (item.options.app).arguments.join(" ") + }" arguments` : "" }` : "" @@ -1858,38 +2596,68 @@ class Server { ); } - stopBonjour(callback = () => {}) { - this.bonjour.unpublishAll(() => { - this.bonjour.destroy(); - - if (callback) { - callback(); - } - }); - } - + /** + * @private + * @returns {void} + */ runBonjour() { + /** + * @private + * @type {import("bonjour").Bonjour | undefined} + */ this.bonjour = require("bonjour")(); this.bonjour.publish({ name: `Webpack Dev Server ${os.hostname()}:${this.options.port}`, - port: this.options.port, - type: this.options.server.type === "http" ? "http" : "https", + port: /** @type {number} */ (this.options.port), + type: + /** @type {ServerConfiguration} */ + (this.options.server).type === "http" ? "http" : "https", subtypes: ["webpack"], - ...this.options.bonjour, + .../** @type {BonjourOptions} */ (this.options.bonjour), + }); + } + + /** + * @private + * @returns {void} + */ + stopBonjour(callback = () => {}) { + /** @type {Bonjour} */ + (this.bonjour).unpublishAll(() => { + /** @type {Bonjour} */ + (this.bonjour).destroy(); + + if (callback) { + callback(); + } }); } + /** + * @private + * @returns {void} + */ logStatus() { const { isColorSupported, cyan, red } = require("colorette"); + /** + * @param {Compiler["options"]} compilerOptions + * @returns {boolean} + */ const getColorsOption = (compilerOptions) => { + /** + * @type {boolean} + */ let colorsEnabled; if ( compilerOptions.stats && - typeof compilerOptions.stats.colors !== "undefined" + typeof (/** @type {StatsOptions} */ (compilerOptions.stats).colors) !== + "undefined" ) { - colorsEnabled = compilerOptions.stats; + colorsEnabled = + /** @type {boolean} */ + (/** @type {StatsOptions} */ (compilerOptions.stats).colors); } else { colorsEnabled = isColorSupported; } @@ -1898,6 +2666,11 @@ class Server { }; const colors = { + /** + * @param {boolean} useColor + * @param {string} msg + * @returns {string} + */ info(useColor, msg) { if (useColor) { return cyan(msg); @@ -1905,6 +2678,11 @@ class Server { return msg; }, + /** + * @param {boolean} useColor + * @param {string} msg + * @returns {string} + */ error(useColor, msg) { if (useColor) { return red(msg); @@ -1916,10 +2694,26 @@ class Server { const useColor = getColorsOption(this.getCompilerOptions()); if (this.options.ipc) { - this.logger.info(`Project is running at: "${this.server.address()}"`); + this.logger.info( + `Project is running at: "${ + /** @type {import("http").Server} */ + (this.server).address() + }"` + ); } else { - const protocol = this.options.server.type === "http" ? "http" : "https"; - const { address, port } = this.server.address(); + const protocol = + /** @type {ServerConfiguration} */ + (this.options.server).type === "http" ? "http" : "https"; + const { address, port } = + /** @type {import("net").AddressInfo} */ + ( + /** @type {import("http").Server} */ + (this.server).address() + ); + /** + * @param {string} newHostname + * @returns {string} + */ const prettyPrintURL = (newHostname) => url.format({ protocol, hostname: newHostname, port, pathname: "/" }); @@ -1972,8 +2766,13 @@ class Server { } } else { networkUrlIPv4 = - parsedIP.kind() === "ipv6" && parsedIP.isIPv4MappedAddress() - ? prettyPrintURL(parsedIP.toIPv4Address().toString()) + parsedIP.kind() === "ipv6" && + /** @type {IPv6} */ + (parsedIP).isIPv4MappedAddress() + ? prettyPrintURL( + /** @type {IPv6} */ + (parsedIP).toIPv4Address().toString() + ) : prettyPrintURL(address); if (parsedIP.kind() === "ipv6") { @@ -1988,10 +2787,19 @@ class Server { } if (localhost || loopbackIPv4 || loopbackIPv6) { - const loopbacks = [] - .concat(localhost ? [colors.info(useColor, localhost)] : []) - .concat(loopbackIPv4 ? [colors.info(useColor, loopbackIPv4)] : []) - .concat(loopbackIPv6 ? [colors.info(useColor, loopbackIPv6)] : []); + const loopbacks = []; + + if (localhost) { + loopbacks.push([colors.info(useColor, localhost)]); + } + + if (loopbackIPv4) { + loopbacks.push([colors.info(useColor, loopbackIPv4)]); + } + + if (loopbackIPv6) { + loopbacks.push([colors.info(useColor, loopbackIPv6)]); + } this.logger.info(`Loopback: ${loopbacks.join(", ")}`); } @@ -2008,18 +2816,19 @@ class Server { ); } - if (this.options.open.length > 0) { + if (/** @type {NormalizedOpen[]} */ (this.options.open).length > 0) { const openTarget = prettyPrintURL(this.options.host || "localhost"); this.openBrowser(openTarget); } } - if (this.options.static.length > 0) { + if (/** @type {NormalizedStatic[]} */ (this.options.static).length > 0) { this.logger.info( `Content not from webpack is served from '${colors.info( useColor, - this.options.static + /** @type {NormalizedStatic[]} */ + (this.options.static) .map((staticOption) => staticOption.directory) .join(", ") )}' directory` @@ -2030,14 +2839,19 @@ class Server { this.logger.info( `404s will fallback to '${colors.info( useColor, - this.options.historyApiFallback.index || "/index.html" + /** @type {ConnectHistoryApiFallbackOptions} */ ( + this.options.historyApiFallback + ).index || "/index.html" )}'` ); } if (this.options.bonjour) { const bonjourProtocol = - this.options.bonjour.type || this.options.server.type === "http" + /** @type {BonjourOptions} */ + (this.options.bonjour).type || + /** @type {ServerConfiguration} */ + (this.options.server).type === "http" ? "http" : "https"; @@ -2047,32 +2861,59 @@ class Server { } } + /** + * @private + * @param {Request} req + * @param {Response} res + * @param {NextFunction} next + */ setHeaders(req, res, next) { let { headers } = this.options; if (headers) { if (typeof headers === "function") { - headers = headers(req, res, this.middleware.context); + headers = headers( + req, + res, + /** @type {import("webpack-dev-middleware").API}*/ + (this.middleware).context + ); } + /** + * @type {{key: string, value: string}[]} + */ const allHeaders = []; if (!Array.isArray(headers)) { // eslint-disable-next-line guard-for-in for (const name in headers) { + // @ts-ignore allHeaders.push({ key: name, value: headers[name] }); } + headers = allHeaders; } - headers.forEach((header) => { - res.setHeader(header.key, header.value); - }); + headers.forEach( + /** + * @param {{key: string, value: any}} header + */ + (header) => { + res.setHeader(header.key, header.value); + } + ); } next(); } + /** + * @private + * @param {{ [key: string]: string | undefined }} headers + * @param {string} headerToCheck + * @returns {boolean} + */ checkHeader(headers, headerToCheck) { // allow user to opt out of this security check, at their own risk // by explicitly enabling allowedHosts @@ -2109,8 +2950,8 @@ class Server { // always allow localhost host, for convenience (hostname === 'localhost') // allow hostname of listening address (hostname === this.options.host) const isValidHostname = - ipaddr.IPv4.isValid(hostname) || - ipaddr.IPv6.isValid(hostname) || + (hostname !== null && ipaddr.IPv4.isValid(hostname)) || + (hostname !== null && ipaddr.IPv6.isValid(hostname)) || hostname === "localhost" || hostname === this.options.host; @@ -2137,7 +2978,7 @@ class Server { // "*.example.com" (hostname.endsWith(allowedHost)) if ( hostname === allowedHost.substring(1) || - hostname.endsWith(allowedHost) + /** @type {string} */ (hostname).endsWith(allowedHost) ) { return true; } @@ -2148,15 +2989,27 @@ class Server { // Also allow if `client.webSocketURL.hostname` provided if ( this.options.client && - typeof this.options.client.webSocketURL !== "undefined" + typeof ( + /** @type {ClientConfiguration} */ (this.options.client).webSocketURL + ) !== "undefined" ) { - return this.options.client.webSocketURL.hostname === hostname; + return ( + /** @type {WebSocketURL} */ + (/** @type {ClientConfiguration} */ (this.options.client).webSocketURL) + .hostname === hostname + ); } // disallow return false; } + /** + * @param {ClientConnection[]} clients + * @param {string} type + * @param {any} [data] + * @param {any} [params] + */ // eslint-disable-next-line class-methods-use-this sendMessage(clients, type, data, params) { for (const client of clients) { @@ -2168,25 +3021,41 @@ class Server { } } + /** + * @private + * @param {Request} req + * @param {Response} res + * @param {NextFunction} next + * @returns {void} + */ serveMagicHtml(req, res, next) { if (req.method !== "GET" && req.method !== "HEAD") { return next(); } - this.middleware.waitUntilValid(() => { + /** @type {import("webpack-dev-middleware").API}*/ + (this.middleware).waitUntilValid(() => { const _path = req.path; try { - const filename = this.middleware.getFilenameFromUrl(`${_path}.js`); - const isFile = this.middleware.context.outputFileSystem - .statSync(filename) - .isFile(); + const filename = + /** @type {import("webpack-dev-middleware").API}*/ + (this.middleware).getFilenameFromUrl(`${_path}.js`); + const isFile = + /** @type {Compiler["outputFileSystem"] & { statSync: import("fs").StatSyncFn }}*/ + ( + /** @type {import("webpack-dev-middleware").API}*/ + (this.middleware).context.outputFileSystem + ) + .statSync(/** @type {import("fs").PathLike} */ (filename)) + .isFile(); if (!isFile) { return next(); } // Serve a page that executes the javascript + // @ts-ignore const queries = req._parsedUrl.search || ""; const responsePage = ``; @@ -2198,6 +3067,12 @@ class Server { } // Send stats to a socket or multiple sockets + /** + * @private + * @param {ClientConnection[]} clients + * @param {StatsCompilation} stats + * @param {boolean} [force] + */ sendStats(clients, stats, force) { const shouldEmit = !force && @@ -2215,10 +3090,20 @@ class Server { this.currentHash = stats.hash; this.sendMessage(clients, "hash", stats.hash); - if (stats.errors.length > 0 || stats.warnings.length > 0) { - const hasErrors = stats.errors.length > 0; + if ( + /** @type {NonNullable} */ + (stats.errors).length > 0 || + /** @type {NonNullable} */ + (stats.warnings).length > 0 + ) { + const hasErrors = + /** @type {NonNullable} */ + (stats.errors).length > 0; - if (stats.warnings.length > 0) { + if ( + /** @type {NonNullable} */ + (stats.warnings).length > 0 + ) { let params; if (hasErrors) { @@ -2228,7 +3113,10 @@ class Server { this.sendMessage(clients, "warnings", stats.warnings, params); } - if (stats.errors.length > 0) { + if ( + /** @type {NonNullable} */ (stats.errors) + .length > 0 + ) { this.sendMessage(clients, "errors", stats.errors); } } else { @@ -2236,6 +3124,10 @@ class Server { } } + /** + * @param {string | string[]} watchPath + * @param {WatchOptions} [watchOptions] + */ watchFiles(watchPath, watchOptions) { const chokidar = require("chokidar"); const watcher = chokidar.watch(watchPath, watchOptions); @@ -2256,44 +3148,65 @@ class Server { this.staticWatchers.push(watcher); } - invalidate(callback) { + /** + * @param {import("webpack-dev-middleware").Callback} [callback] + */ + invalidate(callback = () => {}) { if (this.middleware) { this.middleware.invalidate(callback); } } + /** + * @returns {Promise} + */ async start() { await this.normalizeOptions(); if (this.options.ipc) { - await new Promise((resolve, reject) => { - const net = require("net"); - const socket = new net.Socket(); - - socket.on("error", (error) => { - if (error.code === "ECONNREFUSED") { - // No other server listening on this socket so it can be safely removed - fs.unlinkSync(this.options.ipc); + await /** @type {Promise} */ ( + new Promise((resolve, reject) => { + const net = require("net"); + const socket = new net.Socket(); + + socket.on( + "error", + /** + * @param {Error & { code?: string }} error + */ + (error) => { + if (error.code === "ECONNREFUSED") { + // No other server listening on this socket so it can be safely removed + fs.unlinkSync(/** @type {string} */ (this.options.ipc)); + + resolve(); + + return; + } else if (error.code === "ENOENT") { + resolve(); + + return; + } - resolve(); - - return; - } else if (error.code === "ENOENT") { - resolve(); - - return; - } - - reject(error); - }); + reject(error); + } + ); - socket.connect({ path: this.options.ipc }, () => { - throw new Error(`IPC "${this.options.ipc}" is already used`); - }); - }); + socket.connect( + { path: /** @type {string} */ (this.options.ipc) }, + () => { + throw new Error(`IPC "${this.options.ipc}" is already used`); + } + ); + }) + ); } else { - this.options.host = await Server.getHostname(this.options.host); - this.options.port = await Server.getFreePort(this.options.port); + this.options.host = await Server.getHostname( + /** @type {Host} */ (this.options.host) + ); + this.options.port = await Server.getFreePort( + /** @type {Port} */ (this.options.port) + ); } await this.initialize(); @@ -2302,17 +3215,23 @@ class Server { ? { path: this.options.ipc } : { host: this.options.host, port: this.options.port }; - await new Promise((resolve) => { - this.server.listen(listenOptions, () => { - resolve(); - }); - }); + await /** @type {Promise} */ ( + new Promise((resolve) => { + /** @type {import("http").Server} */ + (this.server).listen(listenOptions, () => { + resolve(); + }); + }) + ); if (this.options.ipc) { // chmod 666 (rw rw rw) const READ_WRITE = 438; - await fs.promises.chmod(this.options.ipc, READ_WRITE); + await fs.promises.chmod( + /** @type {string} */ (this.options.ipc), + READ_WRITE + ); } if (this.options.webSocketServer) { @@ -2330,19 +3249,27 @@ class Server { } } + /** + * @param {(err?: Error) => void} [callback] + */ startCallback(callback = () => {}) { this.start() - .then(() => callback(null), callback) + .then(() => callback(), callback) .catch(callback); } + /** + * @returns {Promise} + */ async stop() { if (this.bonjour) { - await new Promise((resolve) => { - this.stopBonjour(() => { - resolve(); - }); - }); + await /** @type {Promise} */ ( + new Promise((resolve) => { + this.stopBonjour(() => { + resolve(); + }); + }) + ); } this.webSocketProxies = []; @@ -2352,48 +3279,60 @@ class Server { this.staticWatchers = []; if (this.webSocketServer) { - await new Promise((resolve) => { - this.webSocketServer.implementation.close(() => { - this.webSocketServer = null; + await /** @type {Promise} */ ( + new Promise((resolve) => { + /** @type {WebSocketServerImplementation} */ + (this.webSocketServer).implementation.close(() => { + this.webSocketServer = null; - resolve(); - }); + resolve(); + }); - for (const client of this.webSocketServer.clients) { - client.terminate(); - } + for (const client of /** @type {WebSocketServerImplementation} */ ( + this.webSocketServer + ).clients) { + client.terminate(); + } - this.webSocketServer.clients = []; - }); + /** @type {WebSocketServerImplementation} */ + (this.webSocketServer).clients = []; + }) + ); } if (this.server) { - await new Promise((resolve) => { - this.server.close(() => { - this.server = null; + await /** @type {Promise} */ ( + new Promise((resolve) => { + /** @type {import("http").Server} */ + (this.server).close(() => { + this.server = null; - resolve(); - }); + resolve(); + }); - for (const socket of this.sockets) { - socket.destroy(); - } + for (const socket of this.sockets) { + socket.destroy(); + } - this.sockets = []; - }); + this.sockets = []; + }) + ); if (this.middleware) { - await new Promise((resolve, reject) => { - this.middleware.close((error) => { - if (error) { - reject(error); + await /** @type {Promise} */ ( + new Promise((resolve, reject) => { + /** @type {import("webpack-dev-middleware").API}*/ + (this.middleware).close((error) => { + if (error) { + reject(error); - return; - } + return; + } - resolve(); - }); - }); + resolve(); + }); + }) + ); this.middleware = null; } @@ -2406,13 +3345,22 @@ class Server { } } + /** + * @param {(err?: Error) => void} [callback] + */ stopCallback(callback = () => {}) { this.stop() - .then(() => callback(null), callback) + .then(() => callback(), callback) .catch(callback); } // TODO remove in the next major release + /** + * @param {Port} port + * @param {Host} hostname + * @param {(err?: Error) => void} fn + * @returns {void} + */ listen(port, hostname, fn) { util.deprecate( () => {}, @@ -2420,8 +3368,6 @@ class Server { "DEP_WEBPACK_DEV_SERVER_LISTEN" )(); - this.logger = this.compiler.getInfrastructureLogger("webpack-dev-server"); - if (typeof port === "function") { fn = port; } @@ -2458,7 +3404,7 @@ class Server { this.options.host = hostname; } - return this.start() + this.start() .then(() => { if (fn) { fn.call(this.server); @@ -2472,6 +3418,10 @@ class Server { }); } + /** + * @param {(err?: Error) => void} [callback] + * @returns {void} + */ // TODO remove in the next major release close(callback) { util.deprecate( @@ -2480,10 +3430,10 @@ class Server { "DEP_WEBPACK_DEV_SERVER_CLOSE" )(); - return this.stop() + this.stop() .then(() => { if (callback) { - callback(null); + callback(); } }) .catch((error) => { @@ -2494,48 +3444,4 @@ class Server { } } -const mergeExports = (obj, exports) => { - const descriptors = Object.getOwnPropertyDescriptors(exports); - - for (const name of Object.keys(descriptors)) { - const descriptor = descriptors[name]; - - if (descriptor.get) { - const fn = descriptor.get; - - Object.defineProperty(obj, name, { - configurable: false, - enumerable: true, - get: fn, - }); - } else if (typeof descriptor.value === "object") { - Object.defineProperty(obj, name, { - configurable: false, - enumerable: true, - writable: false, - value: mergeExports({}, descriptor.value), - }); - } else { - throw new Error( - "Exposed values must be either a getter or an nested object" - ); - } - } - - return Object.freeze(obj); -}; - -module.exports = mergeExports(Server, { - get schema() { - return schema; - }, - // TODO compatibility with webpack v4, remove it after drop - cli: { - get getArguments() { - return () => require("../bin/cli-flags"); - }, - get processArguments() { - return require("../bin/process-arguments"); - }, - }, -}); +module.exports = Server; diff --git a/lib/servers/BaseServer.js b/lib/servers/BaseServer.js index f512776cd2..30858ba77e 100644 --- a/lib/servers/BaseServer.js +++ b/lib/servers/BaseServer.js @@ -1,10 +1,18 @@ "use strict"; +/** @typedef {import("../Server").ClientConnection} ClientConnection */ + // base class that users should extend if they are making their own // server implementation module.exports = class BaseServer { + /** + * @param {import("../Server")} server + */ constructor(server) { + /** @type {import("../Server")} */ this.server = server; + + /** @type {ClientConnection[]} */ this.clients = []; } }; diff --git a/lib/servers/SockJSServer.js b/lib/servers/SockJSServer.js index 20eaf2679c..5952886ef5 100644 --- a/lib/servers/SockJSServer.js +++ b/lib/servers/SockJSServer.js @@ -3,13 +3,20 @@ const sockjs = require("sockjs"); const BaseServer = require("./BaseServer"); +/** @typedef {import("../Server").WebSocketServerConfiguration} WebSocketServerConfiguration */ +/** @typedef {import("../Server").ClientConnection} ClientConnection */ + // Workaround for sockjs@~0.3.19 // sockjs will remove Origin header, however Origin header is required for checking host. // See https://github.com/webpack/webpack-dev-server/issues/1604 for more information { + // @ts-ignore const SockjsSession = require("sockjs/lib/transport").Session; const decorateConnection = SockjsSession.prototype.decorateConnection; + /** + * @param {import("http").IncomingMessage} req + */ // eslint-disable-next-line func-names SockjsSession.prototype.decorateConnection = function (req) { decorateConnection.call(this, req); @@ -28,6 +35,9 @@ const BaseServer = require("./BaseServer"); module.exports = class SockJSServer extends BaseServer { // options has: error (function), debug (function), server (http/s server), path (string) + /** + * @param {import("../Server")} server + */ constructor(server) { super(server); @@ -35,6 +45,10 @@ module.exports = class SockJSServer extends BaseServer { // Use provided up-to-date sockjs-client sockjs_url: "/__webpack_dev_server__/sockjs.bundle.js", // Default logger is very annoy. Limit useless logs. + /** + * @param {string} severity + * @param {string} line + */ log: (severity, line) => { if (severity === "error") { this.server.logger.error(line); @@ -46,6 +60,10 @@ module.exports = class SockJSServer extends BaseServer { }, }); + /** + * @param {import("sockjs").ServerOptions & { path?: string }} options + * @returns {string | undefined} + */ const getPrefix = (options) => { if (typeof options.prefix !== "undefined") { return options.prefix; @@ -54,23 +72,41 @@ module.exports = class SockJSServer extends BaseServer { return options.path; }; - this.implementation.installHandlers(this.server.server, { - ...this.server.options.webSocketServer.options, - prefix: getPrefix(this.server.options.webSocketServer.options), - }); + const options = { + .../** @type {WebSocketServerConfiguration} */ + (this.server.options.webSocketServer).options, + prefix: getPrefix( + /** @type {NonNullable} */ + ( + /** @type {WebSocketServerConfiguration} */ + (this.server.options.webSocketServer).options + ) + ), + }; + + this.implementation.installHandlers( + /** @type {import("http").Server} */ (this.server.server), + options + ); this.implementation.on("connection", (client) => { + // @ts-ignore // Implement the the same API as for `ws` client.send = client.write; + // @ts-ignore client.terminate = client.close; - this.clients.push(client); + this.clients.push(/** @type {ClientConnection} */ (client)); client.on("close", () => { - this.clients.splice(this.clients.indexOf(client), 1); + this.clients.splice( + this.clients.indexOf(/** @type {ClientConnection} */ (client)), + 1 + ); }); }); + // @ts-ignore this.implementation.close = (callback) => { callback(); }; diff --git a/lib/servers/WebsocketServer.js b/lib/servers/WebsocketServer.js index 270664a2c3..2a7636da5d 100644 --- a/lib/servers/WebsocketServer.js +++ b/lib/servers/WebsocketServer.js @@ -3,14 +3,22 @@ const WebSocket = require("ws"); const BaseServer = require("./BaseServer"); +/** @typedef {import("../Server").WebSocketServerConfiguration} WebSocketServerConfiguration */ +/** @typedef {import("../Server").ClientConnection} ClientConnection */ + module.exports = class WebsocketServer extends BaseServer { static heartbeatInterval = 1000; + /** + * @param {import("../Server")} server + */ constructor(server) { super(server); + /** @type {import("ws").ServerOptions} */ const options = { - ...this.server.options.webSocketServer.options, + .../** @type {WebSocketServerConfiguration} */ + (this.server.options.webSocketServer).options, clientTracking: false, }; const isNoServerMode = @@ -23,46 +31,72 @@ module.exports = class WebsocketServer extends BaseServer { this.implementation = new WebSocket.Server(options); - this.server.server.on("upgrade", (req, sock, head) => { - if (!this.implementation.shouldHandle(req)) { - return; - } - - this.implementation.handleUpgrade(req, sock, head, (connection) => { - this.implementation.emit("connection", connection, req); - }); - }); + /** @type {import("http").Server} */ + (this.server.server).on( + "upgrade", + /** + * @param {import("http").IncomingMessage} req + * @param {import("stream").Duplex} sock + * @param {Buffer} head + */ + (req, sock, head) => { + if (!this.implementation.shouldHandle(req)) { + return; + } - this.implementation.on("error", (err) => { - this.server.logger.error(err.message); - }); + this.implementation.handleUpgrade(req, sock, head, (connection) => { + this.implementation.emit("connection", connection, req); + }); + } + ); + + this.implementation.on( + "error", + /** + * @param {Error} err + */ + (err) => { + this.server.logger.error(err.message); + } + ); const interval = setInterval(() => { - this.clients.forEach((client) => { - if (client.isAlive === false) { - client.terminate(); - - return; + this.clients.forEach( + /** + * @param {ClientConnection} client + */ + (client) => { + if (client.isAlive === false) { + client.terminate(); + + return; + } + + client.isAlive = false; + client.ping(() => {}); } - - client.isAlive = false; - client.ping(() => {}); - }); + ); }, WebsocketServer.heartbeatInterval); - this.implementation.on("connection", (client) => { - this.clients.push(client); + this.implementation.on( + "connection", + /** + * @param {ClientConnection} client + */ + (client) => { + this.clients.push(client); - client.isAlive = true; - - client.on("pong", () => { client.isAlive = true; - }); - client.on("close", () => { - this.clients.splice(this.clients.indexOf(client), 1); - }); - }); + client.on("pong", () => { + client.isAlive = true; + }); + + client.on("close", () => { + this.clients.splice(this.clients.indexOf(client), 1); + }); + } + ); this.implementation.on("close", () => { clearInterval(interval); diff --git a/package-lock.json b/package-lock.json index 1f3b064d82..baa7f17e39 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,9 @@ "version": "4.6.0", "license": "MIT", "dependencies": { + "@types/bonjour": "^3.5.9", + "@types/connect-history-api-fallback": "^1.3.5", + "@types/serve-index": "^1.9.1", "ansi-html-community": "^0.0.8", "bonjour": "^3.5.0", "chokidar": "^3.5.2", @@ -32,7 +35,7 @@ "spdy": "^4.0.2", "strip-ansi": "^7.0.0", "url": "^0.11.0", - "webpack-dev-middleware": "^5.2.1", + "webpack-dev-middleware": "^5.3.0", "ws": "^8.1.0" }, "bin": { @@ -48,6 +51,10 @@ "@babel/runtime": "^7.14.5", "@commitlint/cli": "^15.0.0", "@commitlint/config-conventional": "^15.0.0", + "@types/compression": "^1.7.2", + "@types/default-gateway": "^3.0.1", + "@types/sockjs": "^0.3.33", + "@types/ws": "^8.2.2", "acorn": "^8.2.4", "babel-jest": "^27.4.4", "babel-loader": "^8.2.2", @@ -3211,6 +3218,55 @@ "@babel/types": "^7.3.0" } }, + "node_modules/@types/body-parser": { + "version": "1.19.2", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.2.tgz", + "integrity": "sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g==", + "dependencies": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "node_modules/@types/bonjour": { + "version": "3.5.9", + "resolved": "https://registry.npmjs.org/@types/bonjour/-/bonjour-3.5.9.tgz", + "integrity": "sha512-VkZUiYevvtPyFu5XtpYw9a8moCSzxgjs5PAFF4yXjA7eYHvzBlXe+eJdqBBNWWVzI1r7Ki0KxMYvaQuhm+6f5A==", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/compression": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/@types/compression/-/compression-1.7.2.tgz", + "integrity": "sha512-lwEL4M/uAGWngWFLSG87ZDr2kLrbuR8p7X+QZB1OQlT+qkHsCPDVFnHPyXf4Vyl4yDDorNY+mAhosxkCvppatg==", + "dev": true, + "dependencies": { + "@types/express": "*" + } + }, + "node_modules/@types/connect": { + "version": "3.4.35", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.35.tgz", + "integrity": "sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ==", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/connect-history-api-fallback": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/connect-history-api-fallback/-/connect-history-api-fallback-1.3.5.tgz", + "integrity": "sha512-h8QJa8xSb1WD4fpKBDcATDNGXghFj6/3GRWG6dhmRcu0RX1Ubasur2Uvx5aeEwlf0MwblEC2bMzzMQntxnw/Cw==", + "dependencies": { + "@types/express-serve-static-core": "*", + "@types/node": "*" + } + }, + "node_modules/@types/default-gateway": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/default-gateway/-/default-gateway-3.0.1.tgz", + "integrity": "sha512-tpu0hp+AOIzwdAHyZPzLE5pCf9uT0pb+xZ76T4S7MrY2YTVq918Q7Q2VQ3KCVQqYxM7nxuCK/SL3X97jBEIeKQ==", + "dev": true + }, "node_modules/@types/eslint": { "version": "8.2.0", "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.2.0.tgz", @@ -3234,6 +3290,27 @@ "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.50.tgz", "integrity": "sha512-C6N5s2ZFtuZRj54k2/zyRhNDjJwwcViAM3Nbm8zjBpbqAdZ00mr0CFxvSKeO8Y/e03WVFLpQMdHYVfUd6SB+Hw==" }, + "node_modules/@types/express": { + "version": "4.17.13", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.13.tgz", + "integrity": "sha512-6bSZTPaTIACxn48l50SR+axgrqm6qXFIxrdAKaG6PaJk3+zuUr35hBlgT7vOmJcum+OEaIBLtHV/qloEAFITeA==", + "dependencies": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^4.17.18", + "@types/qs": "*", + "@types/serve-static": "*" + } + }, + "node_modules/@types/express-serve-static-core": { + "version": "4.17.26", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.26.tgz", + "integrity": "sha512-zeu3tpouA043RHxW0gzRxwCHchMgftE8GArRsvYT0ByDMbn19olQHx5jLue0LxWY6iYtXb7rXmuVtSkhy9YZvQ==", + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*" + } + }, "node_modules/@types/graceful-fs": { "version": "4.1.5", "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.5.tgz", @@ -3292,6 +3369,11 @@ "integrity": "sha1-7ihweulOEdK4J7y+UnC86n8+ce4=", "dev": true }, + "node_modules/@types/mime": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.2.tgz", + "integrity": "sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw==" + }, "node_modules/@types/minimist": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.2.tgz", @@ -3321,11 +3403,47 @@ "integrity": "sha512-ekoj4qOQYp7CvjX8ZDBgN86w3MqQhLE1hczEJbEIjgFEumDy+na/4AJAbLXfgEWFNB2pKadM5rPFtuSGMWK7xA==", "dev": true }, + "node_modules/@types/qs": { + "version": "6.9.7", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.7.tgz", + "integrity": "sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==" + }, + "node_modules/@types/range-parser": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.4.tgz", + "integrity": "sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==" + }, "node_modules/@types/retry": { "version": "0.12.1", "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.1.tgz", "integrity": "sha512-xoDlM2S4ortawSWORYqsdU+2rxdh4LRW9ytc3zmT37RIKQh6IHyKwwtKhKis9ah8ol07DCkZxPt8BBvPjC6v4g==" }, + "node_modules/@types/serve-index": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/@types/serve-index/-/serve-index-1.9.1.tgz", + "integrity": "sha512-d/Hs3nWDxNL2xAczmOVZNj92YZCS6RGxfBPjKzuu/XirCgXdpKEb88dYNbrYGint6IVWLNP+yonwVAuRC0T2Dg==", + "dependencies": { + "@types/express": "*" + } + }, + "node_modules/@types/serve-static": { + "version": "1.13.10", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.13.10.tgz", + "integrity": "sha512-nCkHGI4w7ZgAdNkrEu0bv+4xNV/XDqW+DydknebMOQwkpDGx8G+HTlj7R7ABI8i8nKxVw0wtKPi1D+lPOkh4YQ==", + "dependencies": { + "@types/mime": "^1", + "@types/node": "*" + } + }, + "node_modules/@types/sockjs": { + "version": "0.3.33", + "resolved": "https://registry.npmjs.org/@types/sockjs/-/sockjs-0.3.33.tgz", + "integrity": "sha512-f0KEEe05NvUnat+boPTZ0dgaLZ4SfSouXUgv5noUiefG2ajgKjmETo9ZJyuqsl7dfl2aHlLJUiki6B4ZYldiiw==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/source-list-map": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/@types/source-list-map/-/source-list-map-0.1.2.tgz", @@ -3405,6 +3523,15 @@ "node": ">=0.10.0" } }, + "node_modules/@types/ws": { + "version": "8.2.2", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.2.2.tgz", + "integrity": "sha512-NOn5eIcgWLOo6qW8AcuLZ7G8PycXu0xTxxkS6Q18VWFxgPUSOwV0pBj2a/4viNZVu25i7RIB7GttdkAIUUXOOg==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/yargs": { "version": "16.0.4", "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", @@ -18270,6 +18397,55 @@ "@babel/types": "^7.3.0" } }, + "@types/body-parser": { + "version": "1.19.2", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.2.tgz", + "integrity": "sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g==", + "requires": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "@types/bonjour": { + "version": "3.5.9", + "resolved": "https://registry.npmjs.org/@types/bonjour/-/bonjour-3.5.9.tgz", + "integrity": "sha512-VkZUiYevvtPyFu5XtpYw9a8moCSzxgjs5PAFF4yXjA7eYHvzBlXe+eJdqBBNWWVzI1r7Ki0KxMYvaQuhm+6f5A==", + "requires": { + "@types/node": "*" + } + }, + "@types/compression": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/@types/compression/-/compression-1.7.2.tgz", + "integrity": "sha512-lwEL4M/uAGWngWFLSG87ZDr2kLrbuR8p7X+QZB1OQlT+qkHsCPDVFnHPyXf4Vyl4yDDorNY+mAhosxkCvppatg==", + "dev": true, + "requires": { + "@types/express": "*" + } + }, + "@types/connect": { + "version": "3.4.35", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.35.tgz", + "integrity": "sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ==", + "requires": { + "@types/node": "*" + } + }, + "@types/connect-history-api-fallback": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/connect-history-api-fallback/-/connect-history-api-fallback-1.3.5.tgz", + "integrity": "sha512-h8QJa8xSb1WD4fpKBDcATDNGXghFj6/3GRWG6dhmRcu0RX1Ubasur2Uvx5aeEwlf0MwblEC2bMzzMQntxnw/Cw==", + "requires": { + "@types/express-serve-static-core": "*", + "@types/node": "*" + } + }, + "@types/default-gateway": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/default-gateway/-/default-gateway-3.0.1.tgz", + "integrity": "sha512-tpu0hp+AOIzwdAHyZPzLE5pCf9uT0pb+xZ76T4S7MrY2YTVq918Q7Q2VQ3KCVQqYxM7nxuCK/SL3X97jBEIeKQ==", + "dev": true + }, "@types/eslint": { "version": "8.2.0", "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.2.0.tgz", @@ -18293,6 +18469,27 @@ "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.50.tgz", "integrity": "sha512-C6N5s2ZFtuZRj54k2/zyRhNDjJwwcViAM3Nbm8zjBpbqAdZ00mr0CFxvSKeO8Y/e03WVFLpQMdHYVfUd6SB+Hw==" }, + "@types/express": { + "version": "4.17.13", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.13.tgz", + "integrity": "sha512-6bSZTPaTIACxn48l50SR+axgrqm6qXFIxrdAKaG6PaJk3+zuUr35hBlgT7vOmJcum+OEaIBLtHV/qloEAFITeA==", + "requires": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^4.17.18", + "@types/qs": "*", + "@types/serve-static": "*" + } + }, + "@types/express-serve-static-core": { + "version": "4.17.26", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.26.tgz", + "integrity": "sha512-zeu3tpouA043RHxW0gzRxwCHchMgftE8GArRsvYT0ByDMbn19olQHx5jLue0LxWY6iYtXb7rXmuVtSkhy9YZvQ==", + "requires": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*" + } + }, "@types/graceful-fs": { "version": "4.1.5", "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.5.tgz", @@ -18351,6 +18548,11 @@ "integrity": "sha1-7ihweulOEdK4J7y+UnC86n8+ce4=", "dev": true }, + "@types/mime": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.2.tgz", + "integrity": "sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw==" + }, "@types/minimist": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.2.tgz", @@ -18380,11 +18582,47 @@ "integrity": "sha512-ekoj4qOQYp7CvjX8ZDBgN86w3MqQhLE1hczEJbEIjgFEumDy+na/4AJAbLXfgEWFNB2pKadM5rPFtuSGMWK7xA==", "dev": true }, + "@types/qs": { + "version": "6.9.7", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.7.tgz", + "integrity": "sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==" + }, + "@types/range-parser": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.4.tgz", + "integrity": "sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==" + }, "@types/retry": { "version": "0.12.1", "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.1.tgz", "integrity": "sha512-xoDlM2S4ortawSWORYqsdU+2rxdh4LRW9ytc3zmT37RIKQh6IHyKwwtKhKis9ah8ol07DCkZxPt8BBvPjC6v4g==" }, + "@types/serve-index": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/@types/serve-index/-/serve-index-1.9.1.tgz", + "integrity": "sha512-d/Hs3nWDxNL2xAczmOVZNj92YZCS6RGxfBPjKzuu/XirCgXdpKEb88dYNbrYGint6IVWLNP+yonwVAuRC0T2Dg==", + "requires": { + "@types/express": "*" + } + }, + "@types/serve-static": { + "version": "1.13.10", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.13.10.tgz", + "integrity": "sha512-nCkHGI4w7ZgAdNkrEu0bv+4xNV/XDqW+DydknebMOQwkpDGx8G+HTlj7R7ABI8i8nKxVw0wtKPi1D+lPOkh4YQ==", + "requires": { + "@types/mime": "^1", + "@types/node": "*" + } + }, + "@types/sockjs": { + "version": "0.3.33", + "resolved": "https://registry.npmjs.org/@types/sockjs/-/sockjs-0.3.33.tgz", + "integrity": "sha512-f0KEEe05NvUnat+boPTZ0dgaLZ4SfSouXUgv5noUiefG2ajgKjmETo9ZJyuqsl7dfl2aHlLJUiki6B4ZYldiiw==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, "@types/source-list-map": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/@types/source-list-map/-/source-list-map-0.1.2.tgz", @@ -18461,6 +18699,15 @@ } } }, + "@types/ws": { + "version": "8.2.2", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.2.2.tgz", + "integrity": "sha512-NOn5eIcgWLOo6qW8AcuLZ7G8PycXu0xTxxkS6Q18VWFxgPUSOwV0pBj2a/4viNZVu25i7RIB7GttdkAIUUXOOg==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, "@types/yargs": { "version": "16.0.4", "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", diff --git a/package.json b/package.json index 6f9737a854..670d28ae82 100644 --- a/package.json +++ b/package.json @@ -4,10 +4,12 @@ "description": "Serves a webpack app. Updates the browser on changes.", "bin": "bin/webpack-dev-server.js", "main": "lib/Server.js", + "types": "types/lib/Server.d.ts", "files": [ "bin", "lib", - "client" + "client", + "types" ], "engines": { "node": ">= 12.13.0" @@ -15,14 +17,15 @@ "scripts": { "fmt:check": "prettier \"{**/*,*}.{js,json,md,yml,css,ts}\" --list-different", "lint:js": "eslint . --cache", - "lint:type": "tsc --noEmit", - "lint": "npm-run-all lint:js lint:type fmt:check", + "lint:types": "tsc --pretty --noEmit", + "lint": "npm-run-all -p \"fmt:**\" \"lint:**\"", "fmt": "npm run fmt:check -- --write", "fix:js": "npm run lint:js -- --fix", "fix": "npm-run-all fix:js fmt", "commitlint": "commitlint --from=master", - "build": "npm-run-all build:client", "build:client": "rimraf ./client/* && babel client-src/ --out-dir client/ --ignore \"client-src/webpack.config.js\" --ignore \"client-src/modules\" && webpack --config client-src/webpack.config.js", + "build:types": "rimraf ./types/* && tsc --declaration --emitDeclarationOnly --outDir types && node ./scripts/extend-webpack-types.js && prettier \"types/**/*.ts\" --write && prettier \"types/**/*.ts\" --write", + "build": "npm-run-all -p \"build:**\"", "test:only": "jest", "test:coverage": "npm run test:only -- --coverage", "test:watch": "npm run test:coverage --watch", @@ -32,6 +35,11 @@ "release": "standard-version" }, "dependencies": { + "@types/bonjour": "^3.5.9", + "@types/connect-history-api-fallback": "^1.3.5", + "@types/serve-index": "^1.9.1", + "@types/sockjs": "^0.3.33", + "@types/ws": "^8.2.2", "ansi-html-community": "^0.0.8", "bonjour": "^3.5.0", "chokidar": "^3.5.2", @@ -55,7 +63,7 @@ "spdy": "^4.0.2", "strip-ansi": "^7.0.0", "url": "^0.11.0", - "webpack-dev-middleware": "^5.2.1", + "webpack-dev-middleware": "^5.3.0", "ws": "^8.1.0" }, "devDependencies": { @@ -68,6 +76,8 @@ "@babel/runtime": "^7.14.5", "@commitlint/cli": "^15.0.0", "@commitlint/config-conventional": "^15.0.0", + "@types/compression": "^1.7.2", + "@types/default-gateway": "^3.0.1", "acorn": "^8.2.4", "babel-jest": "^27.4.4", "babel-loader": "^8.2.2", diff --git a/scripts/extend-webpack-types.js b/scripts/extend-webpack-types.js new file mode 100644 index 0000000000..0350870eac --- /dev/null +++ b/scripts/extend-webpack-types.js @@ -0,0 +1,26 @@ +"use strict"; + +const fs = require("fs"); +const path = require("path"); + +async function extendTypes() { + const typesPath = path.resolve(__dirname, "../types/lib/Server.d.ts"); + const content = await fs.promises.readFile(typesPath, "utf-8"); + const newContent = `${content} +// DO NOT REMOVE THIS! +type DevServerConfiguration = Configuration; +declare module "webpack" { + interface Configuration { + /** + * Can be used to configure the behaviour of webpack-dev-server when + * the webpack config is passed to webpack-dev-server CLI. + */ + devServer?: DevServerConfiguration | undefined; + } +} +`; + + await fs.promises.writeFile(typesPath, newContent); +} + +extendTypes(); diff --git a/test/e2e/api.test.js b/test/e2e/api.test.js index 41d268240e..085f6dcbb3 100644 --- a/test/e2e/api.test.js +++ b/test/e2e/api.test.js @@ -329,10 +329,7 @@ describe("API", () => { it(`should log warning when the "port" and "host" options from options different from arguments ('listen' method)`, async () => { const compiler = webpack(config); const devServerOptions = { port: 9999, host: "127.0.0.2" }; - const server = new Server(devServerOptions, compiler); - const warnSpy = jest.fn(); - const getInfrastructureLoggerSpy = jest .spyOn(compiler, "getInfrastructureLogger") .mockImplementation(() => { @@ -342,6 +339,7 @@ describe("API", () => { log: () => {}, }; }); + const server = new Server(devServerOptions, compiler); await new Promise((resolve, reject) => { server.listen(port, "127.0.0.1", (error) => { diff --git a/tsconfig.json b/tsconfig.json index f2f7848566..83e104c5f7 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -5,12 +5,14 @@ "lib": ["es2017", "dom"], "allowJs": true, "checkJs": true, - "noEmit": true, - "strict": false, - "noImplicitThis": true, - "alwaysStrict": true, + "strict": true, "types": ["node"], - "esModuleInterop": true + "resolveJsonModule": true, + "allowSyntheticDefaultImports": true }, - "include": ["bin/webpack-dev-server.js"] + "include": [ + "./bin/**/*", + // "./client-src/**/*", + "./lib/**/*" + ] } diff --git a/types/bin/cli-flags.d.ts b/types/bin/cli-flags.d.ts new file mode 100644 index 0000000000..b87ad93353 --- /dev/null +++ b/types/bin/cli-flags.d.ts @@ -0,0 +1,934 @@ +declare const _exports: { + "allowed-hosts": { + configs: ( + | { + type: string; + multiple: boolean; + description: string; + path: string; + } + | { + description: string; + multiple: boolean; + path: string; + type: string; + values: string[]; + } + )[]; + description: string; + multiple: boolean; + simpleType: string; + }; + "allowed-hosts-reset": { + configs: { + type: string; + multiple: boolean; + description: string; + path: string; + }[]; + description: string; + simpleType: string; + multiple: boolean; + }; + bonjour: { + configs: { + type: string; + multiple: boolean; + description: string; + path: string; + }[]; + description: string; + negatedDescription: string; + simpleType: string; + multiple: boolean; + }; + client: { + configs: { + description: string; + multiple: boolean; + path: string; + type: string; + values: boolean[]; + }[]; + description: string; + multiple: boolean; + simpleType: string; + }; + "client-logging": { + configs: { + type: string; + values: string[]; + multiple: boolean; + description: string; + path: string; + }[]; + description: string; + simpleType: string; + multiple: boolean; + }; + "client-overlay": { + configs: { + type: string; + multiple: boolean; + description: string; + path: string; + }[]; + description: string; + negatedDescription: string; + simpleType: string; + multiple: boolean; + }; + "client-overlay-errors": { + configs: { + type: string; + multiple: boolean; + description: string; + path: string; + }[]; + description: string; + simpleType: string; + multiple: boolean; + }; + "client-overlay-warnings": { + configs: { + type: string; + multiple: boolean; + description: string; + path: string; + }[]; + description: string; + simpleType: string; + multiple: boolean; + }; + "client-progress": { + configs: { + type: string; + multiple: boolean; + description: string; + path: string; + }[]; + description: string; + negatedDescription: string; + simpleType: string; + multiple: boolean; + }; + "client-reconnect": { + configs: { + type: string; + multiple: boolean; + description: string; + path: string; + }[]; + description: string; + negatedDescription: string; + simpleType: string; + multiple: boolean; + }; + "client-web-socket-transport": { + configs: ( + | { + type: string; + values: string[]; + multiple: boolean; + description: string; + path: string; + } + | { + type: string; + multiple: boolean; + description: string; + path: string; + } + )[]; + description: string; + simpleType: string; + multiple: boolean; + }; + "client-web-socket-url": { + configs: { + type: string; + multiple: boolean; + description: string; + path: string; + }[]; + description: string; + simpleType: string; + multiple: boolean; + }; + "client-web-socket-url-hostname": { + configs: { + type: string; + multiple: boolean; + description: string; + path: string; + }[]; + description: string; + simpleType: string; + multiple: boolean; + }; + "client-web-socket-url-password": { + configs: { + type: string; + multiple: boolean; + description: string; + path: string; + }[]; + description: string; + simpleType: string; + multiple: boolean; + }; + "client-web-socket-url-pathname": { + configs: { + type: string; + multiple: boolean; + description: string; + path: string; + }[]; + description: string; + simpleType: string; + multiple: boolean; + }; + "client-web-socket-url-port": { + configs: { + type: string; + multiple: boolean; + description: string; + path: string; + }[]; + description: string; + simpleType: string; + multiple: boolean; + }; + "client-web-socket-url-protocol": { + configs: ( + | { + description: string; + multiple: boolean; + path: string; + type: string; + values: string[]; + } + | { + description: string; + multiple: boolean; + path: string; + type: string; + } + )[]; + description: string; + multiple: boolean; + simpleType: string; + }; + "client-web-socket-url-username": { + configs: { + type: string; + multiple: boolean; + description: string; + path: string; + }[]; + description: string; + simpleType: string; + multiple: boolean; + }; + compress: { + configs: { + type: string; + multiple: boolean; + description: string; + path: string; + }[]; + description: string; + negatedDescription: string; + simpleType: string; + multiple: boolean; + }; + "history-api-fallback": { + configs: { + type: string; + multiple: boolean; + description: string; + path: string; + }[]; + description: string; + simpleType: string; + multiple: boolean; + }; + host: { + configs: ( + | { + description: string; + multiple: boolean; + path: string; + type: string; + values: string[]; + } + | { + description: string; + multiple: boolean; + path: string; + type: string; + } + )[]; + description: string; + simpleType: string; + multiple: boolean; + }; + hot: { + configs: ( + | { + type: string; + multiple: boolean; + description: string; + path: string; + } + | { + type: string; + values: string[]; + multiple: boolean; + description: string; + path: string; + } + )[]; + description: string; + negatedDescription: string; + simpleType: string; + multiple: boolean; + }; + http2: { + configs: { + type: string; + multiple: boolean; + description: string; + path: string; + }[]; + description: string; + negatedDescription: string; + simpleType: string; + multiple: boolean; + }; + https: { + configs: { + type: string; + multiple: boolean; + description: string; + path: string; + }[]; + description: string; + negatedDescription: string; + simpleType: string; + multiple: boolean; + }; + "https-ca": { + configs: { + type: string; + multiple: boolean; + description: string; + path: string; + }[]; + description: string; + simpleType: string; + multiple: boolean; + }; + "https-ca-reset": { + configs: { + description: string; + multiple: boolean; + path: string; + type: string; + }[]; + description: string; + multiple: boolean; + simpleType: string; + }; + "https-cacert": { + configs: { + type: string; + multiple: boolean; + description: string; + path: string; + }[]; + description: string; + simpleType: string; + multiple: boolean; + }; + "https-cacert-reset": { + configs: { + description: string; + multiple: boolean; + path: string; + type: string; + }[]; + description: string; + multiple: boolean; + simpleType: string; + }; + "https-cert": { + configs: { + type: string; + multiple: boolean; + description: string; + path: string; + }[]; + description: string; + simpleType: string; + multiple: boolean; + }; + "https-cert-reset": { + configs: { + description: string; + multiple: boolean; + path: string; + type: string; + }[]; + description: string; + multiple: boolean; + simpleType: string; + }; + "https-crl": { + configs: { + description: string; + multiple: boolean; + path: string; + type: string; + }[]; + description: string; + multiple: boolean; + simpleType: string; + }; + "https-crl-reset": { + configs: { + description: string; + multiple: boolean; + path: string; + type: string; + }[]; + description: string; + multiple: boolean; + simpleType: string; + }; + "https-key": { + configs: { + type: string; + multiple: boolean; + description: string; + path: string; + }[]; + description: string; + simpleType: string; + multiple: boolean; + }; + "https-key-reset": { + configs: { + description: string; + multiple: boolean; + path: string; + type: string; + }[]; + description: string; + multiple: boolean; + simpleType: string; + }; + "https-passphrase": { + configs: { + type: string; + multiple: boolean; + description: string; + path: string; + }[]; + description: string; + simpleType: string; + multiple: boolean; + }; + "https-pfx": { + configs: { + type: string; + multiple: boolean; + description: string; + path: string; + }[]; + description: string; + simpleType: string; + multiple: boolean; + }; + "https-pfx-reset": { + configs: { + description: string; + multiple: boolean; + path: string; + type: string; + }[]; + description: string; + multiple: boolean; + simpleType: string; + }; + "https-request-cert": { + configs: { + type: string; + multiple: boolean; + description: string; + path: string; + }[]; + description: string; + negatedDescription: string; + simpleType: string; + multiple: boolean; + }; + ipc: { + configs: ( + | { + type: string; + multiple: boolean; + description: string; + path: string; + } + | { + type: string; + values: boolean[]; + multiple: boolean; + description: string; + path: string; + } + )[]; + description: string; + simpleType: string; + multiple: boolean; + }; + "live-reload": { + configs: { + type: string; + multiple: boolean; + description: string; + path: string; + }[]; + description: string; + negatedDescription: string; + simpleType: string; + multiple: boolean; + }; + "magic-html": { + configs: { + type: string; + multiple: boolean; + description: string; + path: string; + }[]; + description: string; + negatedDescription: string; + simpleType: string; + multiple: boolean; + }; + open: { + configs: { + type: string; + multiple: boolean; + description: string; + path: string; + }[]; + description: string; + negatedDescription: string; + simpleType: string; + multiple: boolean; + }; + "open-app": { + configs: { + type: string; + multiple: boolean; + description: string; + path: string; + }[]; + description: string; + simpleType: string; + multiple: boolean; + }; + "open-app-name": { + configs: { + type: string; + multiple: boolean; + description: string; + path: string; + }[]; + description: string; + simpleType: string; + multiple: boolean; + }; + "open-app-name-reset": { + configs: { + type: string; + multiple: boolean; + description: string; + path: string; + }[]; + description: string; + simpleType: string; + multiple: boolean; + }; + "open-reset": { + configs: { + type: string; + multiple: boolean; + description: string; + path: string; + }[]; + description: string; + simpleType: string; + multiple: boolean; + }; + "open-target": { + configs: { + type: string; + multiple: boolean; + description: string; + path: string; + }[]; + description: string; + negatedDescription: string; + simpleType: string; + multiple: boolean; + }; + "open-target-reset": { + configs: { + type: string; + multiple: boolean; + description: string; + path: string; + }[]; + description: string; + simpleType: string; + multiple: boolean; + }; + port: { + configs: ( + | { + type: string; + multiple: boolean; + description: string; + path: string; + } + | { + type: string; + values: string[]; + multiple: boolean; + description: string; + path: string; + } + )[]; + description: string; + simpleType: string; + multiple: boolean; + }; + "server-options-ca": { + configs: { + description: string; + multiple: boolean; + path: string; + type: string; + }[]; + description: string; + multiple: boolean; + simpleType: string; + }; + "server-options-ca-reset": { + configs: { + description: string; + multiple: boolean; + path: string; + type: string; + }[]; + description: string; + multiple: boolean; + simpleType: string; + }; + "server-options-cacert": { + configs: { + description: string; + multiple: boolean; + path: string; + type: string; + }[]; + description: string; + multiple: boolean; + simpleType: string; + }; + "server-options-cacert-reset": { + configs: { + description: string; + multiple: boolean; + path: string; + type: string; + }[]; + description: string; + multiple: boolean; + simpleType: string; + }; + "server-options-cert": { + configs: { + description: string; + multiple: boolean; + path: string; + type: string; + }[]; + description: string; + multiple: boolean; + simpleType: string; + }; + "server-options-cert-reset": { + configs: { + description: string; + multiple: boolean; + path: string; + type: string; + }[]; + description: string; + multiple: boolean; + simpleType: string; + }; + "server-options-crl": { + configs: { + description: string; + multiple: boolean; + path: string; + type: string; + }[]; + description: string; + multiple: boolean; + simpleType: string; + }; + "server-options-crl-reset": { + configs: { + description: string; + multiple: boolean; + path: string; + type: string; + }[]; + description: string; + multiple: boolean; + simpleType: string; + }; + "server-options-key": { + configs: { + description: string; + multiple: boolean; + path: string; + type: string; + }[]; + description: string; + multiple: boolean; + simpleType: string; + }; + "server-options-key-reset": { + configs: { + description: string; + multiple: boolean; + path: string; + type: string; + }[]; + description: string; + multiple: boolean; + simpleType: string; + }; + "server-options-passphrase": { + configs: { + description: string; + multiple: boolean; + path: string; + type: string; + }[]; + description: string; + multiple: boolean; + simpleType: string; + }; + "server-options-pfx": { + configs: { + description: string; + multiple: boolean; + path: string; + type: string; + }[]; + description: string; + multiple: boolean; + simpleType: string; + }; + "server-options-pfx-reset": { + configs: { + description: string; + multiple: boolean; + path: string; + type: string; + }[]; + description: string; + multiple: boolean; + simpleType: string; + }; + "server-options-request-cert": { + configs: { + description: string; + multiple: boolean; + path: string; + type: string; + }[]; + description: string; + multiple: boolean; + simpleType: string; + }; + "server-type": { + configs: { + description: string; + multiple: boolean; + path: string; + type: string; + values: string[]; + }[]; + description: string; + multiple: boolean; + simpleType: string; + }; + static: { + configs: { + type: string; + multiple: boolean; + description: string; + path: string; + }[]; + description: string; + simpleType: string; + multiple: boolean; + }; + "static-directory": { + configs: { + type: string; + multiple: boolean; + description: string; + path: string; + }[]; + description: string; + simpleType: string; + multiple: boolean; + }; + "static-public-path": { + configs: { + type: string; + multiple: boolean; + description: string; + path: string; + }[]; + description: string; + simpleType: string; + multiple: boolean; + }; + "static-public-path-reset": { + configs: { + type: string; + multiple: boolean; + description: string; + path: string; + }[]; + description: string; + simpleType: string; + multiple: boolean; + }; + "static-reset": { + configs: { + type: string; + multiple: boolean; + description: string; + path: string; + }[]; + description: string; + simpleType: string; + multiple: boolean; + }; + "static-serve-index": { + configs: { + type: string; + multiple: boolean; + description: string; + path: string; + }[]; + description: string; + negatedDescription: string; + simpleType: string; + multiple: boolean; + }; + "static-watch": { + configs: { + type: string; + multiple: boolean; + description: string; + path: string; + }[]; + description: string; + negatedDescription: string; + simpleType: string; + multiple: boolean; + }; + "watch-files": { + configs: { + type: string; + multiple: boolean; + description: string; + path: string; + }[]; + description: string; + simpleType: string; + multiple: boolean; + }; + "watch-files-reset": { + configs: { + type: string; + multiple: boolean; + description: string; + path: string; + }[]; + description: string; + simpleType: string; + multiple: boolean; + }; + "web-socket-server": { + configs: ( + | { + description: string; + multiple: boolean; + path: string; + type: string; + values: boolean[]; + } + | { + description: string; + multiple: boolean; + path: string; + type: string; + values: string[]; + } + | { + description: string; + multiple: boolean; + path: string; + type: string; + } + )[]; + description: string; + simpleType: string; + multiple: boolean; + }; + "web-socket-server-type": { + configs: ( + | { + description: string; + multiple: boolean; + path: string; + type: string; + values: string[]; + } + | { + description: string; + multiple: boolean; + path: string; + type: string; + } + )[]; + description: string; + simpleType: string; + multiple: boolean; + }; +}; +export = _exports; diff --git a/types/bin/process-arguments.d.ts b/types/bin/process-arguments.d.ts new file mode 100644 index 0000000000..6e4872f097 --- /dev/null +++ b/types/bin/process-arguments.d.ts @@ -0,0 +1,50 @@ +export = processArguments; +/** + * @param {Record} args object of arguments + * @param {any} config configuration + * @param {Record} values object with values + * @returns {Problem[] | null} problems or null for success + */ +declare function processArguments( + args: Record, + config: any, + values: Record< + string, + string | number | boolean | RegExp | (string | number | boolean | RegExp)[] + > +): Problem[] | null; +declare namespace processArguments { + export { ProblemType, Problem, LocalProblem, ArgumentConfig, Argument }; +} +type Argument = { + description: string; + simpleType: "string" | "number" | "boolean"; + multiple: boolean; + configs: ArgumentConfig[]; +}; +type Problem = { + type: ProblemType; + path: string; + argument: string; + value?: any | undefined; + index?: number | undefined; + expected?: string | undefined; +}; +type ProblemType = + | "unknown-argument" + | "unexpected-non-array-in-path" + | "unexpected-non-object-in-path" + | "multiple-values-unexpected" + | "invalid-value"; +type LocalProblem = { + type: ProblemType; + path: string; + expected?: string | undefined; +}; +type ArgumentConfig = { + description: string; + path: string; + multiple: boolean; + type: "enum" | "string" | "path" | "number" | "boolean" | "RegExp" | "reset"; + values?: any[] | undefined; +}; diff --git a/types/bin/webpack-dev-server.d.ts b/types/bin/webpack-dev-server.d.ts new file mode 100644 index 0000000000..d3ec8a3a55 --- /dev/null +++ b/types/bin/webpack-dev-server.d.ts @@ -0,0 +1,27 @@ +#!/usr/bin/env node +export type CliOption = { + /** + * display name + */ + name: string; + /** + * npm package name + */ + package: string; + /** + * name of the executable file + */ + binName: string; + /** + * currently installed? + */ + installed: boolean; + /** + * homepage + */ + url: string; + /** + * preprocessor + */ + preprocess: Function; +}; diff --git a/types/lib/Server.d.ts b/types/lib/Server.d.ts new file mode 100644 index 0000000000..82c544247b --- /dev/null +++ b/types/lib/Server.d.ts @@ -0,0 +1,3385 @@ +/// +export = Server; +declare class Server { + static get cli(): { + readonly getArguments: () => { + "allowed-hosts": { + configs: ( + | { + type: string; + multiple: boolean; + description: string; + path: string; + } + | { + description: string; + multiple: boolean; + path: string; + type: string; + values: string[]; + } + )[]; + description: string; + /** @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").Bonjour} Bonjour */ + /** @typedef {import("bonjour").BonjourOptions} 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} DevMiddlewareOptions + */ + /** + * @template Request, Response + * @typedef {import("webpack-dev-middleware").Context} 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?: string | RegExp | string[], 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?: string | RegExp | string[], 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} [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 + */ + /** + * @typedef {{ [url: string]: string | HttpProxyMiddlewareOptions }} ProxyConfigMap + */ + /** + * @typedef {HttpProxyMiddlewareOptions[]} ProxyArray + */ + /** + * @callback ByPass + * @param {Request} req + * @param {Response} res + * @param {ProxyConfigArray} proxyConfig + */ + /** + * @typedef {{ path?: string | string[] | undefined, context?: string | string[] | HttpProxyMiddlewareOptionsFilter | undefined } & HttpProxyMiddlewareOptions & ByPass} ProxyConfigArray + */ + /** + * @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} 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} [devMiddleware] + * @property {boolean} [compress] + * @property {boolean} [magicHtml] + * @property {"auto" | "all" | string | string[]} [allowedHosts] + * @property {boolean | ConnectHistoryApiFallbackOptions} [historyApiFallback] + * @property {boolean} [setupExitSignals] + * @property {boolean | BonjourOptions} [bonjour] + * @property {string | string[] | WatchFiles | Array} [watchFiles] + * @property {boolean | string | Static | Array} [static] + * @property {boolean | ServerOptions} [https] + * @property {boolean} [http2] + * @property {"http" | "https" | "spdy" | string | ServerConfiguration} [server] + * @property {boolean | "sockjs" | "ws" | string | WebSocketServerConfiguration} [webSocketServer] + * @property {ProxyConfigMap | ProxyConfigArray | ProxyArray} [proxy] + * @property {boolean | string | Open | Array} [open] + * @property {boolean} [setupExitSignals] + * @property {boolean | ClientConfiguration} [client] + * @property {Headers | ((req: Request, res: Response, context: DevMiddlewareContext) => Headers)} [headers] + * @property {(devServer: Server) => void} [onAfterSetupMiddleware] + * @property {(devServer: Server) => void} [onBeforeSetupMiddleware] + * @property {(devServer: Server) => void} [onListening] + * @property {(middlewares: Middleware[], devServer: Server) => Middleware[]} [setupMiddlewares] + */ + multiple: boolean; + simpleType: string; + }; + "allowed-hosts-reset": { + configs: { + type: string; + multiple: boolean; + description: string; + path: string; + }[]; + description: string; + simpleType: string; + multiple: boolean; + }; + bonjour: { + configs: { + type: string; + multiple: boolean; + description: string; + path: string; + }[]; + description: string; + negatedDescription: string; + simpleType: string; + multiple: boolean; + }; + client: { + configs: { + description: string; + multiple: boolean; + path: string; + type: string; + values: boolean[]; + }[]; + /** @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} DevMiddlewareOptions + */ + /** + * @template Request, Response + * @typedef {import("webpack-dev-middleware").Context} 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?: string | RegExp | string[], 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?: string | RegExp | string[], 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} [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 + */ + /** + * @typedef {{ [url: string]: string | HttpProxyMiddlewareOptions }} ProxyConfigMap + */ + /** + * @typedef {HttpProxyMiddlewareOptions[]} ProxyArray + */ + /** + * @callback ByPass + * @param {Request} req + * @param {Response} res + * @param {ProxyConfigArray} proxyConfig + */ + /** + * @typedef {{ path?: string | string[] | undefined, context?: string | string[] | HttpProxyMiddlewareOptionsFilter | undefined } & HttpProxyMiddlewareOptions & ByPass} ProxyConfigArray + */ + /** + * @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} 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} [devMiddleware] + * @property {boolean} [compress] + * @property {boolean} [magicHtml] + * @property {"auto" | "all" | string | string[]} [allowedHosts] + * @property {boolean | ConnectHistoryApiFallbackOptions} [historyApiFallback] + * @property {boolean} [setupExitSignals] + * @property {boolean | BonjourOptions} [bonjour] + * @property {string | string[] | WatchFiles | Array} [watchFiles] + * @property {boolean | string | Static | Array} [static] + * @property {boolean | ServerOptions} [https] + * @property {boolean} [http2] + * @property {"http" | "https" | "spdy" | string | ServerConfiguration} [server] + * @property {boolean | "sockjs" | "ws" | string | WebSocketServerConfiguration} [webSocketServer] + * @property {ProxyConfigMap | ProxyConfigArray | ProxyArray} [proxy] + * @property {boolean | string | Open | Array} [open] + * @property {boolean} [setupExitSignals] + * @property {boolean | ClientConfiguration} [client] + * @property {Headers | ((req: Request, res: Response, context: DevMiddlewareContext) => Headers)} [headers] + * @property {(devServer: Server) => void} [onAfterSetupMiddleware] + * @property {(devServer: Server) => void} [onBeforeSetupMiddleware] + * @property {(devServer: Server) => void} [onListening] + * @property {(middlewares: Middleware[], devServer: Server) => Middleware[]} [setupMiddlewares] + */ + description: string; + multiple: boolean; + simpleType: string; + }; + "client-logging": { + configs: { + type: string; + values: string[]; + multiple: boolean; + description: string; + path: string; + }[]; + description: string; + simpleType: string; + multiple: boolean; + }; + "client-overlay": { + configs: { + type: string; + multiple: boolean; + description: string; + path: string; + }[]; + description: string; + negatedDescription: string; + simpleType: string; + multiple: boolean; + }; + "client-overlay-errors": { + configs: { + type: string; + multiple: boolean; + description: string; + path: string; + }[]; + description: string; + simpleType: string; + multiple: boolean; + }; + "client-overlay-warnings": { + configs: { + type: string; + multiple: boolean; + description: string; + path: string; + }[]; + description: string; + simpleType: string; + multiple: boolean; + }; + "client-progress": { + configs: { + type: string; + multiple: boolean; + description: string; + path: string; + }[]; + description: string; + negatedDescription: string; + simpleType: string; + multiple: boolean; + }; + "client-reconnect": { + configs: { + type: string; + multiple: boolean; + description: string; + path: string; + }[]; + description: string; + negatedDescription: string; + simpleType: string; + multiple: boolean; + }; + "client-web-socket-transport": { + configs: ( + | { + type: string; + values: string[]; + multiple: boolean; + description: string; + path: string; + } + | { + type: string; + multiple: boolean; + description: string; + path: string; + } + )[]; + description: string; + simpleType: string; + multiple: boolean; + }; + "client-web-socket-url": { + configs: { + type: string; + multiple: boolean; + description: string; + path: string; + }[]; + description: string; + simpleType: string; + multiple: boolean; + }; + "client-web-socket-url-hostname": { + configs: { + type: string; + multiple: boolean; + description: string; + path: string; + }[]; + description: string; + simpleType: string; + multiple: boolean; + }; + "client-web-socket-url-password": { + configs: { + type: string; + multiple: boolean; + description: string; + path: string; + }[]; + description: string; + simpleType: string; + multiple: boolean; + }; + "client-web-socket-url-pathname": { + configs: { + type: string; + multiple: boolean; + description: string; + path: string; + }[]; + description: string; + simpleType: string; + multiple: boolean; + }; + "client-web-socket-url-port": { + configs: { + type: string; + multiple: boolean; + description: string; + path: string; + }[]; + description: string; + simpleType: string; + multiple: boolean; + }; + "client-web-socket-url-protocol": { + configs: ( + | { + description: string; + multiple: boolean; + path: string; + type: string; + values: string[]; + } + | { + description: string; + multiple: boolean; + path: string; + type: string; + } + )[]; + description: string; + multiple: boolean; + simpleType: string; + }; + "client-web-socket-url-username": { + configs: { + type: string; + multiple: boolean; + description: string; + path: string; + }[]; + description: string; + /** + * @private + * @type {string | undefined} + */ + simpleType: string; + multiple: boolean; + }; + compress: { + configs: { + type: string; + multiple: boolean; + description: string; + path: string; + }[]; + description: string; + negatedDescription: string; + simpleType: string; + multiple: boolean; + }; + "history-api-fallback": { + configs: { + type: string; + multiple: boolean; + description: string; + path: string; + }[]; + description: string; + simpleType: string; + multiple: boolean; + }; + host: { + configs: ( + | { + description: string; + multiple: boolean; + path: string; + type: string; + values: string[]; + } + | { + description: string; + multiple: boolean; + path: string; + type: string; + } + )[]; + description: string; + simpleType: string; + multiple: boolean; + }; + hot: { + configs: ( + | { + type: string; + multiple: boolean; + description: string; + path: string; + } + | { + type: string; + values: string[]; + multiple: boolean; + description: string; + path: string; + } + )[]; + description: string; + negatedDescription: string; + simpleType: string; + multiple: boolean; + }; + http2: { + configs: { + type: string; + multiple: boolean; + description: string; + path: string; + }[]; + /** + * @param {Host} hostname + * @returns {Promise} + */ + description: string; + negatedDescription: string; + simpleType: string; + multiple: boolean; + }; + https: { + configs: { + type: string; + multiple: boolean; + description: string; + path: string; + }[]; + description: string; + negatedDescription: string; + simpleType: string; + multiple: boolean; + }; + "https-ca": { + configs: { + type: string; + multiple: boolean; + description: string; + path: string; + }[]; + description: string; + simpleType: string; + multiple: boolean; + }; + "https-ca-reset": { + configs: { + description: string; + multiple: boolean; + path: string; + type: string; + }[]; + description: string; + multiple: boolean; + simpleType: string; + }; + "https-cacert": { + configs: { + type: string; + multiple: boolean; + description: string; + path: string; + }[]; + description: string; + /** + * @type {string[]} + */ + simpleType: string; + multiple: boolean; + }; + "https-cacert-reset": { + configs: { + description: string; + multiple: boolean; + path: string; + type: string; + }[]; + description: string; + multiple: boolean; + simpleType: string; + }; + "https-cert": { + configs: { + type: string; + /** @type {ClientConfiguration} */ multiple: boolean; + description: string; + path: string; + }[]; + description: string; + simpleType: string; + multiple: boolean; + }; + "https-cert-reset": { + configs: { + description: string; + multiple: boolean; + path: string; + type: string; + }[]; + description: string; + multiple: boolean; + simpleType: string; + }; + "https-crl": { + configs: { + description: string; + multiple: boolean; + path: string; + type: string; + }[]; + description: string; + multiple: boolean; + simpleType: string; + }; + "https-crl-reset": { + configs: { + description: string; + multiple: boolean; + path: string; + type: string; + }[]; + description: string; + multiple: boolean; + simpleType: string; + }; + "https-key": { + configs: { + type: string; + multiple: boolean; + description: string; + path: string; + }[]; + description: string; + simpleType: string; + multiple: boolean; + }; + "https-key-reset": { + configs: { + description: string; + multiple: boolean; + path: string; + type: string; + }[]; + description: string; + multiple: boolean; + simpleType: string; + }; + "https-passphrase": { + configs: { + type: string; + multiple: boolean; + description: string; + path: string; + }[]; + description: string; + simpleType: string; + multiple: boolean; + }; + "https-pfx": { + configs: { + type: string; + multiple: boolean; + description: string; + path: string; + }[]; + description: string; + simpleType: string; + multiple: boolean; + }; + "https-pfx-reset": { + configs: { + description: string; + multiple: boolean; + path: string; + type: string; + }[]; + description: string; + multiple: boolean; + simpleType: string; + }; + "https-request-cert": { + configs: { + type: string; + multiple: boolean; + description: string; + path: string; + }[]; + description: string; + negatedDescription: string; + simpleType: string; + multiple: boolean; + }; + ipc: { + configs: ( + | { + type: string; + multiple: boolean; + description: string; + path: string; + } + | { + type: string; + values: boolean[]; + multiple: boolean; + description: string; + path: string; + } + )[]; + description: string; + simpleType: string; + multiple: boolean; + }; + "live-reload": { + configs: { + type: string; + /** @type {Object} */ multiple: boolean; + description: string; + path: string; + }[]; + description: string; + negatedDescription: string; + simpleType: string; + multiple: boolean /** @type {any} */; + }; + "magic-html": { + configs: { + type: string; + multiple: boolean; + description: string; + path: string; + }[]; + description: string; + negatedDescription: string; + simpleType: string; + multiple: boolean; + }; + open: { + configs: { + type: string; + multiple: boolean; + description: string; + path: string; + }[]; + description: string; + negatedDescription: string; + simpleType: string; + multiple: boolean; + }; + "open-app": { + configs: { + type: string; + multiple: boolean; + description: string; + path: string; + }[]; + description: string; + simpleType: string; + multiple: boolean; + }; + "open-app-name": { + configs: { + type: string; + multiple: boolean; + description: string; + path: string; + }[]; + description: string; + simpleType: string; + multiple: boolean; + }; + "open-app-name-reset": { + configs: { + type: string; + multiple: boolean; + description: string; + path: string; + }[]; + description: string; + simpleType: string; + multiple: boolean; + }; + "open-reset": { + configs: { + type: string; + multiple: boolean; + description: string; + path: string; + }[]; + description: string; + simpleType: string; + multiple: boolean; + }; + "open-target": { + configs: { + type: string; + multiple: boolean; + description: string; + path: string; + }[]; + description: string; + negatedDescription: string; + simpleType: string; + multiple: boolean; + }; + "open-target-reset": { + configs: { + type: string; + multiple: boolean; + description: string; + path: string; + }[]; + description: string; + simpleType: string; + multiple: boolean; + }; + port: { + configs: ( + | { + type: string; + multiple: boolean; + description: string; + path: string; + } + | { + type: string; + values: string[]; + multiple: boolean; + description: string; + path: string; + } + )[]; + description: string; + simpleType: string; + multiple: boolean; + }; + "server-options-ca": { + configs: { + description: string; + multiple: boolean; + path: string; + type: string; + }[]; + description: string; + multiple: boolean; + simpleType: string; + }; + "server-options-ca-reset": { + configs: { + description: string; + multiple: boolean; + path: string; + type: string; + }[]; + description: string; + multiple: boolean; + simpleType: string; + }; + "server-options-cacert": { + configs: { + description: string; + multiple: boolean; + path: string; + type: string; + }[]; + description: string; + multiple: boolean; + simpleType: string; + }; + "server-options-cacert-reset": { + configs: { + description: string; + multiple: boolean; + path: string; + type: string; + }[]; + description: string; + multiple: boolean; + simpleType: string; + }; + "server-options-cert": { + configs: { + description: string; + multiple: boolean; + path: string; + type: string; + }[]; + description: string; + multiple: boolean; + simpleType: string; + }; + "server-options-cert-reset": { + configs: { + description: string; + multiple: boolean; + path: string; + type: string; + }[]; + description: string; + multiple: boolean; + simpleType: string; + }; + "server-options-crl": { + configs: { + description: string; + multiple: boolean; + path: string; + type: string; + }[]; + description: string; + multiple: boolean; + simpleType: string; + }; + "server-options-crl-reset": { + configs: { + description: string; + multiple: boolean; + path: string; + type: string; + }[]; + description: string; + multiple: boolean; + simpleType: string; + }; + "server-options-key": { + configs: { + description: string; + multiple: boolean; + path: string; + type: string; + }[]; + description: string; + multiple: boolean; + simpleType: string; + }; + "server-options-key-reset": { + configs: { + description: string; + multiple: boolean; + path: string; + type: string; + }[]; + description: string; + multiple: boolean; + simpleType: string; + }; + "server-options-passphrase": { + configs: { + description: string; + multiple: boolean; + path: string; + type: string; + }[]; + description: string; + multiple: boolean; + simpleType: string; + }; + "server-options-pfx": { + configs: { + description: string; + multiple: boolean; + path: string; + type: string; + }[]; + description: string; + multiple: boolean; + simpleType: string; + }; + "server-options-pfx-reset": { + configs: { + description: string; + multiple: boolean; + path: string; + type: string; + }[]; + description: string; + multiple: boolean; + simpleType: string; + }; + "server-options-request-cert": { + configs: { + description: string; + multiple: boolean; + path: string; + type: string; + }[]; + description: string; + multiple: boolean; + simpleType: string; + }; + "server-type": { + configs: { + description: string; + multiple: boolean; + path: string; + type: string; + values: string[]; + }[]; + description: string; + multiple: boolean; + simpleType: string; + }; + static: { + configs: { + type: string; + multiple: boolean; + description: string; + path: string; + }[]; + description: string; + simpleType: string; + multiple: boolean; + }; + "static-directory": { + configs: { + type: string; + multiple: boolean; + description: string; + path: string; + }[]; + description: string; + /** @type {any} */ + simpleType: string; + multiple: boolean; + }; + "static-public-path": { + configs: { + type: string; + multiple: boolean; + description: string; + path: string; + }[]; + description: string; + simpleType: string; + multiple: boolean; + }; + "static-public-path-reset": { + configs: { + type: string; + multiple: boolean; + description: string; + path: string; + }[]; + description: string; + simpleType: string; + multiple: boolean; + }; + "static-reset": { + configs: { + type: string; + multiple: boolean; + description: string; + path: string; + }[]; + description: string; + simpleType: string; + multiple: boolean; + }; + "static-serve-index": { + configs: { + type: string; + multiple: boolean; + description: string; + path: string; + }[]; + description: string; + negatedDescription: string; + simpleType: string; + multiple: boolean; + }; + "static-watch": { + configs: { + type: string; + multiple: boolean; + description: string; + path: string; + }[]; + description: string; + negatedDescription: string; + simpleType: string; + multiple: boolean; + }; + "watch-files": { + configs: { + type: string; + multiple: boolean; + description: string; + path: string; + }[]; + description: string; + simpleType: string; + multiple: boolean; + }; + "watch-files-reset": { + configs: { + type: string; + multiple: boolean; + description: string; + path: string; + }[]; + description: string; + simpleType: string; + multiple: boolean; + }; + "web-socket-server": { + configs: ( + | { + description: string; + multiple: boolean; + path: string; + type: string; + values: boolean[]; + } + | { + description: string; + multiple: boolean; + path: string; + type: string; + values: string[]; + } + | { + description: string; + multiple: boolean; + path: string; + type: string; + } + )[]; + description: string; + simpleType: string; + multiple: boolean; + }; + "web-socket-server-type": { + configs: ( + | { + description: string; + multiple: boolean; + path: string; + type: string; + values: string[]; + } + | { + description: string; + multiple: boolean; + path: string; + type: string; + } + )[]; + description: string; + simpleType: string; + multiple: boolean; + }; + }; + readonly processArguments: ( + args: Record, + config: any, + values: Record< + string, + | string + | number + | boolean + | RegExp + | (string | number | boolean | RegExp)[] + > + ) => import("../bin/process-arguments").Problem[] | null; + }; + static get schema(): { + title: string; + type: string; + definitions: { + AllowedHosts: { + anyOf: ( + | { + type: string; + minItems: number; + items: { + $ref: string; + }; + enum?: undefined; + $ref?: undefined; + } + | { + enum: string[]; + type?: undefined; + minItems?: undefined; + items?: undefined; + $ref?: undefined; + } + | { + $ref: string; + type?: undefined; + minItems?: undefined; + items?: undefined; + enum?: undefined; + } + )[]; + description: string /** @typedef {import("webpack").Configuration} WebpackConfiguration */; + link: string; + }; + AllowedHostsItem: { + type: string; + minLength: number; + }; + Bonjour: { + anyOf: ( + | { + type: string; + description?: undefined; + link?: undefined; + } + | { + type: string; + description: string; + link: string; + } + )[]; + description: string; + link: string; + }; + Client: { + description: string; + link: string; + anyOf: ( + | { + enum: boolean[]; + type?: undefined; + additionalProperties?: undefined; + properties?: undefined; + } + | { + type: string; + additionalProperties: boolean; + properties: { + logging: { + $ref: string; + }; + overlay: { + $ref: string; + }; + progress: { + $ref: string; + }; + reconnect: { + $ref: string; + }; + webSocketTransport: { + $ref: string; + }; + webSocketURL: { + $ref: string; + }; + }; + enum?: undefined; + } + )[]; + }; + ClientLogging: { + enum: string[]; + /** @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} DevMiddlewareOptions + */ + /** + * @template Request, Response + * @typedef {import("webpack-dev-middleware").Context} 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?: string | RegExp | string[], 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?: string | RegExp | string[], 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} [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 + */ + /** + * @typedef {{ [url: string]: string | HttpProxyMiddlewareOptions }} ProxyConfigMap + */ + /** + * @typedef {HttpProxyMiddlewareOptions[]} ProxyArray + */ + /** + * @callback ByPass + * @param {Request} req + * @param {Response} res + * @param {ProxyConfigArray} proxyConfig + */ + /** + * @typedef {{ path?: string | string[] | undefined, context?: string | string[] | HttpProxyMiddlewareOptionsFilter | undefined } & HttpProxyMiddlewareOptions & ByPass} ProxyConfigArray + */ + /** + * @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} 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} [devMiddleware] + * @property {boolean} [compress] + * @property {boolean} [magicHtml] + * @property {"auto" | "all" | string | string[]} [allowedHosts] + * @property {boolean | ConnectHistoryApiFallbackOptions} [historyApiFallback] + * @property {boolean} [setupExitSignals] + * @property {boolean | BonjourOptions} [bonjour] + * @property {string | string[] | WatchFiles | Array} [watchFiles] + * @property {boolean | string | Static | Array} [static] + * @property {boolean | ServerOptions} [https] + * @property {boolean} [http2] + * @property {"http" | "https" | "spdy" | string | ServerConfiguration} [server] + * @property {boolean | "sockjs" | "ws" | string | WebSocketServerConfiguration} [webSocketServer] + * @property {ProxyConfigMap | ProxyConfigArray | ProxyArray} [proxy] + * @property {boolean | string | Open | Array} [open] + * @property {boolean} [setupExitSignals] + * @property {boolean | ClientConfiguration} [client] + * @property {Headers | ((req: Request, res: Response, context: DevMiddlewareContext) => Headers)} [headers] + * @property {(devServer: Server) => void} [onAfterSetupMiddleware] + * @property {(devServer: Server) => void} [onBeforeSetupMiddleware] + * @property {(devServer: Server) => void} [onListening] + * @property {(middlewares: Middleware[], devServer: Server) => Middleware[]} [setupMiddlewares] + */ + description: string; + link: string; + }; + ClientOverlay: { + anyOf: ( + | { + description: string; + link: string; + type: string; + additionalProperties?: undefined; + properties?: undefined; + } + | { + type: string; + additionalProperties: boolean; + properties: { + errors: { + description: string; + type: string; + }; + warnings: { + description: string; + type: string; + }; + }; + description?: undefined; + link?: undefined; + } + )[]; + }; + ClientProgress: { + description: string; + link: string; + type: string; + }; + ClientReconnect: { + description: string; + link: string; + anyOf: ( + | { + type: string; + minimum?: undefined; + } + | { + type: string; + minimum: number; + } + )[]; + }; + ClientWebSocketTransport: { + anyOf: { + $ref: string; + }[]; + description: string; + link: string; + }; + ClientWebSocketTransportEnum: { + enum: string[]; + }; + ClientWebSocketTransportString: { + type: string; + minLength: number; + }; + ClientWebSocketURL: { + description: string; + link: string; + anyOf: ( + | { + /** + * @typedef {HttpProxyMiddlewareOptions[]} ProxyArray + */ + /** + * @callback ByPass + * @param {Request} req + * @param {Response} res + * @param {ProxyConfigArray} proxyConfig + */ + /** + * @typedef {{ path?: string | string[] | undefined, context?: string | string[] | HttpProxyMiddlewareOptionsFilter | undefined } & HttpProxyMiddlewareOptions & ByPass} ProxyConfigArray + */ + /** + * @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} 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} [devMiddleware] + * @property {boolean} [compress] + * @property {boolean} [magicHtml] + * @property {"auto" | "all" | string | string[]} [allowedHosts] + * @property {boolean | ConnectHistoryApiFallbackOptions} [historyApiFallback] + * @property {boolean} [setupExitSignals] + * @property {boolean | BonjourOptions} [bonjour] + * @property {string | string[] | WatchFiles | Array} [watchFiles] + * @property {boolean | string | Static | Array} [static] + * @property {boolean | ServerOptions} [https] + * @property {boolean} [http2] + * @property {"http" | "https" | "spdy" | string | ServerConfiguration} [server] + * @property {boolean | "sockjs" | "ws" | string | WebSocketServerConfiguration} [webSocketServer] + * @property {ProxyConfigMap | ProxyConfigArray | ProxyArray} [proxy] + * @property {boolean | string | Open | Array} [open] + * @property {boolean} [setupExitSignals] + * @property {boolean | ClientConfiguration} [client] + * @property {Headers | ((req: Request, res: Response, context: DevMiddlewareContext) => Headers)} [headers] + * @property {(devServer: Server) => void} [onAfterSetupMiddleware] + * @property {(devServer: Server) => void} [onBeforeSetupMiddleware] + * @property {(devServer: Server) => void} [onListening] + * @property {(middlewares: Middleware[], devServer: Server) => Middleware[]} [setupMiddlewares] + */ + type: string; + minLength: number; + additionalProperties?: undefined; + properties?: undefined; + } + | { + type: string; + additionalProperties: boolean; + properties: { + hostname: { + description: string; + type: string; + minLength: number; + }; + pathname: { + description: string; + type: string; + }; + password: { + description: string; + type: string; + }; + port: { + description: string; + anyOf: ( + | { + type: string; + minLength?: undefined; + } + | { + type: string; + minLength: number; + } + )[]; + }; + protocol: { + description: string; + anyOf: ( + | { + enum: string[]; + type?: undefined; + minLength?: undefined; + } + | { + type: string; + minLength: number; + enum?: undefined; + } + )[]; + }; + username: { + description: string; + type: string; + }; + }; + minLength?: undefined; + } + )[]; + }; + Compress: { + type: string; + description: string; + link: string; + }; + DevMiddleware: { + description: string; + link: string; + type: string; + additionalProperties: boolean; + }; + HTTP2: { + type: string; + description: string; + link: string; + }; + HTTPS: { + anyOf: ( + | { + type: string; + additionalProperties?: undefined; + properties?: undefined; + } + | { + type: string; + additionalProperties: boolean; + properties: { + passphrase: { + type: string; + description: string; + }; + requestCert: { + type: string; + description: string; + }; + ca: { + anyOf: ( + | { + type: string; + items: { + anyOf: ( + | { + type: string; + instanceof?: undefined; + } + | { + instanceof: string; + type?: undefined; + } + )[]; + }; + instanceof?: undefined; + } + | { + type: string; + items?: undefined; + instanceof?: undefined; + } + | { + instanceof: string; + type?: undefined; + items?: undefined; + } + )[]; + description: string; + }; + cacert: { + anyOf: ( + | { + type: string; + items: { + anyOf: ( + | { + type: string; + instanceof?: undefined; + } + | { + instanceof: string; + type?: undefined; + } + )[]; + }; + instanceof?: undefined; + } + | { + type: string; + items?: undefined; + instanceof?: undefined; + } + | { + instanceof: string; + type?: undefined; + items?: undefined; + } + )[]; + description: string; + }; + cert: { + anyOf: ( + | { + type: string; + items: { + anyOf: ( + | { + type: string; + instanceof?: undefined; + } + | { + instanceof: string; + type?: undefined; + } + )[]; + }; + instanceof?: undefined; + } + | { + type: string; + /** + * @type {Socket[]} + */ + items?: undefined; + instanceof?: undefined; + } + | { + instanceof: string; + type?: undefined; + items?: undefined; + } + )[]; + description: string; + }; + crl: { + anyOf: ( + | { + type: string; + items: { + anyOf: ( + | { + type: string; + instanceof?: undefined; + } + | { + /** + * @private + * @returns {StatsOptions} + * @constructor + */ + instanceof: string; + type?: undefined; + } + )[]; + }; + instanceof?: undefined; + } + | { + type: string; + items?: undefined; + instanceof?: undefined; + } + | { + instanceof: string; + type?: undefined; + items?: undefined; + } + )[]; + description: string; + }; + key: { + anyOf: ( + | { + type: string; + items: { + anyOf: ( + | { + type: string; + instanceof?: undefined; + additionalProperties?: undefined; + } + | { + instanceof: string; + type?: undefined; + additionalProperties?: undefined; + } + | { + type: string; + /** @type {NetworkInterfaceInfo[]} */ + additionalProperties: boolean; + instanceof?: undefined; + } + )[]; + }; + instanceof?: undefined; + } + | { + type: string; + items?: undefined; + instanceof?: undefined; + } + | { + instanceof: string; + type?: undefined; + items?: undefined; + } + )[]; + description: string; + }; + pfx: { + anyOf: ( + | { + type: string; + items: { + anyOf: ( + | { + type: string; + instanceof?: undefined; + additionalProperties?: undefined; + } + | { + instanceof: string; + type?: undefined; + additionalProperties?: undefined; + } + | { + type: string; + /** + * @param {Host} hostname + * @returns {Promise} + */ + additionalProperties: boolean; + instanceof?: undefined; + } + )[]; + }; + instanceof?: undefined; + } + | { + type: string; + items?: undefined; + instanceof?: undefined; + } + | { + instanceof: string; + type?: undefined; + items?: undefined; + } + )[]; + description: string; + }; + }; + } + )[]; + description: string; + link: string; + }; + HeaderObject: { + type: string; + additionalProperties: boolean; + properties: { + key: { + description: string; + type: string; + }; + value: { + description: string; + type: string; + }; + }; + cli: { + exclude: boolean; + }; + }; + Headers: { + anyOf: ( + | { + type: string; + items: { + $ref: string; + }; + minItems: number; + instanceof?: undefined; + } + | { + type: string; + items?: undefined; + minItems?: undefined; + instanceof?: undefined; + } + | { + instanceof: string; + type?: undefined; + items?: undefined; + minItems?: undefined; + } + )[]; + description: string; + link: string; + }; + HistoryApiFallback: { + anyOf: ( + | { + type: string; + description?: undefined; + link?: undefined; + } + | { + type: string; + description: string; + link: string; + } + )[]; + description: string; + link: string; + }; + Host: { + description: string; + link: string; + anyOf: ( + | { + enum: string[]; + type?: undefined; + minLength?: undefined; + } + | { + type: string; + minLength: number; + enum?: undefined; + } + )[]; + }; + Hot: { + anyOf: ( + | { + type: string; + enum?: undefined; + } + | { + enum: string[]; + type?: undefined; + } + )[]; + description: string; + link: string; + }; + IPC: { + anyOf: ( + | { + type: string; + minLength: number; + enum?: undefined; + } + | { + type: string; + enum: boolean[]; + minLength?: undefined; + } + )[]; + description: string; + link: string; + }; + LiveReload: { + type: string; + description: string; + /** @type {{ type: WebSocketServerConfiguration["type"], options: NonNullable }} */ + link: string; + }; + MagicHTML: { + type: string; + description: string; + link: string; + }; + OnAfterSetupMiddleware: { + instanceof: string; + description: string; + link: string; + }; + OnBeforeSetupMiddleware: { + instanceof: string; + description: string; + link: string; + }; + OnListening: { + instanceof: string; + description: string; + link: string; + }; + Open: { + anyOf: ( + | { + type: string; + items: { + anyOf: { + $ref: string; + }[]; + }; + $ref?: undefined; + } + | { + $ref: string; + type?: undefined; + items?: undefined; + } + )[]; + description: string; + link: string; + }; + OpenBoolean: { + type: string; + }; + OpenObject: { + type: string; + additionalProperties: boolean; + properties: { + target: { + anyOf: ( + | { + type: string; + items: { + type: string; + }; + } + | { + type: string; + items?: undefined; + } + )[]; + description: string; + }; + app: { + anyOf: ( + | { + type: string; + additionalProperties: boolean; + properties: { + name: { + anyOf: ( + | { + type: string; + items: { + type: string; + minLength: number; + }; + minItems: number; + minLength?: undefined; + } + | { + type: string; + minLength: number; + items?: undefined; + minItems?: undefined; + } + )[]; + }; + arguments: { + items: { + type: string; + minLength: number; + }; + }; + }; + minLength?: undefined; + description?: undefined; + } + | { + type: string; + minLength: number; + description: string; + additionalProperties?: undefined; + properties?: undefined; + } + )[]; + description: string; + }; + }; + }; + OpenString: { + type: string; + minLength: number; + }; + Port: { + anyOf: ( + | { + type: string; + minimum: number; + maximum: number; + minLength?: undefined; + enum?: undefined; + } + | { + type: string; + minLength: number; + minimum?: undefined; + maximum?: undefined; + enum?: undefined; + } + | { + enum: string[]; + type?: undefined; + minimum?: undefined; + maximum?: undefined; + minLength?: undefined; + } + )[]; + description: string; + link: string; + }; + Proxy: { + anyOf: ( + | { + type: string; + items?: undefined; + } + | { + type: string; + items: { + anyOf: ( + | { + type: string; + instanceof?: undefined; + } + | { + instanceof: string; + type?: undefined; + } + )[]; + }; + } + )[]; + description: string; + link: string; + }; + Server: { + anyOf: { + $ref: string; + }[]; + link: string; + description: string; + }; + ServerType: { + enum: string[]; + }; + ServerEnum: { + enum: string[]; + cli: { + exclude: boolean; + }; + }; + ServerString: { + type: string; + minLength: number; + cli: { + exclude: boolean; + }; + }; + ServerObject: { + type: string; + properties: { + type: { + anyOf: { + $ref: string; + }[]; + }; + options: { + $ref: string; + }; + }; + additionalProperties: boolean; + }; + ServerOptions: { + type: string; + additionalProperties: boolean; + properties: { + /** @type {any} */ + passphrase: { + type: string; + description: string; + }; + /** @type {any} */ + requestCert: { + type: string; + description: string; + }; + ca: { + anyOf: ( + | { + type: string; + items: { + anyOf: ( + | { + type: string; + instanceof?: undefined; + } + | { + instanceof: string; + type?: undefined; + } + )[]; + }; + /** @type {string} */ instanceof?: undefined; + } + | { + type: string; + items?: undefined; + instanceof?: undefined; + } + | { + instanceof: string; + type?: undefined; + items?: undefined; + } + )[]; + description: string; + }; + cacert: { + anyOf: ( + | { + type: string; + items: { + anyOf: ( + | { + type: string; + instanceof?: undefined; + } + | { + instanceof: string; + type?: undefined; + } + )[]; + }; + instanceof?: undefined; + } + | { + type: string; + items?: undefined; + instanceof?: undefined; + } + | { + instanceof: string; + type?: undefined; + items?: undefined; + } + )[]; + description: string; + }; + cert: { + anyOf: ( + | { + type: string; + items: { + anyOf: ( + | { + type: string; + instanceof?: undefined; + } + | { + instanceof: string; + type?: undefined; + } + )[]; + }; + instanceof?: undefined; + } + | { + type: string; + items?: undefined; + instanceof?: undefined; + } + | { + instanceof: string; + type?: undefined; + items?: undefined; + } + )[]; + description: string; + }; + crl: { + anyOf: ( + | { + type: string; + items: { + anyOf: ( + | { + type: string; + instanceof?: undefined; + } + | { + instanceof: string; + type?: undefined; + } + )[]; + }; + instanceof?: undefined; + } + | { + type: string; + items?: undefined; + instanceof?: undefined; + } + | { + instanceof: string; + type?: undefined; + items?: undefined; + } + )[]; + description: string; + }; + key: { + anyOf: ( + | { + type: string; + items: { + anyOf: ( + | { + type: string; + instanceof?: undefined; + additionalProperties?: undefined; + } + | { + instanceof: string; + type?: undefined; + additionalProperties?: undefined; + } + | { + type: string; + additionalProperties: boolean; + instanceof?: undefined; + } + )[]; + }; + instanceof?: undefined; + } + | { + type: string; + items?: undefined; + instanceof?: undefined; + } + | { + instanceof: string; + type?: undefined; + items?: undefined; + } + )[]; + description: string; + }; + pfx: { + anyOf: ( + | { + type: string; + items: { + anyOf: ( + | { + type: string; + instanceof?: undefined; + additionalProperties?: undefined; + } + | { + instanceof: string; + type?: undefined; + additionalProperties?: undefined; + } + | { + type: string; + additionalProperties: boolean; + instanceof?: undefined; + } + )[]; + }; + instanceof?: undefined; + } + | { + type: string; + items?: undefined; + instanceof?: undefined; + } + | { + instanceof: string; + type?: undefined; + items?: undefined; + } + )[]; + description: string; + }; + }; + }; + SetupExitSignals: { + type: string; + description: string; + link: string; + cli: { + exclude: boolean; + }; + }; + SetupMiddlewares: { + instanceof: string; + description: string; + link: string; + }; + Static: { + anyOf: ( + | { + type: string; + items: { + anyOf: { + $ref: string; + }[]; + }; + $ref?: undefined; + } + | { + type: string; + items?: undefined; + $ref?: undefined; + } + | { + $ref: string; + type?: undefined; + items?: undefined; + } + )[]; + description: string; + link: string; + }; + StaticObject: { + type: string; + additionalProperties: boolean; + properties: { + directory: { + type: string; + minLength: number; + description: string; + link: string; + }; + staticOptions: { + type: string; + link: string; + additionalProperties: boolean; + }; + publicPath: { + anyOf: ( + | { + type: string; + items: { + type: string; + }; + minItems: number; + } + | { + type: string; + items?: undefined; + minItems?: undefined; + } + )[]; + description: string; + link: string; + }; + serveIndex: { + anyOf: ( + | { + type: string; + additionalProperties?: undefined; + } + | { + type: string; + additionalProperties: boolean; + } + )[]; + description: string; + link: string; + }; + watch: { + anyOf: ( + | { + type: string; + description?: undefined; + link?: undefined; + } + | { + type: string; + description: string; + link: string; + } + )[]; + description: string; + link: string; + }; + }; + }; + StaticString: { + type: string; + minLength: number; + }; + WatchFiles: { + anyOf: ( + | { + type: string; + items: { + anyOf: { + $ref: string; + }[]; + }; + $ref?: undefined; + } + | { + $ref: string; + type?: undefined; + items?: undefined; + } + )[]; + description: string; + link: string; + }; + WatchFilesObject: { + cli: { + exclude: boolean; + }; + type: string; + properties: { + paths: { + anyOf: ( + | { + type: string; + items: { + type: string; + minLength: number; + }; + minLength?: undefined; + } + | { + type: string; + minLength: number; + items?: undefined; + } + )[]; + description: string; + }; + options: { + type: string; + description: string; + link: string; + additionalProperties: boolean; + }; + }; + additionalProperties: boolean; + }; + WatchFilesString: { + type: string; + minLength: number; + }; + WebSocketServer: { + anyOf: { + $ref: string; + }[]; + description: string; + link: string; + }; + WebSocketServerType: { + enum: string[]; + }; + WebSocketServerEnum: { + anyOf: ( + | { + enum: boolean[]; + $ref?: undefined; + } + | { + $ref: string; + enum?: undefined; + } + )[]; + description: string; + }; + WebSocketServerFunction: { + instanceof: string; + }; + WebSocketServerObject: { + type: string; + properties: { + type: { + anyOf: { + $ref: string; + }[]; + }; + options: { + type: string; + additionalProperties: boolean; + cli: { + exclude: boolean; + }; + }; + }; + additionalProperties: boolean; + }; + WebSocketServerString: { + type: string; + minLength: number; + }; + }; + additionalProperties: boolean; + properties: { + allowedHosts: { + $ref: string; + }; + bonjour: { + $ref: string; + }; + client: { + $ref: string; + }; + compress: { + $ref: string; + }; + devMiddleware: { + $ref: string; + }; + headers: { + $ref: string; + }; + historyApiFallback: { + $ref: string; + }; + host: { + $ref: string; + }; + hot: { + $ref: string; + }; + http2: { + $ref: string /** @type {ServerConfiguration} */; + }; + https: { + $ref: string; + }; + ipc: { + $ref: string; + }; + liveReload: { + $ref: string; + }; + magicHtml: { + $ref: string; + }; + onAfterSetupMiddleware: { + $ref: string; + }; + onBeforeSetupMiddleware: { + $ref: string; + }; + onListening: { + $ref: string; + }; + open: { + $ref: string; + }; + port: { + $ref: string; + }; + proxy: { + $ref: string; + }; + server: { + $ref: string; + }; + setupExitSignals: { + $ref: string; + }; + setupMiddlewares: { + $ref: string; + }; + static: { + $ref: string; + }; + watchFiles: { + $ref: string; + }; + webSocketServer: { + $ref: string; + }; + }; + }; + /** + * @param {string} URL + * @returns {boolean} + */ + static isAbsoluteURL(URL: string): boolean; + /** + * @param {string} gateway + * @returns {string | undefined} + */ + static findIp(gateway: string): string | undefined; + /** + * @param {"v4" | "v6"} family + * @returns {Promise} + */ + static internalIP(family: "v4" | "v6"): Promise; + /** + * @param {"v4" | "v6"} family + * @returns {string | undefined} + */ + static internalIPSync(family: "v4" | "v6"): string | undefined; + /** + * @param {Host} hostname + * @returns {Promise} + */ + static getHostname(hostname: Host): Promise; + /** + * @param {Port} port + * @returns {Promise} + */ + static getFreePort(port: Port): Promise; + /** + * @returns {string} + */ + static findCacheDir(): string; + /** + * @param {Configuration | Compiler | MultiCompiler} options + * @param {Compiler | MultiCompiler | Configuration} compiler + */ + constructor( + options: + | import("webpack").Compiler + | import("webpack").MultiCompiler + | Configuration + | undefined, + compiler: Compiler | MultiCompiler | Configuration + ); + compiler: import("webpack").Compiler | import("webpack").MultiCompiler; + /** + * @type {ReturnType} + * */ + logger: ReturnType; + options: Configuration; + /** + * @type {FSWatcher[]} + */ + staticWatchers: FSWatcher[]; + /** + * @private + * @type {{ name: string | symbol, listener: (...args: any[]) => void}[] }} + */ + private listeners; + /** + * @private + * @type {RequestHandler[]} + */ + private webSocketProxies; + /** + * @type {Socket[]} + */ + sockets: Socket[]; + /** + * @private + * @type {string | undefined} + */ + private currentHash; + /** + * @private + * @param {Compiler} compiler + */ + private addAdditionalEntries; + /** + * @private + * @returns {Compiler["options"]} + */ + private getCompilerOptions; + /** + * @private + * @returns {Promise} + */ + private normalizeOptions; + /** + * @private + * @returns {string} + */ + private getClientTransport; + /** + * @private + * @returns {string} + */ + private getServerTransport; + /** + * @private + * @returns {void} + */ + private setupProgressPlugin; + /** + * @private + * @returns {Promise} + */ + private initialize; + /** + * @private + * @returns {void} + */ + private setupApp; + /** @type {import("express").Application | undefined}*/ + app: import("express").Application | undefined; + /** + * @private + * @param {Stats | MultiStats} statsObj + * @returns {StatsCompilation} + */ + private getStats; + /** + * @private + * @returns {void} + */ + private setupHooks; + /** + * @private + * @type {Stats | MultiStats} + */ + private stats; + /** + * @private + * @returns {void} + */ + private setupHostHeaderCheck; + /** + * @private + * @returns {void} + */ + private setupDevMiddleware; + middleware: + | import("webpack-dev-middleware").API< + express.Request< + import("express-serve-static-core").ParamsDictionary, + any, + any, + qs.ParsedQs, + Record + >, + express.Response> + > + | null + | undefined; + /** + * @private + * @returns {void} + */ + private setupBuiltInRoutes; + /** + * @private + * @returns {void} + */ + private setupWatchStaticFiles; + /** + * @private + * @returns {void} + */ + private setupWatchFiles; + /** + * @private + * @returns {void} + */ + private setupMiddlewares; + /** + * @private + * @returns {void} + */ + private createServer; + /** @type {import("http").Server | undefined | null} */ + server: import("http").Server | undefined | null; + /** + * @private + * @returns {void} + */ + private createWebSocketServer; + /** @type {WebSocketServerImplementation | undefined | null} */ + webSocketServer: WebSocketServerImplementation | undefined | null; + /** + * @private + * @param {string} defaultOpenTarget + * @returns {void} + */ + private openBrowser; + /** + * @private + * @returns {void} + */ + private runBonjour; + /** + * @private + * @type {import("bonjour").Bonjour | undefined} + */ + private bonjour; + /** + * @private + * @returns {void} + */ + private stopBonjour; + /** + * @private + * @returns {void} + */ + private logStatus; + /** + * @private + * @param {Request} req + * @param {Response} res + * @param {NextFunction} next + */ + private setHeaders; + /** + * @private + * @param {{ [key: string]: string | undefined }} headers + * @param {string} headerToCheck + * @returns {boolean} + */ + private checkHeader; + /** + * @param {ClientConnection[]} clients + * @param {string} type + * @param {any} [data] + * @param {any} [params] + */ + sendMessage( + clients: ClientConnection[], + type: string, + data?: any, + params?: any + ): void; + /** + * @private + * @param {Request} req + * @param {Response} res + * @param {NextFunction} next + * @returns {void} + */ + private serveMagicHtml; + /** + * @private + * @param {ClientConnection[]} clients + * @param {StatsCompilation} stats + * @param {boolean} [force] + */ + private sendStats; + /** + * @param {string | string[]} watchPath + * @param {WatchOptions} [watchOptions] + */ + watchFiles( + watchPath: string | string[], + watchOptions?: import("chokidar").WatchOptions | undefined + ): void; + /** + * @param {import("webpack-dev-middleware").Callback} [callback] + */ + invalidate( + callback?: import("webpack-dev-middleware").Callback | undefined + ): void; + /** + * @returns {Promise} + */ + start(): Promise; + /** + * @param {(err?: Error) => void} [callback] + */ + startCallback( + callback?: ((err?: Error | undefined) => void) | undefined + ): void; + /** + * @returns {Promise} + */ + stop(): Promise; + /** + * @param {(err?: Error) => void} [callback] + */ + stopCallback( + callback?: ((err?: Error | undefined) => void) | undefined + ): void; + /** + * @param {Port} port + * @param {Host} hostname + * @param {(err?: Error) => void} fn + * @returns {void} + */ + listen( + port: Port, + hostname: Host, + fn: (err?: Error | undefined) => void + ): void; + /** + * @param {(err?: Error) => void} [callback] + * @returns {void} + */ + close(callback?: ((err?: Error | undefined) => void) | undefined): void; +} +declare namespace Server { + export { + DEFAULT_STATS, + Schema, + Compiler, + MultiCompiler, + WebpackConfiguration, + StatsOptions, + StatsCompilation, + Stats, + MultiStats, + NetworkInterfaceInfo, + Request, + Response, + NextFunction, + ExpressRequestHandler, + ExpressErrorRequestHandler, + WatchOptions, + FSWatcher, + ConnectHistoryApiFallbackOptions, + Bonjour, + BonjourOptions, + RequestHandler, + HttpProxyMiddlewareOptions, + HttpProxyMiddlewareOptionsFilter, + ServeIndexOptions, + ServeStaticOptions, + IPv4, + IPv6, + Socket, + IncomingMessage, + OpenOptions, + ServerOptions, + DevMiddlewareOptions, + DevMiddlewareContext, + Host, + Port, + WatchFiles, + Static, + NormalizedStatic, + ServerConfiguration, + WebSocketServerConfiguration, + ClientConnection, + WebSocketServer, + WebSocketServerImplementation, + ProxyConfigMap, + ProxyArray, + ByPass, + ProxyConfigArray, + OpenApp, + Open, + NormalizedOpen, + WebSocketURL, + ClientConfiguration, + Headers, + Middleware, + Configuration, + }; +} +type Compiler = import("webpack").Compiler; +type Configuration = { + ipc?: string | boolean | undefined; + host?: string | undefined; + port?: Port | undefined; + hot?: boolean | "only" | undefined; + liveReload?: boolean | undefined; + devMiddleware?: + | DevMiddlewareOptions< + express.Request< + import("express-serve-static-core").ParamsDictionary, + any, + any, + qs.ParsedQs, + Record + >, + express.Response> + > + | undefined; + compress?: boolean | undefined; + magicHtml?: boolean | undefined; + allowedHosts?: string | string[] | undefined; + historyApiFallback?: + | boolean + | import("connect-history-api-fallback").Options + | undefined; + setupExitSignals?: boolean | undefined; + bonjour?: boolean | import("bonjour").BonjourOptions | undefined; + watchFiles?: + | string + | string[] + | WatchFiles + | (string | WatchFiles)[] + | undefined; + static?: string | boolean | Static | (string | Static)[] | undefined; + https?: boolean | ServerOptions | undefined; + http2?: boolean | undefined; + server?: string | ServerConfiguration | undefined; + webSocketServer?: string | boolean | WebSocketServerConfiguration | undefined; + proxy?: ProxyConfigMap | ProxyConfigArray | ProxyArray | undefined; + open?: string | boolean | Open | (string | Open)[] | undefined; + client?: boolean | ClientConfiguration | undefined; + headers?: + | Headers + | (( + req: Request, + res: Response, + context: DevMiddlewareContext + ) => Headers) + | undefined; + onAfterSetupMiddleware?: ((devServer: Server) => void) | undefined; + onBeforeSetupMiddleware?: ((devServer: Server) => void) | undefined; + onListening?: ((devServer: Server) => void) | undefined; + setupMiddlewares?: + | ((middlewares: Middleware[], devServer: Server) => Middleware[]) + | undefined; +}; +type FSWatcher = import("chokidar").FSWatcher; +type Socket = import("net").Socket; +import express = require("express"); +type WebSocketServerImplementation = { + implementation: WebSocketServer; + clients: ClientConnection[]; +}; +type ClientConnection = ( + | import("ws").WebSocket + | (import("sockjs").Connection & { + send: import("ws").WebSocket["send"]; + terminate: import("ws").WebSocket["terminate"]; + ping: import("ws").WebSocket["ping"]; + }) +) & { + isAlive?: boolean; +}; +type Port = number | string | "auto"; +type Host = "local-ip" | "local-ipv4" | "local-ipv6" | string; +type MultiCompiler = import("webpack").MultiCompiler; +declare class DEFAULT_STATS { + private constructor(); +} +type Schema = import("schema-utils/declarations/validate").Schema; +type WebpackConfiguration = import("webpack").Configuration; +type StatsOptions = import("webpack").StatsOptions; +type StatsCompilation = import("webpack").StatsCompilation; +type Stats = import("webpack").Stats; +type MultiStats = import("webpack").MultiStats; +type NetworkInterfaceInfo = import("os").NetworkInterfaceInfo; +type Request = import("express").Request; +type Response = import("express").Response; +type NextFunction = import("express").NextFunction; +type ExpressRequestHandler = import("express").RequestHandler; +type ExpressErrorRequestHandler = import("express").ErrorRequestHandler; +type WatchOptions = import("chokidar").WatchOptions; +type ConnectHistoryApiFallbackOptions = + import("connect-history-api-fallback").Options; +type Bonjour = import("bonjour").Bonjour; +type BonjourOptions = import("bonjour").BonjourOptions; +type RequestHandler = import("http-proxy-middleware").RequestHandler; +type HttpProxyMiddlewareOptions = import("http-proxy-middleware").Options; +type HttpProxyMiddlewareOptionsFilter = import("http-proxy-middleware").Filter; +type ServeIndexOptions = import("serve-index").Options; +type ServeStaticOptions = import("serve-static").ServeStaticOptions; +type IPv4 = import("ipaddr.js").IPv4; +type IPv6 = import("ipaddr.js").IPv6; +type IncomingMessage = import("http").IncomingMessage; +type OpenOptions = import("open").Options; +type ServerOptions = import("https").ServerOptions & { + spdy?: { + plain?: boolean | undefined; + ssl?: boolean | undefined; + "x-forwarded-for"?: string | undefined; + protocol?: string | undefined; + protocols?: string[] | undefined; + }; +}; +type DevMiddlewareOptions = + import("webpack-dev-middleware").Options; +type DevMiddlewareContext = + import("webpack-dev-middleware").Context; +type WatchFiles = { + paths: string | string[]; + options?: + | (import("chokidar").WatchOptions & { + aggregateTimeout?: number | undefined; + ignored?: string | RegExp | string[] | undefined; + poll?: number | boolean | undefined; + }) + | undefined; +}; +type Static = { + directory?: string | undefined; + publicPath?: string | string[] | undefined; + serveIndex?: boolean | import("serve-index").Options | undefined; + staticOptions?: + | import("serve-static").ServeStaticOptions + | undefined; + watch?: + | boolean + | (import("chokidar").WatchOptions & { + aggregateTimeout?: number | undefined; + ignored?: string | RegExp | string[] | undefined; + poll?: number | boolean | undefined; + }) + | undefined; +}; +type NormalizedStatic = { + directory: string; + publicPath: string[]; + serveIndex: false | ServeIndexOptions; + staticOptions: ServeStaticOptions; + watch: false | WatchOptions; +}; +type ServerConfiguration = { + type?: string | undefined; + options?: ServerOptions | undefined; +}; +type WebSocketServerConfiguration = { + type?: string | Function | undefined; + options?: Record | undefined; +}; +type WebSocketServer = + | import("ws").WebSocketServer + | (import("sockjs").Server & { + close: import("ws").WebSocketServer["close"]; + }); +type ProxyConfigMap = { + [url: string]: string | import("http-proxy-middleware").Options; +}; +type ProxyArray = HttpProxyMiddlewareOptions[]; +type ByPass = ( + req: Request, + res: Response, + proxyConfig: ProxyConfigArray +) => any; +type ProxyConfigArray = { + path?: string | string[] | undefined; + context?: string | string[] | HttpProxyMiddlewareOptionsFilter | undefined; +} & HttpProxyMiddlewareOptions & + ByPass; +type OpenApp = { + name?: string | undefined; + arguments?: string[] | undefined; +}; +type Open = { + app?: string | string[] | OpenApp | undefined; + target?: string | string[] | undefined; +}; +type NormalizedOpen = { + target: string; + options: import("open").Options; +}; +type WebSocketURL = { + hostname?: string | undefined; + password?: string | undefined; + pathname?: string | undefined; + port?: string | number | undefined; + protocol?: string | undefined; + username?: string | undefined; +}; +type ClientConfiguration = { + logging?: "none" | "error" | "warn" | "info" | "log" | "verbose" | undefined; + overlay?: + | boolean + | { + warnings?: boolean | undefined; + errors?: boolean | undefined; + } + | undefined; + progress?: boolean | undefined; + reconnect?: number | boolean | undefined; + webSocketTransport?: string | undefined; + webSocketURL?: string | WebSocketURL | undefined; +}; +type Headers = + | Array<{ + key: string; + value: string; + }> + | Record; +type Middleware = + | { + name?: string; + path?: string; + middleware: ExpressRequestHandler | ExpressErrorRequestHandler; + } + | ExpressRequestHandler + | ExpressErrorRequestHandler; +import path = require("path"); + +// DO NOT REMOVE THIS! +type DevServerConfiguration = Configuration; +declare module "webpack" { + interface Configuration { + /** + * Can be used to configure the behaviour of webpack-dev-server when + * the webpack config is passed to webpack-dev-server CLI. + */ + devServer?: DevServerConfiguration | undefined; + } +} diff --git a/types/lib/servers/BaseServer.d.ts b/types/lib/servers/BaseServer.d.ts new file mode 100644 index 0000000000..87aedf8ba9 --- /dev/null +++ b/types/lib/servers/BaseServer.d.ts @@ -0,0 +1,15 @@ +export = BaseServer; +declare class BaseServer { + /** + * @param {import("../Server")} server + */ + constructor(server: import("../Server")); + /** @type {import("../Server")} */ + server: import("../Server"); + /** @type {ClientConnection[]} */ + clients: ClientConnection[]; +} +declare namespace BaseServer { + export { ClientConnection }; +} +type ClientConnection = import("../Server").ClientConnection; diff --git a/types/lib/servers/SockJSServer.d.ts b/types/lib/servers/SockJSServer.d.ts new file mode 100644 index 0000000000..8116b4dd9d --- /dev/null +++ b/types/lib/servers/SockJSServer.d.ts @@ -0,0 +1,12 @@ +export = SockJSServer; +declare class SockJSServer extends BaseServer { + implementation: sockjs.Server; +} +declare namespace SockJSServer { + export { WebSocketServerConfiguration, ClientConnection }; +} +import BaseServer = require("./BaseServer"); +import sockjs = require("sockjs"); +type WebSocketServerConfiguration = + import("../Server").WebSocketServerConfiguration; +type ClientConnection = import("../Server").ClientConnection; diff --git a/types/lib/servers/WebsocketServer.d.ts b/types/lib/servers/WebsocketServer.d.ts new file mode 100644 index 0000000000..d831f35670 --- /dev/null +++ b/types/lib/servers/WebsocketServer.d.ts @@ -0,0 +1,13 @@ +export = WebsocketServer; +declare class WebsocketServer extends BaseServer { + static heartbeatInterval: number; + implementation: WebSocket.Server; +} +declare namespace WebsocketServer { + export { WebSocketServerConfiguration, ClientConnection }; +} +import BaseServer = require("./BaseServer"); +import WebSocket = require("ws"); +type WebSocketServerConfiguration = + import("../Server").WebSocketServerConfiguration; +type ClientConnection = import("../Server").ClientConnection;