diff --git a/client-src/index.ts b/client-src/index.ts index b58290e..41960a5 100644 --- a/client-src/index.ts +++ b/client-src/index.ts @@ -585,13 +585,8 @@ const formatURL = (objURL: { hash = `#${hash}`; } - pathname = pathname.replace( - /[?#]/g, - /** - * @param {string} match - * @returns {string} - */ - (match) => encodeURIComponent(match), + pathname = pathname.replace(/[?#]/g, (match: string): string => + encodeURIComponent(match), ); search = search.replace('#', '%23'); diff --git a/client-src/modules/logger/Logger.js b/client-src/modules/logger/Logger.ts similarity index 51% rename from client-src/modules/logger/Logger.js rename to client-src/modules/logger/Logger.ts index 7d7fbcb..50a96c8 100644 --- a/client-src/modules/logger/Logger.js +++ b/client-src/modules/logger/Logger.ts @@ -8,93 +8,54 @@ * https://github.com/webpack/webpack-dev-server/blob/main/LICENSE */ -// @ts-nocheck - -'use strict'; - -const LogType = Object.freeze({ - error: /** @type {"error"} */ ('error'), // message, c style arguments - warn: /** @type {"warn"} */ ('warn'), // message, c style arguments - info: /** @type {"info"} */ ('info'), // message, c style arguments - log: /** @type {"log"} */ ('log'), // message, c style arguments - debug: /** @type {"debug"} */ ('debug'), // message, c style arguments - - trace: /** @type {"trace"} */ ('trace'), // no arguments - - group: /** @type {"group"} */ ('group'), // [label] - groupCollapsed: /** @type {"groupCollapsed"} */ ('groupCollapsed'), // [label] - groupEnd: /** @type {"groupEnd"} */ ('groupEnd'), // [label] - - profile: /** @type {"profile"} */ ('profile'), // [profileName] - profileEnd: /** @type {"profileEnd"} */ ('profileEnd'), // [profileName] - - time: /** @type {"time"} */ ('time'), // name, time as [seconds, nanoseconds] - - clear: /** @type {"clear"} */ ('clear'), // no arguments - status: /** @type {"status"} */ ('status'), // message, arguments -}); - -module.exports.LogType = LogType; - -/** @typedef {typeof LogType[keyof typeof LogType]} LogTypeEnum */ -/** @typedef {Map} TimersMap */ +import { + LogType, + type Args, + type EXPECTED_ANY, + type LogTypeEnum, + type TimersMap, +} from '../types'; const LOG_SYMBOL = Symbol('webpack logger raw log method'); const TIMERS_SYMBOL = Symbol('webpack logger times'); const TIMERS_AGGREGATES_SYMBOL = Symbol('webpack logger aggregated times'); -/** @typedef {EXPECTED_ANY[]} Args */ - class WebpackLogger { - /** - * @param {(type: LogTypeEnum, args?: Args) => void} log log function - * @param {(name: string | (() => string)) => WebpackLogger} getChildLogger function to create child logger - */ - constructor(log, getChildLogger) { + private [LOG_SYMBOL]: (type: LogTypeEnum, args?: Args) => void; + private [TIMERS_SYMBOL]: TimersMap = new Map(); + private [TIMERS_AGGREGATES_SYMBOL]: TimersMap = new Map(); + // @ts-ignore + private getChildLogger: (name: string | (() => string)) => WebpackLogger; + + constructor( + log: (type: LogTypeEnum, args?: Args) => void, + getChildLogger: (name: string | (() => string)) => WebpackLogger, + ) { this[LOG_SYMBOL] = log; this.getChildLogger = getChildLogger; } - /** - * @param {Args} args args - */ - error(...args) { + error(...args: Args) { this[LOG_SYMBOL](LogType.error, args); } - /** - * @param {Args} args args - */ - warn(...args) { + warn(...args: Args) { this[LOG_SYMBOL](LogType.warn, args); } - /** - * @param {Args} args args - */ - info(...args) { + info(...args: Args) { this[LOG_SYMBOL](LogType.info, args); } - /** - * @param {Args} args args - */ - log(...args) { + log(...args: Args) { this[LOG_SYMBOL](LogType.log, args); } - /** - * @param {Args} args args - */ - debug(...args) { + debug(...args: Args) { this[LOG_SYMBOL](LogType.debug, args); } - /** - * @param {EXPECTED_ANY} assertion assertion - * @param {Args} args args - */ - assert(assertion, ...args) { + assert(assertion: EXPECTED_ANY, ...args: Args) { if (!assertion) { this[LOG_SYMBOL](LogType.error, args); } @@ -108,24 +69,15 @@ class WebpackLogger { this[LOG_SYMBOL](LogType.clear); } - /** - * @param {Args} args args - */ - status(...args) { + status(...args: Args) { this[LOG_SYMBOL](LogType.status, args); } - /** - * @param {Args} args args - */ - group(...args) { + group(...args: Args) { this[LOG_SYMBOL](LogType.group, args); } - /** - * @param {Args} args args - */ - groupCollapsed(...args) { + groupCollapsed(...args: Args) { this[LOG_SYMBOL](LogType.groupCollapsed, args); } @@ -133,33 +85,20 @@ class WebpackLogger { this[LOG_SYMBOL](LogType.groupEnd); } - /** - * @param {string=} label label - */ - profile(label) { + profile(label?: string) { this[LOG_SYMBOL](LogType.profile, [label]); } - /** - * @param {string=} label label - */ - profileEnd(label) { + profileEnd(label?: string) { this[LOG_SYMBOL](LogType.profileEnd, [label]); } - /** - * @param {string} label label - */ - time(label) { - /** @type {TimersMap} */ + time(label: string) { this[TIMERS_SYMBOL] = this[TIMERS_SYMBOL] || new Map(); this[TIMERS_SYMBOL].set(label, process.hrtime()); } - /** - * @param {string=} label label - */ - timeLog(label) { + timeLog(label?: string) { const prev = this[TIMERS_SYMBOL] && this[TIMERS_SYMBOL].get(label); if (!prev) { throw new Error(`No such label '${label}' for WebpackLogger.timeLog()`); @@ -168,24 +107,18 @@ class WebpackLogger { this[LOG_SYMBOL](LogType.time, [label, ...time]); } - /** - * @param {string=} label label - */ - timeEnd(label) { + timeEnd(label?: string) { const prev = this[TIMERS_SYMBOL] && this[TIMERS_SYMBOL].get(label); if (!prev) { throw new Error(`No such label '${label}' for WebpackLogger.timeEnd()`); } const time = process.hrtime(prev); /** @type {TimersMap} */ - (this[TIMERS_SYMBOL]).delete(label); + this[TIMERS_SYMBOL].delete(label); this[LOG_SYMBOL](LogType.time, [label, ...time]); } - /** - * @param {string=} label label - */ - timeAggregate(label) { + timeAggregate(label?: string) { const prev = this[TIMERS_SYMBOL] && this[TIMERS_SYMBOL].get(label); if (!prev) { throw new Error( @@ -193,9 +126,7 @@ class WebpackLogger { ); } const time = process.hrtime(prev); - /** @type {TimersMap} */ - (this[TIMERS_SYMBOL]).delete(label); - /** @type {TimersMap} */ + this[TIMERS_SYMBOL].delete(label); this[TIMERS_AGGREGATES_SYMBOL] = this[TIMERS_AGGREGATES_SYMBOL] || new Map(); const current = this[TIMERS_AGGREGATES_SYMBOL].get(label); @@ -211,10 +142,7 @@ class WebpackLogger { this[TIMERS_AGGREGATES_SYMBOL].set(label, time); } - /** - * @param {string=} label label - */ - timeAggregateEnd(label) { + timeAggregateEnd(label?: string) { if (this[TIMERS_AGGREGATES_SYMBOL] === undefined) return; const time = this[TIMERS_AGGREGATES_SYMBOL].get(label); if (time === undefined) return; @@ -223,4 +151,4 @@ class WebpackLogger { } } -module.exports.Logger = WebpackLogger; +export { WebpackLogger as Logger }; diff --git a/client-src/modules/logger/createConsoleLogger.js b/client-src/modules/logger/createConsoleLogger.ts similarity index 62% rename from client-src/modules/logger/createConsoleLogger.js rename to client-src/modules/logger/createConsoleLogger.ts index 7c0e0b1..e47447a 100644 --- a/client-src/modules/logger/createConsoleLogger.js +++ b/client-src/modules/logger/createConsoleLogger.ts @@ -8,54 +8,19 @@ * https://github.com/webpack/webpack-dev-server/blob/main/LICENSE */ -// @ts-nocheck -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ +import { + LogType, + type Args, + type FilterFunction, + type FilterItemTypes, + type LoggerOptions, + type LoggingFunction, + type LogTypeEnum, +} from '../types'; -'use strict'; - -const { LogType } = require('./Logger'); - -/** @typedef {import("../../declarations/WebpackOptions").FilterItemTypes} FilterItemTypes */ -/** @typedef {import("../../declarations/WebpackOptions").FilterTypes} FilterTypes */ -/** @typedef {import("./Logger").LogTypeEnum} LogTypeEnum */ -/** @typedef {import("./Logger").Args} Args */ - -/** @typedef {(item: string) => boolean} FilterFunction */ -/** @typedef {(value: string, type: LogTypeEnum, args?: Args) => void} LoggingFunction */ - -/** - * @typedef {object} LoggerConsole - * @property {() => void} clear - * @property {() => void} trace - * @property {(...args: Args) => void} info - * @property {(...args: Args) => void} log - * @property {(...args: Args) => void} warn - * @property {(...args: Args) => void} error - * @property {(...args: Args) => void=} debug - * @property {(...args: Args) => void=} group - * @property {(...args: Args) => void=} groupCollapsed - * @property {(...args: Args) => void=} groupEnd - * @property {(...args: Args) => void=} status - * @property {(...args: Args) => void=} profile - * @property {(...args: Args) => void=} profileEnd - * @property {(...args: Args) => void=} logTime - */ - -/** - * @typedef {object} LoggerOptions - * @property {false | true | "none" | "error" | "warn" | "info" | "log" | "verbose"} level loglevel - * @property {FilterTypes | boolean} debug filter for debug logging - * @property {LoggerConsole} console the console to log to - */ - -/** - * @param {FilterItemTypes} item an input item - * @returns {FilterFunction | undefined} filter function - */ -const filterToFunction = (item) => { +const filterToFunction = ( + item: FilterItemTypes, +): FilterFunction | undefined => { if (typeof item === 'string') { const regExp = new RegExp( `[\\\\/]${item.replace(/[-[\]{}()*+?.\\^$|]/g, '\\$&')}([\\\\/]|$|!|\\?)`, @@ -73,10 +38,7 @@ const filterToFunction = (item) => { } }; -/** - * @enum {number} - */ -const LogLevel = { +const LogLevel: Record = { none: 6, false: 6, error: 5, @@ -87,29 +49,19 @@ const LogLevel = { verbose: 1, }; -/** - * @param {LoggerOptions} options options object - * @returns {LoggingFunction} logging function - */ -module.exports = ({ level = 'info', debug = false, console }) => { - const debugFilters = - /** @type {FilterFunction[]} */ - ( - typeof debug === 'boolean' - ? [() => debug] - : /** @type {FilterItemTypes[]} */ ([ - ...(Array.isArray(debug) ? debug : [debug]), - ]).map(filterToFunction) - ); +export default ({ + level = 'info', + debug = false, + console, +}: LoggerOptions): LoggingFunction => { + const debugFilters = ( + typeof debug === 'boolean' + ? [() => debug] + : [...(Array.isArray(debug) ? debug : [debug])].map(filterToFunction) + ) as FilterFunction[]; const loglevel = LogLevel[`${level}`] || 0; - /** - * @param {string} name name of the logger - * @param {LogTypeEnum} type type of the log entry - * @param {Args=} args arguments of the log entry - * @returns {void} - */ - const logger = (name, type, args) => { + const logger = (name: string, type: LogTypeEnum, args?: Args): void => { const labeledArgs = () => { if (Array.isArray(args)) { if (args.length > 0 && typeof args[0] === 'string') { @@ -176,9 +128,7 @@ module.exports = ({ level = 'info', debug = false, console }) => { break; case LogType.time: { if (!debug && loglevel > LogLevel.log) return; - const [label, start, end] = - /** @type {[string, number, number]} */ - (args); + const [label, start, end] = args as [string, number, number]; const ms = start * 1000 + end / 1000000; const msg = `[${name}] ${label}: ${ms} ms`; if (typeof console.logTime === 'function') { diff --git a/client-src/modules/logger/index.js b/client-src/modules/logger/index.ts similarity index 89% rename from client-src/modules/logger/index.js rename to client-src/modules/logger/index.ts index 7de1ea9..a4fb141 100644 --- a/client-src/modules/logger/index.js +++ b/client-src/modules/logger/index.ts @@ -8,6 +8,4 @@ * https://github.com/webpack/webpack-dev-server/blob/main/LICENSE */ -// @ts-nocheck -// @ts-expect-error export { default } from './runtime'; diff --git a/client-src/modules/logger/runtime.js b/client-src/modules/logger/runtime.ts similarity index 51% rename from client-src/modules/logger/runtime.js rename to client-src/modules/logger/runtime.ts index bb0646c..93fb935 100644 --- a/client-src/modules/logger/runtime.js +++ b/client-src/modules/logger/runtime.ts @@ -8,45 +8,43 @@ * https://github.com/webpack/webpack-dev-server/blob/main/LICENSE */ -// @ts-nocheck +import { SyncBailHook } from './tapable'; +import { Logger } from './Logger'; +import createConsoleLogger from './createConsoleLogger'; +import type { LoggerOptions } from '../types'; -'use strict'; - -const { SyncBailHook } = require('tapable'); -const { Logger } = require('./Logger'); -const createConsoleLogger = require('./createConsoleLogger'); - -/** @type {createConsoleLogger.LoggerOptions} */ const currentDefaultLoggerOptions = { level: 'info', debug: false, console, -}; +} as LoggerOptions; + let currentDefaultLogger = createConsoleLogger(currentDefaultLoggerOptions); -/** - * @param {createConsoleLogger.LoggerOptions} options new options, merge with old options - * @returns {void} - */ -module.exports.configureDefaultLogger = (options) => { +const configureDefaultLogger = (options: LoggerOptions): void => { Object.assign(currentDefaultLoggerOptions, options); currentDefaultLogger = createConsoleLogger(currentDefaultLoggerOptions); }; -/** - * @param {string} name name of the logger - * @returns {Logger} a logger - */ -module.exports.getLogger = (name) => +const getLogger = (name: string): Logger => new Logger( (type, args) => { - if (module.exports.hooks.log.call(name, type, args) === undefined) { + if (hooks.log.call(name, type, args) === undefined) { currentDefaultLogger(name, type, args); } }, - (childName) => module.exports.getLogger(`${name}/${childName}`), + (childName) => getLogger(`${name}/${childName}`), ); -module.exports.hooks = { +const hooks = { + // @ts-ignore log: new SyncBailHook(['origin', 'type', 'args']), }; + +export { getLogger, configureDefaultLogger, hooks }; + +export default { + getLogger, + configureDefaultLogger, + hooks, +}; diff --git a/client-src/modules/logger/tapable.js b/client-src/modules/logger/tapable.ts similarity index 82% rename from client-src/modules/logger/tapable.js rename to client-src/modules/logger/tapable.ts index 8ca2ac9..ab5968f 100644 --- a/client-src/modules/logger/tapable.js +++ b/client-src/modules/logger/tapable.ts @@ -8,11 +8,6 @@ * https://github.com/webpack/webpack-dev-server/blob/main/LICENSE */ -// @ts-nocheck -/** - * @returns {SyncBailHook} mocked sync bail hook - * @constructor - */ function SyncBailHook() { return { call() {}, diff --git a/client-src/modules/logger/truncateArgs.js b/client-src/modules/logger/truncateArgs.js deleted file mode 100644 index 51e1e68..0000000 --- a/client-src/modules/logger/truncateArgs.js +++ /dev/null @@ -1,90 +0,0 @@ -/** - * The following code is modified based on - * https://github.com/webpack/webpack-dev-server - * - * MIT Licensed - * Author Tobias Koppers @sokra - * Copyright (c) JS Foundation and other contributors - * https://github.com/webpack/webpack-dev-server/blob/main/LICENSE - */ - -// @ts-nocheck - -'use strict'; - -/** - * @param {number[]} array array of numbers - * @returns {number} sum of all numbers in array - */ -const arraySum = (array) => { - let sum = 0; - for (const item of array) sum += item; - return sum; -}; - -/** - * @param {EXPECTED_ANY[]} args items to be truncated - * @param {number} maxLength maximum length of args including spaces between - * @returns {string[]} truncated args - */ -const truncateArgs = (args, maxLength) => { - const lengths = args.map((a) => `${a}`.length); - const availableLength = maxLength - lengths.length + 1; - - if (availableLength > 0 && args.length === 1) { - if (availableLength >= args[0].length) { - return args; - } else if (availableLength > 3) { - return [`...${args[0].slice(-availableLength + 3)}`]; - } - return [args[0].slice(-availableLength)]; - } - - // Check if there is space for at least 4 chars per arg - if (availableLength < arraySum(lengths.map((i) => Math.min(i, 6)))) { - // remove args - if (args.length > 1) return truncateArgs(args.slice(0, -1), maxLength); - return []; - } - - let currentLength = arraySum(lengths); - - // Check if all fits into maxLength - if (currentLength <= availableLength) return args; - - // Try to remove chars from the longest items until it fits - while (currentLength > availableLength) { - const maxLength = Math.max(...lengths); - const shorterItems = lengths.filter((l) => l !== maxLength); - const nextToMaxLength = - shorterItems.length > 0 ? Math.max(...shorterItems) : 0; - const maxReduce = maxLength - nextToMaxLength; - let maxItems = lengths.length - shorterItems.length; - let overrun = currentLength - availableLength; - for (let i = 0; i < lengths.length; i++) { - if (lengths[i] === maxLength) { - const reduce = Math.min(Math.floor(overrun / maxItems), maxReduce); - lengths[i] -= reduce; - currentLength -= reduce; - overrun -= reduce; - maxItems--; - } - } - } - - // Return args reduced to length in lengths - return args.map((a, i) => { - const str = `${a}`; - const length = lengths[i]; - if (str.length === length) { - return str; - } else if (length > 5) { - return `...${str.slice(-length + 3)}`; - } else if (length > 0) { - return str.slice(-length); - } - return ''; - }); -}; - -module.exports = truncateArgs; diff --git a/client-src/modules/sockjs-client/index.js b/client-src/modules/sockjs-client/index.ts similarity index 100% rename from client-src/modules/sockjs-client/index.js rename to client-src/modules/sockjs-client/index.ts diff --git a/client-src/modules/types.ts b/client-src/modules/types.ts new file mode 100644 index 0000000..95e7654 --- /dev/null +++ b/client-src/modules/types.ts @@ -0,0 +1,63 @@ +import type { FilterTypes } from '@rspack/core'; + +// biome-ignore lint/suspicious/noExplicitAny: expected any +export type EXPECTED_ANY = any; + +export type FilterFunction = (item: string) => boolean; +export type LoggingFunction = ( + value: string, + type: LogTypeEnum, + args?: Args, +) => void; + +export type LoggerConsole = { + clear: () => void; + trace: () => void; + info: (...args: Args) => void; + log: (...args: Args) => void; + warn: (...args: Args) => void; + error: (...args: Args) => void; + debug?: (...args: Args) => void; + group?: (...args: Args) => void; + groupCollapsed?: (...args: Args) => void; + groupEnd?: (...args: Args) => void; + status?: (...args: Args) => void; + profile?: (...args: Args) => void; + profileEnd?: (...args: Args) => void; + logTime?: (...args: Args) => void; +}; + +export type LoggerOptions = { + level: false | true | 'none' | 'error' | 'warn' | 'info' | 'log' | 'verbose'; + debug: FilterTypes | boolean; + console: LoggerConsole; +}; + +export const LogType = Object.freeze({ + error: 'error', // message, c style arguments + warn: 'warn', // message, c style arguments + info: 'info', // message, c style arguments + log: 'log', // message, c style arguments + debug: 'debug', // message, c style arguments + + trace: 'trace', // no arguments + + group: 'group', // [label] + groupCollapsed: 'groupCollapsed', // [label] + groupEnd: 'groupEnd', // [label] + + profile: 'profile', // [profileName] + profileEnd: 'profileEnd', // [profileName] + + time: 'time', // name, time as [seconds, nanoseconds] + + clear: 'clear', // no arguments + status: 'status', // message, arguments +}); + +export type LogTypeEnum = (typeof LogType)[keyof typeof LogType]; +export type TimersMap = Map; + +export type Args = EXPECTED_ANY[]; + +export type { FilterItemTypes } from '@rspack/core'; diff --git a/client-src/utils/ansiHTML.ts b/client-src/utils/ansiHTML.ts index 5637171..93d546e 100644 --- a/client-src/utils/ansiHTML.ts +++ b/client-src/utils/ansiHTML.ts @@ -202,7 +202,6 @@ export default function ansiHTML(text: string) { /** * Customize colors. - * @param {Object} colors reference to _defColors */ ansiHTML.setColors = (colors: typeof _defColors) => { if (typeof colors !== 'object') { @@ -258,7 +257,6 @@ ansiHTML.reset = () => { /** * Expose tags, including open and close. - * @type {Object} */ ansiHTML.tags = {} as AnsiHtmlTags; diff --git a/client-src/utils/log.ts b/client-src/utils/log.ts index a210e9e..8fac07e 100644 --- a/client-src/utils/log.ts +++ b/client-src/utils/log.ts @@ -8,7 +8,7 @@ * https://github.com/webpack/webpack-dev-server/blob/main/LICENSE */ -import type { LoggerOptions } from '../modules/logger/createConsoleLogger'; +import type { LoggerOptions } from '../modules/types'; import logger from '../modules/logger/index'; import { LogLevel } from '../type'; diff --git a/scripts/build-client-modules.cjs b/scripts/build-client-modules.cjs index 2ee0809..f8fe29d 100644 --- a/scripts/build-client-modules.cjs +++ b/scripts/build-client-modules.cjs @@ -35,6 +35,10 @@ const baseForModules = { // experiments: { // outputModule: true, // }, + resolve: { + extensions: ['.js', '.ts'], + tsConfig: path.resolve(__dirname, '../tsconfig.client.json'), + }, output: { path: modulesDir, ...library, @@ -50,16 +54,32 @@ const baseForModules = { }, ], }, + { + test: /\.ts$/, + loader: 'builtin:swc-loader', + options: { + jsc: { + parser: { + syntax: 'typescript', + }, + }, + }, + type: 'javascript/auto', + }, ], }, }; const configs = [ merge(baseForModules, { - entry: path.resolve(__dirname, '../client-src/modules/logger/index.js'), + entry: path.resolve(__dirname, '../client-src/modules/logger/index.ts'), output: { filename: 'logger/index.js', }, + resolve: { + extensions: ['.js', '.ts'], + tsConfig: path.resolve(__dirname, '../tsconfig.client.json'), + }, module: { rules: [ { @@ -70,6 +90,18 @@ const configs = [ }, ], }, + { + test: /\.ts$/, + loader: 'builtin:swc-loader', + options: { + jsc: { + parser: { + syntax: 'typescript', + }, + }, + }, + type: 'javascript/auto', + }, ], }, plugins: [ @@ -86,7 +118,7 @@ const configs = [ merge(baseForModules, { entry: path.resolve( __dirname, - '../client-src/modules/sockjs-client/index.js', + '../client-src/modules/sockjs-client/index.ts', ), output: { filename: 'sockjs-client/index.js', diff --git a/src/config.ts b/src/config.ts index 1b4bcac..2954bd1 100644 --- a/src/config.ts +++ b/src/config.ts @@ -1,16 +1,14 @@ -import type { DevServer } from '@rspack/core'; -import type { Service as BonjourOptions } from 'bonjour-service'; -import type { Options as ConnectHistoryApiFallbackOptions } from 'connect-history-api-fallback'; import type { + BonjourOptions, ClientConfiguration, + ConnectHistoryApiFallbackOptions, + DevServer, NormalizedStatic, Open, ServerConfiguration, WatchFiles, WebSocketServerConfiguration, -} from './server'; - -export type { DevServer }; +} from './types'; export interface ResolvedDevServer extends DevServer { port: number | string; diff --git a/src/server.ts b/src/server.ts index 77fe922..9a5e4b3 100644 --- a/src/server.ts +++ b/src/server.ts @@ -16,267 +16,70 @@ import * as fs from 'graceful-fs'; import * as ipaddr from 'ipaddr.js'; import { validate } from 'schema-utils'; import schema from './options.json'; - -// Type definition matching open package's Options type -// (Cannot import directly from ES module in CommonJS context) -type OpenOptions = { - readonly wait?: boolean; - readonly background?: boolean; - readonly newInstance?: boolean; - readonly app?: OpenApp | readonly OpenApp[]; - readonly allowNonzeroExitCode?: boolean; -}; - -import type { - Server as HTTPServer, - IncomingMessage, - ServerResponse, -} from 'node:http'; -import type { Socket } from 'node:net'; -import type { AddressInfo } from 'node:net'; -import type { NetworkInterfaceInfo } from 'node:os'; import type { + BasicApplication, + ExpressApplication, + HTTPServer, + Response, + Request, + Host, + Port, + DevMiddlewareOptions, + ConnectHistoryApiFallbackOptions, + BonjourOptions, + WatchFiles, + Static, + ServerType, + ServerConfiguration, + WebSocketServerConfiguration, + ProxyConfigArray, + Open, + ClientConfiguration, + Middleware, + DevMiddlewareContext, + OverlayMessageOptions, Compiler, - DevServer, MultiCompiler, - MultiStats, + FSWatcher, + EXPECTED_ANY, + RequestHandler, + Socket, + Bonjour, + WebSocketServerImplementation, Stats, - StatsCompilation, + MultiStats, + DevServer, + Schema, StatsOptions, -} from '@rspack/core'; -import type { Bonjour, Service as BonjourOptions } from 'bonjour-service'; -import type { FSWatcher, WatchOptions } from 'chokidar'; -import type { Options as ConnectHistoryApiFallbackOptions } from 'connect-history-api-fallback'; -import type { - Application as ExpressApplication, - ErrorRequestHandler as ExpressErrorRequestHandler, - Request as ExpressRequest, - RequestHandler as ExpressRequestHandler, - Response as ExpressResponse, -} from 'express'; -import type { - Options as HttpProxyMiddlewareOptions, - Filter as HttpProxyMiddlewareOptionsFilter, - RequestHandler, -} from 'http-proxy-middleware'; -import type { IPv6 } from 'ipaddr.js'; -import type { Schema } from 'schema-utils/declarations/validate'; -import type { Options as ServeIndexOptions } from 'serve-index'; -import type { ServeStaticOptions } from 'serve-static'; - -// biome-ignore lint/suspicious/noExplicitAny: expected any -export type EXPECTED_ANY = any; - -export type NextFunction = (err?: EXPECTED_ANY) => void; -export type SimpleHandleFunction = ( - req: IncomingMessage, - res: ServerResponse, -) => void; -export type NextHandleFunction = ( - req: IncomingMessage, - res: ServerResponse, - next: NextFunction, -) => void; -export type ErrorHandleFunction = ( - err: EXPECTED_ANY, - req: IncomingMessage, - res: ServerResponse, - next: NextFunction, -) => void; -export type HandleFunction = - | SimpleHandleFunction - | NextHandleFunction - | ErrorHandleFunction; - -export type ServerOptions = import('https').ServerOptions & { - spdy?: { - plain?: boolean; - ssl?: boolean; - 'x-forwarded-for'?: string; - protocol?: string; - protocols?: string[]; - }; -}; - -// type-level helpers, inferred as util types -export type Request = - T extends ExpressApplication ? ExpressRequest : IncomingMessage; -export type Response = - T extends ExpressApplication ? ExpressResponse : ServerResponse; - -export type DevMiddlewareOptions< - T extends Request, - U extends Response, -> = import('webpack-dev-middleware').Options; -export type DevMiddlewareContext< - T extends Request, - U extends Response, -> = import('webpack-dev-middleware').Context; - -export type Host = 'local-ip' | 'local-ipv4' | 'local-ipv6' | string; -export type Port = number | string | 'auto'; - -export interface WatchFiles { - paths: string | string[]; - options?: WatchOptions & { - aggregateTimeout?: number; - ignored?: WatchOptions['ignored']; - poll?: number | boolean; - }; -} - -export interface Static { - directory?: string; - publicPath?: string | string[]; - serveIndex?: boolean | ServeIndexOptions; - staticOptions?: ServeStaticOptions; - watch?: - | boolean - | (WatchOptions & { - aggregateTimeout?: number; - ignored?: WatchOptions['ignored']; - poll?: number | boolean; - }); -} - -export interface NormalizedStatic { - directory: string; - publicPath: string[]; - serveIndex: false | ServeIndexOptions; - staticOptions: ServeStaticOptions; - watch: false | WatchOptions; -} - -export type ServerType< - A extends BasicApplication = ExpressApplication, - S extends import('http').Server = import('http').Server, -> = - | 'http' - | 'https' - | 'spdy' - | 'http2' - | string - | ((serverOptions: ServerOptions, application: A) => S); - -export interface ServerConfiguration< - A extends BasicApplication = ExpressApplication, - S extends import('http').Server = import('http').Server, -> { - type?: ServerType; - options?: ServerOptions; -} - -export interface WebSocketServerConfiguration { - type?: 'sockjs' | 'ws' | string | (() => WebSocketServerConfiguration); - options?: Record; -} - -export 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 }; - -export type WebSocketServer = - | import('ws').WebSocketServer - | (import('sockjs').Server & { - close: import('ws').WebSocketServer['close']; - }); - -export interface WebSocketServerImplementation { - implementation: WebSocketServer; - clients: ClientConnection[]; -} - -export type ByPass< - Req = Request, - Res = Response, - ProxyConfig = ProxyConfigArrayItem, -> = (req: Req, res: Res, proxyConfig: ProxyConfig) => void; - -export type ProxyConfigArrayItem = { - path?: HttpProxyMiddlewareOptionsFilter; - context?: HttpProxyMiddlewareOptionsFilter; - bypass?: ByPass; -} & HttpProxyMiddlewareOptions; - -export type ProxyConfigArray = Array< - | ProxyConfigArrayItem - | (( - req?: Request | undefined, - res?: Response | undefined, - next?: NextFunction | undefined, - ) => ProxyConfigArrayItem) ->; - -export interface OpenApp { - name?: string; - arguments?: string[]; -} - -export interface Open { - app?: string | string[] | OpenApp; - target?: string | string[]; -} - -export interface NormalizedOpen { - target: string; - options: OpenOptions; -} - -export interface WebSocketURL { - hostname?: string; - password?: string; - pathname?: string; - port?: number | string; - protocol?: string; - username?: string; -} - -export interface ClientConfiguration { - logging?: 'log' | 'info' | 'warn' | 'error' | 'none' | 'verbose'; - overlay?: - | boolean - | { - warnings?: OverlayMessageOptions; - errors?: OverlayMessageOptions; - runtimeErrors?: OverlayMessageOptions; - }; - progress?: boolean; - reconnect?: boolean | number; - webSocketTransport?: 'ws' | 'sockjs' | string; - webSocketURL?: string | WebSocketURL; -} - -export type Headers = - | Array<{ key: string; value: string }> - | Record; - -export type MiddlewareHandler = - T extends ExpressApplication - ? ExpressRequestHandler | ExpressErrorRequestHandler - : HandleFunction; - -export interface MiddlewareObject< - T extends BasicApplication = ExpressApplication, -> { - name?: string; - path?: string; - middleware: MiddlewareHandler; -} - -export type Middleware = - | MiddlewareObject - | MiddlewareHandler; - -export type BasicServer = import('net').Server | import('tls').Server; + NetworkInterfaceInfo, + WebSocketURL, + WatchOptions, + NormalizedStatic, + ServerOptions, + NormalizedOpen, + OpenOptions, + StatsCompilation, + NextFunction, + MiddlewareHandler, + ProxyConfigArrayItem, + ByPass, + ServeIndexOptions, + WebSocketServer, + ClientConnection, + IncomingMessage, + MiddlewareObject, + NextHandleFunction, + HandleFunction, + SimpleHandleFunction, + OpenApp, + AddressInfo, + IPv6, + Headers, +} from './types'; export interface Configuration< A extends BasicApplication = ExpressApplication, - S extends import('http').Server = import('http').Server, + S extends HTTPServer = HTTPServer, > { ipc?: boolean | string; host?: Host; @@ -343,8 +146,6 @@ const memoize = (fn: FunctionReturning): FunctionReturning => { const getExpress = memoize(() => require('express')); -type OverlayMessageOptions = boolean | ((error: Error) => void); - const encodeOverlaySettings = ( setting?: OverlayMessageOptions, ): undefined | string | boolean => { @@ -352,24 +153,9 @@ const encodeOverlaySettings = ( ? encodeURIComponent(setting.toString()) : setting; }; -// TypeScript overloads for express-like use -function useFn(fn: NextHandleFunction): BasicApplication; -function useFn(fn: HandleFunction): BasicApplication; -function useFn(route: string, fn: NextHandleFunction): BasicApplication; -function useFn(route: string, fn: HandleFunction): BasicApplication; -function useFn( - routeOrFn: string | NextHandleFunction | HandleFunction, - fn?: NextHandleFunction | HandleFunction, -): BasicApplication { - return {} as BasicApplication; -} const DEFAULT_ALLOWED_PROTOCOLS = /^(file|.+-extension):/i; -type BasicApplication = { - use: typeof useFn; -}; - function isMultiCompiler( compiler: Compiler | MultiCompiler, ): compiler is MultiCompiler { @@ -1878,10 +1664,7 @@ class Server< }); this.compiler.hooks.done.tap( 'webpack-dev-server', - /** - * @param {Stats | MultiStats} stats stats - */ - (stats: Stats | MultiStats) => { + (stats: Stats | MultiStats): void => { if (this.webSocketServer) { this.sendStats(this.webSocketServer.clients, this.getStats(stats)); } @@ -1890,10 +1673,6 @@ class Server< ); } - /** - * @private - * @returns {void} - */ setupWatchStaticFiles(): void { const watchFiles = this.options.static as NormalizedStatic[]; @@ -2222,17 +2001,11 @@ class Server< this.webSocketProxies.push(proxyMiddleware); } - /** - * @param {Request} req request - * @param {Response} res response - * @param {NextFunction} next next function - * @returns {Promise} - */ const handler = async ( req: Request, res: Response, next: NextFunction, - ) => { + ): Promise => { if (typeof proxyConfigOrCallback === 'function') { const newProxyConfig = proxyConfigOrCallback(req, res, next); @@ -2491,15 +2264,9 @@ class Server< }); }); - (this.server as S).on( - 'error', - /** - * @param {Error} error error - */ - (error) => { - throw error; - }, - ); + (this.server as S).on('error', (error: Error) => { + throw error; + }); } createWebSocketServer() { @@ -2508,11 +2275,7 @@ class Server< (this.webSocketServer?.implementation as WebSocketServer).on( 'connection', - /** - * @param {ClientConnection} client client - * @param {IncomingMessage} request request - */ - (client, request) => { + (client: ClientConnection, request: IncomingMessage) => { const headers = typeof request !== 'undefined' ? (request.headers as { [key: string]: string | undefined }) diff --git a/src/servers/BaseServer.ts b/src/servers/BaseServer.ts index c208bd3..aecc40c 100644 --- a/src/servers/BaseServer.ts +++ b/src/servers/BaseServer.ts @@ -9,7 +9,7 @@ */ import type Server from '../server'; -import type { ClientConnection } from '../server'; +import type { ClientConnection } from '../types'; // base class that users should extend if they are making their own // server implementation @@ -17,9 +17,6 @@ class BaseServer { server: Server; clients: ClientConnection[]; - /** - * @param {Server} server server - */ constructor(server: Server) { this.server = server; this.clients = []; diff --git a/src/servers/SockJSServer.ts b/src/servers/SockJSServer.ts index 25f9e5c..32312d0 100644 --- a/src/servers/SockJSServer.ts +++ b/src/servers/SockJSServer.ts @@ -14,7 +14,7 @@ import type { ClientConnection, EXPECTED_ANY, WebSocketServerConfiguration, -} from '../server'; +} from '../types'; import BaseServer from './BaseServer'; // Workaround for sockjs@~0.3.19 @@ -25,9 +25,6 @@ import BaseServer from './BaseServer'; const { decorateConnection } = SockjsSession.prototype; - /** - * @param {import("http").IncomingMessage} req request - */ // eslint-disable-next-line func-names SockjsSession.prototype.decorateConnection = function ( req: import('http').IncomingMessage, @@ -50,9 +47,6 @@ class SockJSServer extends BaseServer { implementation: sockjs.Server & { close?: (callback: () => void) => void }; // options has: error (function), debug (function), server (http/s server), path (string) - /** - * @param {Server} server server - */ constructor(server: Server) { super(server); @@ -60,11 +54,6 @@ class SockJSServer extends BaseServer { this.server.options.webSocketServer as WebSocketServerConfiguration ).options as NonNullable; - /** - * Get sockjs URL - * @param {NonNullable} options options - * @returns {string} sockjs URL - */ const getSockjsUrl = ( options: NonNullable, ): string => { @@ -91,11 +80,6 @@ class SockJSServer extends BaseServer { }, }); - /** - * Get prefix - * @param {sockjs.ServerOptions & { path?: string }} options options - * @returns {string | undefined} prefix - */ const getPrefix = ( options: sockjs.ServerOptions & { path?: string }, ): string | undefined => { diff --git a/src/servers/WebsocketServer.ts b/src/servers/WebsocketServer.ts index b9de59f..441a61c 100644 --- a/src/servers/WebsocketServer.ts +++ b/src/servers/WebsocketServer.ts @@ -10,7 +10,7 @@ import WebSocket from 'ws'; import type Server from '../server'; -import type { ClientConnection, WebSocketServerConfiguration } from '../server'; +import type { ClientConnection, WebSocketServerConfiguration } from '../types'; import BaseServer from './BaseServer'; class WebsocketServer extends BaseServer { @@ -18,9 +18,6 @@ class WebsocketServer extends BaseServer { implementation: WebSocket.Server; - /** - * @param {Server} server server - */ constructor(server: Server) { super(server); diff --git a/src/types.ts b/src/types.ts new file mode 100644 index 0000000..acc41db --- /dev/null +++ b/src/types.ts @@ -0,0 +1,282 @@ +import type { + Server as HTTPServer, + IncomingMessage, + ServerResponse, +} from 'node:http'; +export type { HTTPServer, IncomingMessage }; +export type { Socket } from 'node:net'; +export type { AddressInfo } from 'node:net'; +export type { NetworkInterfaceInfo } from 'node:os'; +export type { + Compiler, + DevServer, + MultiCompiler, + MultiStats, + Stats, + StatsCompilation, + StatsOptions, +} from '@rspack/core'; +import type { Bonjour, Service as BonjourOptions } from 'bonjour-service'; +export type { Bonjour, BonjourOptions }; +import type { FSWatcher, WatchOptions } from 'chokidar'; +export type { FSWatcher, WatchOptions }; +import type { Options as ConnectHistoryApiFallbackOptions } from 'connect-history-api-fallback'; +export type { ConnectHistoryApiFallbackOptions }; +import type { + Application as ExpressApplication, + ErrorRequestHandler as ExpressErrorRequestHandler, + Request as ExpressRequest, + RequestHandler as ExpressRequestHandler, + Response as ExpressResponse, +} from 'express'; + +export type { ExpressApplication }; +import type { + Options as HttpProxyMiddlewareOptions, + Filter as HttpProxyMiddlewareOptionsFilter, + RequestHandler, +} from 'http-proxy-middleware'; +export type { RequestHandler }; +export type { IPv6 } from 'ipaddr.js'; +export type { Schema } from 'schema-utils/declarations/validate'; +import type { Options as ServeIndexOptions } from 'serve-index'; +export type { ServeIndexOptions }; +import type { ServeStaticOptions } from 'serve-static'; + +// biome-ignore lint/suspicious/noExplicitAny: expected any +export type EXPECTED_ANY = any; + +export type NextFunction = (err?: EXPECTED_ANY) => void; +export type SimpleHandleFunction = ( + req: IncomingMessage, + res: ServerResponse, +) => void; +export type NextHandleFunction = ( + req: IncomingMessage, + res: ServerResponse, + next: NextFunction, +) => void; +export type ErrorHandleFunction = ( + err: EXPECTED_ANY, + req: IncomingMessage, + res: ServerResponse, + next: NextFunction, +) => void; +export type HandleFunction = + | SimpleHandleFunction + | NextHandleFunction + | ErrorHandleFunction; + +export type ServerOptions = import('https').ServerOptions & { + spdy?: { + plain?: boolean; + ssl?: boolean; + 'x-forwarded-for'?: string; + protocol?: string; + protocols?: string[]; + }; +}; + +// type-level helpers, inferred as util types +export type Request = + T extends ExpressApplication ? ExpressRequest : IncomingMessage; +export type Response = + T extends ExpressApplication ? ExpressResponse : ServerResponse; + +export type DevMiddlewareOptions< + T extends Request, + U extends Response, +> = import('webpack-dev-middleware').Options; +export type DevMiddlewareContext< + T extends Request, + U extends Response, +> = import('webpack-dev-middleware').Context; + +export type Host = 'local-ip' | 'local-ipv4' | 'local-ipv6' | string; +export type Port = number | string | 'auto'; + +export interface WatchFiles { + paths: string | string[]; + options?: WatchOptions & { + aggregateTimeout?: number; + ignored?: WatchOptions['ignored']; + poll?: number | boolean; + }; +} + +export interface Static { + directory?: string; + publicPath?: string | string[]; + serveIndex?: boolean | ServeIndexOptions; + staticOptions?: ServeStaticOptions; + watch?: + | boolean + | (WatchOptions & { + aggregateTimeout?: number; + ignored?: WatchOptions['ignored']; + poll?: number | boolean; + }); +} + +export interface NormalizedStatic { + directory: string; + publicPath: string[]; + serveIndex: false | ServeIndexOptions; + staticOptions: ServeStaticOptions; + watch: false | WatchOptions; +} + +export type ServerType< + A extends BasicApplication = ExpressApplication, + S extends import('http').Server = import('http').Server, +> = + | 'http' + | 'https' + | 'spdy' + | 'http2' + | string + | ((serverOptions: ServerOptions, application: A) => S); + +export interface ServerConfiguration< + A extends BasicApplication = ExpressApplication, + S extends import('http').Server = import('http').Server, +> { + type?: ServerType; + options?: ServerOptions; +} + +export interface WebSocketServerConfiguration { + type?: 'sockjs' | 'ws' | string | (() => WebSocketServerConfiguration); + options?: Record; +} + +export 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 }; + +export type WebSocketServer = + | import('ws').WebSocketServer + | (import('sockjs').Server & { + close: import('ws').WebSocketServer['close']; + }); + +export interface WebSocketServerImplementation { + implementation: WebSocketServer; + clients: ClientConnection[]; +} + +export type ByPass< + Req = Request, + Res = Response, + ProxyConfig = ProxyConfigArrayItem, +> = (req: Req, res: Res, proxyConfig: ProxyConfig) => void; + +export type ProxyConfigArrayItem = { + path?: HttpProxyMiddlewareOptionsFilter; + context?: HttpProxyMiddlewareOptionsFilter; + bypass?: ByPass; +} & HttpProxyMiddlewareOptions; + +export type ProxyConfigArray = Array< + | ProxyConfigArrayItem + | (( + req?: Request | undefined, + res?: Response | undefined, + next?: NextFunction | undefined, + ) => ProxyConfigArrayItem) +>; + +export interface OpenApp { + name?: string; + arguments?: string[]; +} + +export interface Open { + app?: string | string[] | OpenApp; + target?: string | string[]; +} + +export interface NormalizedOpen { + target: string; + options: OpenOptions; +} + +export interface WebSocketURL { + hostname?: string; + password?: string; + pathname?: string; + port?: number | string; + protocol?: string; + username?: string; +} + +export interface ClientConfiguration { + logging?: 'log' | 'info' | 'warn' | 'error' | 'none' | 'verbose'; + overlay?: + | boolean + | { + warnings?: OverlayMessageOptions; + errors?: OverlayMessageOptions; + runtimeErrors?: OverlayMessageOptions; + }; + progress?: boolean; + reconnect?: boolean | number; + webSocketTransport?: 'ws' | 'sockjs' | string; + webSocketURL?: string | WebSocketURL; +} + +export type Headers = + | Array<{ key: string; value: string }> + | Record; + +export type MiddlewareHandler = + T extends ExpressApplication + ? ExpressRequestHandler | ExpressErrorRequestHandler + : HandleFunction; + +export interface MiddlewareObject< + T extends BasicApplication = ExpressApplication, +> { + name?: string; + path?: string; + middleware: MiddlewareHandler; +} + +export type Middleware = + | MiddlewareObject + | MiddlewareHandler; + +export type BasicServer = import('net').Server | import('tls').Server; + +export type OverlayMessageOptions = boolean | ((error: Error) => void); + +// TypeScript overloads for express-like use +function useFn(fn: NextHandleFunction): BasicApplication; +function useFn(fn: HandleFunction): BasicApplication; +function useFn(route: string, fn: NextHandleFunction): BasicApplication; +function useFn(route: string, fn: HandleFunction): BasicApplication; +function useFn( + routeOrFn: string | NextHandleFunction | HandleFunction, + fn?: NextHandleFunction | HandleFunction, +): BasicApplication { + return {} as BasicApplication; +} + +export type BasicApplication = { + use: typeof useFn; +}; + +// Type definition matching open package's Options type +// (Cannot import directly from ES module in CommonJS context) +export type OpenOptions = { + readonly wait?: boolean; + readonly background?: boolean; + readonly newInstance?: boolean; + readonly app?: OpenApp | readonly OpenApp[]; + readonly allowNonzeroExitCode?: boolean; +};