Server.js 105 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699270027012702270327042705270627072708270927102711271227132714271527162717271827192720272127222723272427252726272727282729273027312732273327342735273627372738273927402741274227432744274527462747274827492750275127522753275427552756275727582759276027612762276327642765276627672768276927702771277227732774277527762777277827792780278127822783278427852786278727882789279027912792279327942795279627972798279928002801280228032804280528062807280828092810281128122813281428152816281728182819282028212822282328242825282628272828282928302831283228332834283528362837283828392840284128422843284428452846284728482849285028512852285328542855285628572858285928602861286228632864286528662867286828692870287128722873287428752876287728782879288028812882288328842885288628872888288928902891289228932894289528962897289828992900290129022903290429052906290729082909291029112912291329142915291629172918291929202921292229232924292529262927292829292930293129322933293429352936293729382939294029412942294329442945294629472948294929502951295229532954295529562957295829592960296129622963296429652966296729682969297029712972297329742975297629772978297929802981298229832984298529862987298829892990299129922993299429952996299729982999300030013002300330043005300630073008300930103011301230133014301530163017301830193020302130223023302430253026302730283029303030313032303330343035303630373038303930403041304230433044304530463047304830493050305130523053305430553056305730583059306030613062306330643065306630673068306930703071307230733074
  1. "use strict";
  2. Object.defineProperty(exports, "__esModule", {
  3. value: true
  4. });
  5. exports.default = void 0;
  6. var _nodeModule = require("node:module");
  7. var _nodeOs = _interopRequireDefault(require("node:os"));
  8. var _nodePath = _interopRequireDefault(require("node:path"));
  9. var _nodeUrl = _interopRequireDefault(require("node:url"));
  10. var _gracefulFs = _interopRequireDefault(require("graceful-fs"));
  11. var _ipaddr = _interopRequireDefault(require("ipaddr.js"));
  12. var _schemaUtils = require("schema-utils");
  13. var _options = _interopRequireDefault(require("./options.json"));
  14. function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
  15. // Named `cjsRequire` (not `require`) so it doesn't shadow the implicit CommonJS
  16. // `require` in the transpiled `dist/cjs` build, which would collide with the
  17. // `require("url")` that `babel-plugin-transform-import-meta` injects here.
  18. const cjsRequire = (0, _nodeModule.createRequire)(require('url').pathToFileURL(__filename).toString());
  19. /** @type {Promise<typeof import("webpack") | undefined> | undefined} */
  20. let webpackPeer;
  21. /**
  22. * Lazily load the optional `webpack` peer dependency.
  23. * @returns {Promise<typeof import("webpack") | undefined>} resolved webpack module, or undefined when not installed
  24. */
  25. function loadWebpackPeer() {
  26. webpackPeer ??= import("webpack").then(m => m.default).catch(() => undefined);
  27. return webpackPeer;
  28. }
  29. /** @typedef {import("schema-utils").Schema} Schema */
  30. /** @typedef {import("webpack").Compiler} Compiler */
  31. /** @typedef {import("webpack").MultiCompiler} MultiCompiler */
  32. /** @typedef {import("webpack").Configuration} WebpackConfiguration */
  33. /** @typedef {import("webpack").StatsOptions} StatsOptions */
  34. /** @typedef {import("webpack").StatsCompilation} StatsCompilation */
  35. /** @typedef {import("webpack").Stats} Stats */
  36. /** @typedef {import("webpack").MultiStats} MultiStats */
  37. /** @typedef {import("os").NetworkInterfaceInfo} NetworkInterfaceInfo */
  38. /** @typedef {import("chokidar").ChokidarOptions} WatchOptions */
  39. /** @typedef {import("chokidar").FSWatcher} FSWatcher */
  40. /** @typedef {import("connect-history-api-fallback").Options} ConnectHistoryApiFallbackOptions */
  41. /** @typedef {import("bonjour-service").Bonjour} Bonjour */
  42. /** @typedef {import("bonjour-service").Service} BonjourOptions */
  43. /** @typedef {import("http-proxy-middleware").RequestHandler} RequestHandler */
  44. /** @typedef {import("http-proxy-middleware").Options} HttpProxyMiddlewareOptions */
  45. /** @typedef {import("http-proxy-middleware").Filter} HttpProxyMiddlewareOptionsFilter */
  46. /** @typedef {import("serve-index").Options} ServeIndexOptions */
  47. /** @typedef {import("serve-static").ServeStaticOptions} ServeStaticOptions */
  48. /** @typedef {import("ipaddr.js").IPv4} IPv4 */
  49. /** @typedef {import("ipaddr.js").IPv6} IPv6 */
  50. /** @typedef {import("net").Socket} Socket */
  51. /** @typedef {import("http").Server} HTTPServer */
  52. /** @typedef {import("http").IncomingMessage} IncomingMessage */
  53. /** @typedef {import("http").ServerResponse} ServerResponse */
  54. /** @typedef {import("open").Options} OpenOptions */
  55. /** @typedef {import("express").Application} ExpressApplication */
  56. /** @typedef {import("express").RequestHandler} ExpressRequestHandler */
  57. /** @typedef {import("express").ErrorRequestHandler} ExpressErrorRequestHandler */
  58. /** @typedef {import("express").Request} ExpressRequest */
  59. /** @typedef {import("express").Response} ExpressResponse */
  60. // eslint-disable-next-line jsdoc/reject-any-type
  61. /** @typedef {any} EXPECTED_ANY */
  62. /** @typedef {(err?: EXPECTED_ANY) => void} NextFunction */
  63. /** @typedef {(req: IncomingMessage, res: ServerResponse) => void} SimpleHandleFunction */
  64. /** @typedef {(req: IncomingMessage, res: ServerResponse, next: NextFunction) => void} NextHandleFunction */
  65. /** @typedef {(err: EXPECTED_ANY, req: IncomingMessage, res: ServerResponse, next: NextFunction) => void} ErrorHandleFunction */
  66. /** @typedef {SimpleHandleFunction | NextHandleFunction | ErrorHandleFunction} HandleFunction */
  67. /** @typedef {import("https").ServerOptions} ServerOptions */
  68. /**
  69. * @template {BasicApplication} [T=ExpressApplication]
  70. * @typedef {T extends ExpressApplication ? ExpressRequest : IncomingMessage} Request
  71. */
  72. /**
  73. * @template {BasicApplication} [T=ExpressApplication]
  74. * @typedef {T extends ExpressApplication ? ExpressResponse : ServerResponse} Response
  75. */
  76. /**
  77. * @template {Request} T
  78. * @template {Response} U
  79. * @typedef {import("webpack-dev-middleware").Options<T, U>} DevMiddlewareOptions
  80. */
  81. /**
  82. * @template {Request} T
  83. * @template {Response} U
  84. * @typedef {import("webpack-dev-middleware").Context<T, U>} DevMiddlewareContext
  85. */
  86. /**
  87. * @typedef {"local-ip" | "local-ipv4" | "local-ipv6" | string} Host
  88. */
  89. /**
  90. * @typedef {number | string | "auto"} Port
  91. */
  92. /**
  93. * @typedef {object} WatchFiles
  94. * @property {string | string[]} paths paths
  95. * @property {(WatchOptions & { aggregateTimeout?: number, ignored?: WatchOptions["ignored"], poll?: number | boolean })=} options options
  96. */
  97. /**
  98. * @typedef {object} Static
  99. * @property {string=} directory directory
  100. * @property {(string | string[])=} publicPath public path
  101. * @property {(boolean | ServeIndexOptions)=} serveIndex serve index
  102. * @property {ServeStaticOptions=} staticOptions static options
  103. * @property {(boolean | WatchOptions & { aggregateTimeout?: number, ignored?: WatchOptions["ignored"], poll?: number | boolean })=} watch watch and watch options
  104. */
  105. /**
  106. * @typedef {object} NormalizedStatic
  107. * @property {string} directory
  108. * @property {string[]} publicPath
  109. * @property {false | ServeIndexOptions} serveIndex
  110. * @property {ServeStaticOptions} staticOptions
  111. * @property {false | WatchOptions} watch
  112. */
  113. /**
  114. * @template {BasicApplication} [A=ExpressApplication]
  115. * @template {BasicServer} [S=import("http").Server]
  116. * @typedef {"http" | "https" | "http2" | string | ((serverOptions: ServerOptions, application: A) => S)} ServerType
  117. */
  118. /**
  119. * @template {BasicApplication} [A=ExpressApplication]
  120. * @template {BasicServer} [S=import("http").Server]
  121. * @typedef {object} ServerConfiguration
  122. * @property {ServerType<A, S>=} type type
  123. * @property {ServerOptions=} options options
  124. */
  125. /**
  126. * @typedef {object} WebSocketServerConfiguration
  127. * @property {("ws" | string | (() => WebSocketServerConfiguration))=} type type
  128. * @property {Record<string, EXPECTED_ANY>=} options options
  129. */
  130. /**
  131. * @typedef {(import("ws").WebSocket & { send: import("ws").WebSocket["send"], terminate: import("ws").WebSocket["terminate"], ping: import("ws").WebSocket["ping"] }) & { isAlive?: boolean }} ClientConnection
  132. */
  133. /**
  134. * @typedef {import("ws").WebSocketServer & { close: import("ws").WebSocketServer["close"] }} WebSocketServer
  135. */
  136. /**
  137. * @typedef {{ implementation: WebSocketServer, clients: ClientConnection[] }} WebSocketServerImplementation
  138. */
  139. /**
  140. * @typedef {{ path?: HttpProxyMiddlewareOptionsFilter | undefined, context?: HttpProxyMiddlewareOptionsFilter | undefined } & HttpProxyMiddlewareOptions} ProxyConfigArrayItem
  141. */
  142. /**
  143. * @typedef {(ProxyConfigArrayItem | ((req?: Request | undefined, res?: Response | undefined, next?: NextFunction | undefined) => ProxyConfigArrayItem))[]} ProxyConfigArray
  144. */
  145. /**
  146. * @typedef {object} OpenApp
  147. * @property {string=} name
  148. * @property {string[]=} arguments
  149. */
  150. /**
  151. * @typedef {object} Open
  152. * @property {(string | string[] | OpenApp)=} app
  153. * @property {(string | string[])=} target target
  154. */
  155. /**
  156. * @typedef {object} NormalizedOpen
  157. * @property {string} target
  158. * @property {import("open").Options} options
  159. */
  160. /**
  161. * @typedef {object} WebSocketURL
  162. * @property {string=} hostname hostname
  163. * @property {string=} password password
  164. * @property {string=} pathname pathname
  165. * @property {(number | string)=} port port
  166. * @property {string=} protocol protocol
  167. * @property {string=} username username
  168. */
  169. /**
  170. * @typedef {boolean | ((error: Error) => void)} OverlayMessageOptions
  171. */
  172. /**
  173. * @typedef {object} ClientConfiguration
  174. * @property {"log" | "info" | "warn" | "error" | "none" | "verbose"=} logging logging
  175. * @property {(boolean | { warnings?: OverlayMessageOptions, errors?: OverlayMessageOptions, runtimeErrors?: OverlayMessageOptions })=} overlay overlay
  176. * @property {boolean=} progress progress
  177. * @property {(boolean | number)=} reconnect reconnect
  178. * @property {("ws" | string)=} webSocketTransport web socket transport
  179. * @property {(string | WebSocketURL)=} webSocketURL web socket URL
  180. */
  181. /**
  182. * @typedef {{ key: string, value: string }[] | Record<string, string | string[]>} Headers
  183. */
  184. /**
  185. * @template {BasicApplication} [T=ExpressApplication]
  186. * @typedef {T extends ExpressApplication ? ExpressRequestHandler | ExpressErrorRequestHandler : HandleFunction} MiddlewareHandler
  187. */
  188. /**
  189. * @typedef {{ name?: string, path?: string, middleware: MiddlewareHandler }} MiddlewareObject
  190. */
  191. /**
  192. * @typedef {MiddlewareObject | MiddlewareHandler} Middleware
  193. */
  194. /** @typedef {import("net").Server | import("tls").Server} BasicServer */
  195. /**
  196. * @template {BasicApplication} [A=ExpressApplication]
  197. * @template {BasicServer} [S=import("http").Server]
  198. * @typedef {object} Configuration
  199. * @property {(boolean | string)=} ipc
  200. * @property {Host=} host
  201. * @property {Port=} port
  202. * @property {(boolean | "only")=} hot
  203. * @property {boolean=} liveReload
  204. * @property {DevMiddlewareOptions<Request, Response>=} devMiddleware
  205. * @property {boolean=} compress
  206. * @property {("auto" | "all" | string | string[])=} allowedHosts
  207. * @property {(boolean | ConnectHistoryApiFallbackOptions)=} historyApiFallback
  208. * @property {(boolean | Record<string, never> | BonjourOptions)=} bonjour
  209. * @property {(string | string[] | WatchFiles | (string | WatchFiles)[])=} watchFiles
  210. * @property {(boolean | string | Static | (string | Static)[])=} static
  211. * @property {(ServerType<A, S> | ServerConfiguration<A, S>)=} server
  212. * @property {(() => Promise<A>)=} app
  213. * @property {(boolean | "ws" | string | WebSocketServerConfiguration)=} webSocketServer
  214. * @property {ProxyConfigArray=} proxy
  215. * @property {(boolean | string | Open | (string | Open)[])=} open
  216. * @property {boolean=} setupExitSignals
  217. * @property {(boolean | ClientConfiguration)=} client
  218. * @property {(Headers | ((req: Request, res: Response, context: DevMiddlewareContext<Request, Response> | undefined) => Headers))=} headers
  219. * @property {((devServer: Server<A, S>) => void)=} onListening
  220. * @property {((middlewares: Middleware[], devServer: Server<A, S>) => Middleware[])=} setupMiddlewares
  221. */
  222. if (!process.env.WEBPACK_SERVE) {
  223. process.env.WEBPACK_SERVE = "true";
  224. }
  225. /**
  226. * @template T
  227. * @typedef {() => T} FunctionReturning
  228. */
  229. /**
  230. * @template T
  231. * @param {FunctionReturning<T>} fn memorized function
  232. * @returns {FunctionReturning<T>} new function
  233. */
  234. const memoize = fn => {
  235. let cache = false;
  236. /** @type {T | undefined} */
  237. let result;
  238. return () => {
  239. if (cache) {
  240. return /** @type {T} */result;
  241. }
  242. result = fn();
  243. cache = true;
  244. // Allow to clean up memory for fn
  245. // and all dependent resources
  246. /** @type {FunctionReturning<T> | undefined} */
  247. fn = undefined;
  248. return /** @type {T} */result;
  249. };
  250. };
  251. const getExpress = memoize(async () => (await import("express")).default);
  252. /**
  253. * @param {OverlayMessageOptions=} setting overlay settings
  254. * @returns {undefined | string | boolean} encoded overlay settings
  255. */
  256. const encodeOverlaySettings = setting => typeof setting === "function" ? encodeURIComponent(setting.toString()) : setting;
  257. // Working for overload, because typescript doesn't support this yes
  258. /**
  259. * @overload
  260. * @param {NextHandleFunction} fn function
  261. * @returns {BasicApplication} application
  262. */
  263. /**
  264. * @overload
  265. * @param {HandleFunction} fn function
  266. * @returns {BasicApplication} application
  267. */
  268. /**
  269. * @overload
  270. * @param {string} route route
  271. * @param {NextHandleFunction} fn function
  272. * @returns {BasicApplication} application
  273. */
  274. /**
  275. * @param {string} route route
  276. * @param {HandleFunction} fn function
  277. * @returns {BasicApplication} application
  278. */
  279. // eslint-disable-next-line no-unused-vars
  280. function useFn(route, fn) {
  281. return /** @type {BasicApplication} */{};
  282. }
  283. const DEFAULT_ALLOWED_PROTOCOLS = /^(file|.+-extension):/i;
  284. /**
  285. * @typedef {object} BasicApplication
  286. * @property {typeof useFn} use
  287. */
  288. const pluginName = "webpack-dev-server";
  289. /**
  290. * @template {BasicApplication} [A=ExpressApplication]
  291. * @template {BasicServer} [S=HTTPServer]
  292. */
  293. class Server {
  294. /**
  295. * @param {Configuration<A, S> | undefined} options options
  296. * @param {(Compiler | MultiCompiler)=} compiler compiler, omitted when the server is used as a plugin via `apply()`
  297. */
  298. constructor(options, compiler) {
  299. options = options === undefined ? {} : options;
  300. (0, _schemaUtils.validate)(/** @type {Schema} */_options.default, options, {
  301. name: "Dev Server",
  302. baseDataPath: "options"
  303. });
  304. if (compiler) {
  305. this.compiler = compiler;
  306. /**
  307. * @type {ReturnType<Compiler["getInfrastructureLogger"]>}
  308. */
  309. this.logger = this.compiler.getInfrastructureLogger(pluginName);
  310. }
  311. this.options = options;
  312. /**
  313. * @type {FSWatcher[]}
  314. */
  315. this.staticWatchers = [];
  316. /**
  317. * @private
  318. * @type {{ name: string | symbol, listener: (...args: EXPECTED_ANY[]) => void }[]} }
  319. */
  320. this.listeners = [];
  321. // Keep track of websocket proxies for external websocket upgrade.
  322. /**
  323. * @private
  324. * @type {RequestHandler[]}
  325. */
  326. this.webSocketProxies = [];
  327. /**
  328. * @type {Socket[]}
  329. */
  330. this.sockets = [];
  331. /**
  332. * @private
  333. * @type {string | undefined}
  334. */
  335. this.currentHash = undefined;
  336. /**
  337. * @private
  338. * @type {boolean}
  339. */
  340. this.isPlugin = false;
  341. }
  342. static get schema() {
  343. return _options.default;
  344. }
  345. /**
  346. * @private
  347. * @returns {StatsOptions} default stats options
  348. */
  349. static get DEFAULT_STATS() {
  350. return {
  351. all: false,
  352. hash: true,
  353. warnings: true,
  354. errors: true,
  355. errorDetails: false
  356. };
  357. }
  358. /**
  359. * @param {string} URL url
  360. * @returns {boolean} true when URL is absolute, otherwise false
  361. */
  362. static isAbsoluteURL(URL) {
  363. // Don't match Windows paths `c:\`
  364. if (/^[a-zA-Z]:\\/.test(URL)) {
  365. return false;
  366. }
  367. // Scheme: https://tools.ietf.org/html/rfc3986#section-3.1
  368. // Absolute URL: https://tools.ietf.org/html/rfc3986#section-4.3
  369. return /^[a-zA-Z][a-zA-Z\d+\-.]*:/.test(URL);
  370. }
  371. /**
  372. * @param {string} gatewayOrFamily gateway or family
  373. * @param {boolean=} isInternal ip should be internal
  374. * @returns {string | undefined} resolved IP
  375. */
  376. static findIp(gatewayOrFamily, isInternal) {
  377. if (gatewayOrFamily === "v4" || gatewayOrFamily === "v6") {
  378. let host;
  379. const networks = Object.values(_nodeOs.default.networkInterfaces()).flatMap(networks => networks ?? []).filter(network => {
  380. if (!network || !network.address) {
  381. return false;
  382. }
  383. if (network.family !== `IP${gatewayOrFamily}`) {
  384. return false;
  385. }
  386. if (typeof isInternal !== "undefined" && network.internal !== isInternal) {
  387. return false;
  388. }
  389. if (gatewayOrFamily === "v6") {
  390. const range = _ipaddr.default.parse(network.address).range();
  391. if (range !== "ipv4Mapped" && range !== "uniqueLocal" && range !== "loopback") {
  392. return false;
  393. }
  394. }
  395. return network.address;
  396. });
  397. if (networks.length > 0) {
  398. // Take the first network found
  399. host = networks[0].address;
  400. if (host.includes(":")) {
  401. host = `[${host}]`;
  402. }
  403. }
  404. return host;
  405. }
  406. const gatewayIp = _ipaddr.default.parse(gatewayOrFamily);
  407. // Look for the matching interface in all local interfaces.
  408. for (const addresses of Object.values(_nodeOs.default.networkInterfaces())) {
  409. for (const {
  410. cidr
  411. } of (/** @type {NetworkInterfaceInfo[]} */
  412. addresses)) {
  413. const net = _ipaddr.default.parseCIDR(/** @type {string} */cidr);
  414. if (net[0] && net[0].kind() === gatewayIp.kind() &&
  415. // eslint-disable-next-line unicorn/prefer-regexp-test
  416. gatewayIp.match(net)) {
  417. return net[0].toString();
  418. }
  419. }
  420. }
  421. }
  422. /**
  423. * @param {Host} hostname hostname
  424. * @returns {Promise<string>} resolved hostname
  425. */
  426. static async getHostname(hostname) {
  427. if (hostname === "local-ip") {
  428. return Server.findIp("v4", false) || Server.findIp("v6", false) || "0.0.0.0";
  429. } else if (hostname === "local-ipv4") {
  430. return Server.findIp("v4", false) || "0.0.0.0";
  431. } else if (hostname === "local-ipv6") {
  432. return Server.findIp("v6", false) || "::";
  433. }
  434. return hostname;
  435. }
  436. /**
  437. * @param {Port} port port
  438. * @param {string} host host
  439. * @returns {Promise<number | string>} free port
  440. */
  441. static async getFreePort(port, host) {
  442. if (typeof port !== "undefined" && port !== null && port !== "auto") {
  443. return port;
  444. }
  445. const {
  446. default: pRetry
  447. } = await import("p-retry");
  448. const {
  449. default: getPort
  450. } = await Promise.resolve().then(() => require("./getPort.js"));
  451. const basePort = typeof process.env.WEBPACK_DEV_SERVER_BASE_PORT !== "undefined" ? Number.parseInt(process.env.WEBPACK_DEV_SERVER_BASE_PORT, 10) : 8080;
  452. // Try to find unused port and listen on it for 3 times,
  453. // if port is not specified in options.
  454. const defaultPortRetry = typeof process.env.WEBPACK_DEV_SERVER_PORT_RETRY !== "undefined" ? Number.parseInt(process.env.WEBPACK_DEV_SERVER_PORT_RETRY, 10) : 3;
  455. return pRetry(() => getPort(basePort, host), {
  456. retries: defaultPortRetry
  457. });
  458. }
  459. /**
  460. * @returns {string} path to cache dir
  461. */
  462. static findCacheDir() {
  463. const cwd = process.cwd();
  464. /**
  465. * @type {string | undefined}
  466. */
  467. let dir = cwd;
  468. for (;;) {
  469. try {
  470. if (_gracefulFs.default.statSync(_nodePath.default.join(dir, "package.json")).isFile()) break;
  471. // eslint-disable-next-line no-empty
  472. } catch {}
  473. const parent = _nodePath.default.dirname(dir);
  474. if (dir === parent) {
  475. dir = undefined;
  476. break;
  477. }
  478. dir = parent;
  479. }
  480. if (!dir) {
  481. return _nodePath.default.resolve(cwd, `.cache/${pluginName}`);
  482. } else if (process.versions.pnp === "1") {
  483. return _nodePath.default.resolve(dir, `.pnp/.cache/${pluginName}`);
  484. } else if (process.versions.pnp === "3") {
  485. return _nodePath.default.resolve(dir, `.yarn/.cache/${pluginName}`);
  486. }
  487. return _nodePath.default.resolve(dir, `node_modules/.cache/${pluginName}`);
  488. }
  489. /**
  490. * @private
  491. * @param {Compiler} compiler compiler
  492. * @returns {boolean} true when target is `web` or `universal`, otherwise false
  493. */
  494. static isWebTarget(compiler) {
  495. const {
  496. platform
  497. } = compiler;
  498. // A `web` or universal target (`web` and `node` both `null`) injects the
  499. // client. `target: false` is `null` everywhere, so it is excluded.
  500. return Boolean(platform.web || /** @type {{ universal?: boolean | null }} */platform?.universal || compiler.options.target !== false && platform.web === null && platform.node === null);
  501. }
  502. /**
  503. * @private
  504. * @param {Compiler} compiler compiler
  505. * @returns {Promise<void>}
  506. */
  507. async addAdditionalEntries(compiler) {
  508. /**
  509. * @type {string[]}
  510. */
  511. const additionalEntries = [];
  512. const isWebTarget = Server.isWebTarget(compiler);
  513. // TODO maybe empty client
  514. if (this.options.client && isWebTarget) {
  515. let webSocketURLStr = "";
  516. if (this.options.webSocketServer) {
  517. const webSocketURL = /** @type {WebSocketURL} */
  518. /** @type {ClientConfiguration} */
  519. this.options.client.webSocketURL;
  520. const webSocketServer = /** @type {{ type: WebSocketServerConfiguration["type"], options: NonNullable<WebSocketServerConfiguration["options"]> }} */
  521. this.options.webSocketServer;
  522. const searchParams = new URLSearchParams();
  523. /** @type {string} */
  524. let protocol;
  525. // We are proxying dev server and need to specify custom `hostname`
  526. if (typeof webSocketURL.protocol !== "undefined") {
  527. protocol = webSocketURL.protocol;
  528. } else {
  529. protocol = this.isTlsServer ? "wss:" : "ws:";
  530. }
  531. searchParams.set("protocol", protocol);
  532. if (typeof webSocketURL.username !== "undefined") {
  533. searchParams.set("username", webSocketURL.username);
  534. }
  535. if (typeof webSocketURL.password !== "undefined") {
  536. searchParams.set("password", webSocketURL.password);
  537. }
  538. /** @type {string} */
  539. let hostname;
  540. const isWebSocketServerHostDefined = typeof webSocketServer.options.host !== "undefined";
  541. const isWebSocketServerPortDefined = typeof webSocketServer.options.port !== "undefined";
  542. // We are proxying dev server and need to specify custom `hostname`
  543. if (typeof webSocketURL.hostname !== "undefined") {
  544. hostname = webSocketURL.hostname;
  545. }
  546. // Web socket server works on custom `hostname`, only for `ws` because `sock-js` is not support custom `hostname`
  547. else if (isWebSocketServerHostDefined) {
  548. hostname = webSocketServer.options.host;
  549. }
  550. // The `host` option is specified
  551. else if (typeof this.options.host !== "undefined") {
  552. hostname = this.options.host;
  553. }
  554. // The `port` option is not specified
  555. else {
  556. hostname = "0.0.0.0";
  557. }
  558. searchParams.set("hostname", hostname);
  559. /** @type {number | string} */
  560. let port;
  561. // We are proxying dev server and need to specify custom `port`
  562. if (typeof webSocketURL.port !== "undefined") {
  563. port = webSocketURL.port;
  564. }
  565. // Web socket server works on custom `port`, only for `ws` because `sock-js` is not support custom `port`
  566. else if (isWebSocketServerPortDefined) {
  567. port = webSocketServer.options.port;
  568. }
  569. // The `port` option is specified
  570. else if (typeof this.options.port === "number") {
  571. port = this.options.port;
  572. }
  573. // The `port` option is specified using `string`
  574. else if (typeof this.options.port === "string" && this.options.port !== "auto") {
  575. port = Number(this.options.port);
  576. }
  577. // The `port` option is not specified or set to `auto`
  578. else {
  579. port = "0";
  580. }
  581. searchParams.set("port", String(port));
  582. /** @type {string} */
  583. let pathname = "";
  584. // We are proxying dev server and need to specify custom `pathname`
  585. if (typeof webSocketURL.pathname !== "undefined") {
  586. pathname = webSocketURL.pathname;
  587. }
  588. // Web socket server works on custom `path`
  589. else if (typeof webSocketServer.options.prefix !== "undefined" || typeof webSocketServer.options.path !== "undefined") {
  590. pathname = webSocketServer.options.prefix || webSocketServer.options.path;
  591. }
  592. searchParams.set("pathname", pathname);
  593. const client = /** @type {ClientConfiguration} */this.options.client;
  594. if (typeof client.logging !== "undefined") {
  595. searchParams.set("logging", client.logging);
  596. }
  597. if (typeof client.progress !== "undefined") {
  598. searchParams.set("progress", String(client.progress));
  599. }
  600. if (typeof client.overlay !== "undefined") {
  601. const overlayString = typeof client.overlay === "boolean" ? String(client.overlay) : JSON.stringify({
  602. ...client.overlay,
  603. errors: encodeOverlaySettings(client.overlay.errors),
  604. warnings: encodeOverlaySettings(client.overlay.warnings),
  605. runtimeErrors: encodeOverlaySettings(client.overlay.runtimeErrors)
  606. });
  607. searchParams.set("overlay", overlayString);
  608. }
  609. if (typeof client.reconnect !== "undefined") {
  610. searchParams.set("reconnect", typeof client.reconnect === "number" ? String(client.reconnect) : "10");
  611. }
  612. if (typeof this.options.hot !== "undefined") {
  613. searchParams.set("hot", String(this.options.hot));
  614. }
  615. if (typeof this.options.liveReload !== "undefined") {
  616. searchParams.set("live-reload", String(this.options.liveReload));
  617. }
  618. webSocketURLStr = searchParams.toString();
  619. }
  620. additionalEntries.push(`${this.getClientEntry()}?${webSocketURLStr}`);
  621. }
  622. const clientHotEntry = this.getClientHotEntry();
  623. if (clientHotEntry) {
  624. additionalEntries.push(clientHotEntry);
  625. }
  626. const webpack = compiler.webpack || (/** @type {NonNullable<Awaited<ReturnType<typeof loadWebpackPeer>>>} */
  627. await loadWebpackPeer());
  628. // use a hook to add entries if available
  629. for (const additionalEntry of additionalEntries) {
  630. new webpack.EntryPlugin(compiler.context, additionalEntry, {
  631. name: undefined
  632. }).apply(compiler);
  633. }
  634. }
  635. /**
  636. * @private
  637. * @returns {Compiler["options"]} compiler options
  638. */
  639. getCompilerOptions() {
  640. if (typeof (/** @type {MultiCompiler} */this.compiler.compilers) !== "undefined") {
  641. if (/** @type {MultiCompiler} */this.compiler.compilers.length === 1) {
  642. return /** @type {MultiCompiler} */this.compiler.compilers[0].options;
  643. }
  644. // Configuration with the `devServer` options
  645. const compilerWithDevServer = /** @type {MultiCompiler} */
  646. this.compiler.compilers.find(config => config.options.devServer);
  647. if (compilerWithDevServer) {
  648. return compilerWithDevServer.options;
  649. }
  650. // Configuration with `web` preset
  651. const compilerWithWebPreset = /** @type {MultiCompiler} */
  652. this.compiler.compilers.find(config => Server.isWebTarget(config));
  653. if (compilerWithWebPreset) {
  654. return compilerWithWebPreset.options;
  655. }
  656. // Fallback
  657. return /** @type {MultiCompiler} */this.compiler.compilers[0].options;
  658. }
  659. return /** @type {Compiler} */this.compiler.options;
  660. }
  661. /**
  662. * @private
  663. * @returns {Promise<void>}
  664. */
  665. async normalizeOptions() {
  666. const {
  667. options
  668. } = this;
  669. const compilerOptions = this.getCompilerOptions();
  670. const compilerWatchOptions = compilerOptions.watchOptions;
  671. /**
  672. * @param {WatchOptions & { aggregateTimeout?: number, ignored?: WatchOptions["ignored"], poll?: number | boolean }} watchOptions watch options
  673. * @returns {WatchOptions} normalized watch options
  674. */
  675. const getWatchOptions = (watchOptions = {}) => {
  676. const getPolling = () => {
  677. if (typeof watchOptions.usePolling !== "undefined") {
  678. return watchOptions.usePolling;
  679. }
  680. if (typeof watchOptions.poll !== "undefined") {
  681. return Boolean(watchOptions.poll);
  682. }
  683. if (typeof compilerWatchOptions.poll !== "undefined") {
  684. return Boolean(compilerWatchOptions.poll);
  685. }
  686. return false;
  687. };
  688. const getInterval = () => {
  689. if (typeof watchOptions.interval !== "undefined") {
  690. return watchOptions.interval;
  691. }
  692. if (typeof watchOptions.poll === "number") {
  693. return watchOptions.poll;
  694. }
  695. if (typeof compilerWatchOptions.poll === "number") {
  696. return compilerWatchOptions.poll;
  697. }
  698. };
  699. const usePolling = getPolling();
  700. const interval = getInterval();
  701. const {
  702. poll: _poll,
  703. interval: _interval,
  704. ...rest
  705. } = watchOptions;
  706. return {
  707. ignoreInitial: true,
  708. persistent: true,
  709. followSymlinks: false,
  710. atomic: false,
  711. alwaysStat: true,
  712. ignorePermissionErrors: true,
  713. // Respect options from compiler watchOptions
  714. usePolling,
  715. ...(interval !== undefined ? {
  716. interval
  717. } : {}),
  718. ...rest
  719. };
  720. };
  721. /**
  722. * @param {(string | Static | undefined)=} optionsForStatic for static
  723. * @returns {NormalizedStatic} normalized options for static
  724. */
  725. const getStaticItem = optionsForStatic => {
  726. const getDefaultStaticOptions = () => ({
  727. directory: _nodePath.default.join(process.cwd(), "public"),
  728. staticOptions: {},
  729. publicPath: ["/"],
  730. serveIndex: {
  731. icons: true
  732. },
  733. watch: getWatchOptions()
  734. });
  735. /** @type {NormalizedStatic} */
  736. let item;
  737. if (typeof optionsForStatic === "undefined") {
  738. item = getDefaultStaticOptions();
  739. } else if (typeof optionsForStatic === "string") {
  740. item = {
  741. ...getDefaultStaticOptions(),
  742. directory: optionsForStatic
  743. };
  744. } else {
  745. const def = getDefaultStaticOptions();
  746. item = {
  747. directory: typeof optionsForStatic.directory !== "undefined" ? optionsForStatic.directory : def.directory,
  748. staticOptions: typeof optionsForStatic.staticOptions !== "undefined" ? {
  749. ...def.staticOptions,
  750. ...optionsForStatic.staticOptions
  751. } : def.staticOptions,
  752. publicPath: typeof optionsForStatic.publicPath !== "undefined" ? Array.isArray(optionsForStatic.publicPath) ? optionsForStatic.publicPath : [optionsForStatic.publicPath] : def.publicPath,
  753. serveIndex:
  754. // Check if 'serveIndex' property is defined in 'optionsForStatic'
  755. // If 'serveIndex' is a boolean and true, use default 'serveIndex'
  756. // If 'serveIndex' is an object, merge its properties with default 'serveIndex'
  757. // If 'serveIndex' is neither a boolean true nor an object, use it as-is
  758. // If 'serveIndex' is not defined in 'optionsForStatic', use default 'serveIndex'
  759. typeof optionsForStatic.serveIndex !== "undefined" ? typeof optionsForStatic.serveIndex === "boolean" && optionsForStatic.serveIndex ? def.serveIndex : typeof optionsForStatic.serveIndex === "object" ? {
  760. ...def.serveIndex,
  761. ...optionsForStatic.serveIndex
  762. } : optionsForStatic.serveIndex : def.serveIndex,
  763. watch: typeof optionsForStatic.watch !== "undefined" ? typeof optionsForStatic.watch === "boolean" ? optionsForStatic.watch ? def.watch : false : getWatchOptions(optionsForStatic.watch) : def.watch
  764. };
  765. }
  766. if (Server.isAbsoluteURL(item.directory)) {
  767. throw new Error("Using a URL as static.directory is not supported");
  768. }
  769. return item;
  770. };
  771. if (typeof options.allowedHosts === "undefined") {
  772. // AllowedHosts allows some default hosts picked from `options.host` or `webSocketURL.hostname` and `localhost`
  773. options.allowedHosts = "auto";
  774. }
  775. // We store allowedHosts as array when supplied as string
  776. else if (typeof options.allowedHosts === "string" && options.allowedHosts !== "auto" && options.allowedHosts !== "all") {
  777. options.allowedHosts = [options.allowedHosts];
  778. }
  779. // CLI pass options as array, we should normalize them
  780. else if (Array.isArray(options.allowedHosts) && options.allowedHosts.includes("all")) {
  781. options.allowedHosts = "all";
  782. }
  783. if (typeof options.bonjour === "undefined") {
  784. options.bonjour = false;
  785. } else if (typeof options.bonjour === "boolean") {
  786. options.bonjour = options.bonjour ? {} : false;
  787. }
  788. if (typeof options.client === "undefined" || typeof options.client === "object" && options.client !== null) {
  789. if (!options.client) {
  790. options.client = {};
  791. }
  792. if (typeof options.client.webSocketURL === "undefined") {
  793. options.client.webSocketURL = {};
  794. } else if (typeof options.client.webSocketURL === "string") {
  795. const parsedURL = new URL(options.client.webSocketURL);
  796. options.client.webSocketURL = {
  797. protocol: parsedURL.protocol,
  798. hostname: parsedURL.hostname,
  799. port: parsedURL.port.length > 0 ? Number(parsedURL.port) : "",
  800. pathname: parsedURL.pathname,
  801. username: parsedURL.username,
  802. password: parsedURL.password
  803. };
  804. } else if (typeof options.client.webSocketURL.port === "string") {
  805. options.client.webSocketURL.port = Number(options.client.webSocketURL.port);
  806. }
  807. // Enable client overlay by default
  808. if (typeof options.client.overlay === "undefined") {
  809. options.client.overlay = true;
  810. } else if (typeof options.client.overlay !== "boolean") {
  811. options.client.overlay = {
  812. errors: true,
  813. warnings: true,
  814. ...options.client.overlay
  815. };
  816. }
  817. if (typeof options.client.reconnect === "undefined") {
  818. options.client.reconnect = 10;
  819. } else if (options.client.reconnect === true) {
  820. options.client.reconnect = Infinity;
  821. } else if (options.client.reconnect === false) {
  822. options.client.reconnect = 0;
  823. }
  824. // Respect infrastructureLogging.level
  825. if (typeof options.client.logging === "undefined") {
  826. options.client.logging = compilerOptions.infrastructureLogging ? compilerOptions.infrastructureLogging.level : "info";
  827. }
  828. }
  829. if (typeof options.compress === "undefined") {
  830. options.compress = true;
  831. }
  832. if (typeof options.devMiddleware === "undefined") {
  833. options.devMiddleware = {};
  834. }
  835. // No need to normalize `headers`
  836. if (typeof options.historyApiFallback === "undefined") {
  837. options.historyApiFallback = false;
  838. } else if (typeof options.historyApiFallback === "boolean" && options.historyApiFallback) {
  839. options.historyApiFallback = {};
  840. }
  841. // No need to normalize `host`
  842. options.hot = typeof options.hot === "boolean" || options.hot === "only" ? options.hot : true;
  843. if (typeof options.server === "function" || typeof options.server === "string") {
  844. options.server = {
  845. type: options.server,
  846. options: {}
  847. };
  848. } else {
  849. const serverOptions = /** @type {ServerConfiguration<A, S>} */
  850. options.server || {};
  851. options.server = {
  852. type: serverOptions.type || "http",
  853. options: {
  854. ...serverOptions.options
  855. }
  856. };
  857. }
  858. const serverOptions = /** @type {ServerOptions} */options.server.options;
  859. if (options.server.type === "https" || options.server.type === "http2") {
  860. if (typeof serverOptions.requestCert === "undefined") {
  861. serverOptions.requestCert = false;
  862. }
  863. const httpsProperties = /** @type {(keyof ServerOptions)[]} */
  864. ["ca", "cert", "crl", "key", "pfx"];
  865. for (const property_ of httpsProperties) {
  866. const property = /** @type {keyof ServerOptions} */property_;
  867. if (typeof serverOptions[property] === "undefined") {
  868. continue;
  869. }
  870. const value = serverOptions[property];
  871. /**
  872. * @param {string | Buffer | undefined} item file to read
  873. * @returns {string | Buffer | undefined} content of file
  874. */
  875. const readFile = item => {
  876. if (Buffer.isBuffer(item) || typeof item === "object" && item !== null && !Array.isArray(item)) {
  877. return item;
  878. }
  879. if (item) {
  880. let stats = null;
  881. try {
  882. stats = _gracefulFs.default.lstatSync(_gracefulFs.default.realpathSync(item)).isFile();
  883. } catch {
  884. // Ignore error
  885. }
  886. // It is a file
  887. return stats ? _gracefulFs.default.readFileSync(item) : item;
  888. }
  889. };
  890. /** @type {EXPECTED_ANY} */
  891. serverOptions[property] = Array.isArray(value) ? value.map(item => readFile(/** @type {string | Buffer | undefined} */
  892. item)) : readFile(/** @type {string | Buffer | undefined} */
  893. value);
  894. }
  895. let fakeCert;
  896. if (!serverOptions.key || !serverOptions.cert) {
  897. const certificateDir = Server.findCacheDir();
  898. const certificatePath = _nodePath.default.join(certificateDir, "server.pem");
  899. let certificateExists;
  900. try {
  901. const certificate = await _gracefulFs.default.promises.stat(certificatePath);
  902. certificateExists = certificate.isFile();
  903. } catch {
  904. certificateExists = false;
  905. }
  906. if (certificateExists) {
  907. const certificateTtl = 1000 * 60 * 60 * 24;
  908. const certificateStat = await _gracefulFs.default.promises.stat(certificatePath);
  909. const now = Date.now();
  910. // cert is more than 30 days old, kill it with fire
  911. if ((now - Number(certificateStat.ctime)) / certificateTtl > 30) {
  912. this.logger.info("SSL certificate is more than 30 days old. Removing...");
  913. await _gracefulFs.default.promises.rm(certificatePath, {
  914. recursive: true
  915. });
  916. certificateExists = false;
  917. }
  918. }
  919. if (!certificateExists) {
  920. this.logger.info("Generating SSL certificate...");
  921. const {
  922. default: selfsigned
  923. } = await import("selfsigned");
  924. const attributes = [{
  925. name: "commonName",
  926. value: "localhost"
  927. }];
  928. const notBeforeDate = new Date();
  929. const notAfterDate = new Date();
  930. notAfterDate.setDate(notAfterDate.getDate() + 30);
  931. const pems = await selfsigned.generate(attributes, {
  932. algorithm: "sha256",
  933. keySize: 2048,
  934. notBeforeDate,
  935. notAfterDate,
  936. extensions: [{
  937. name: "basicConstraints",
  938. cA: true
  939. }, {
  940. name: "keyUsage",
  941. keyCertSign: true,
  942. digitalSignature: true,
  943. nonRepudiation: true,
  944. keyEncipherment: true,
  945. dataEncipherment: true
  946. }, {
  947. name: "extKeyUsage",
  948. serverAuth: true,
  949. clientAuth: true,
  950. codeSigning: true,
  951. timeStamping: true
  952. }, {
  953. name: "subjectAltName",
  954. altNames: [{
  955. // type 2 is DNS
  956. type: 2,
  957. value: "localhost"
  958. }, {
  959. type: 2,
  960. value: "localhost.localdomain"
  961. }, {
  962. type: 2,
  963. value: "lvh.me"
  964. }, {
  965. type: 2,
  966. value: "*.lvh.me"
  967. }, {
  968. type: 2,
  969. value: "[::1]"
  970. }, {
  971. // type 7 is IP
  972. type: 7,
  973. ip: "127.0.0.1"
  974. }, {
  975. type: 7,
  976. ip: "fe80::1"
  977. }]
  978. }]
  979. });
  980. await _gracefulFs.default.promises.mkdir(certificateDir, {
  981. recursive: true
  982. });
  983. await _gracefulFs.default.promises.writeFile(certificatePath, pems.private + pems.cert, {
  984. encoding: "utf8"
  985. });
  986. }
  987. fakeCert = await _gracefulFs.default.promises.readFile(certificatePath);
  988. this.logger.info(`SSL certificate: ${certificatePath}`);
  989. }
  990. serverOptions.key ||= fakeCert;
  991. serverOptions.cert ||= fakeCert;
  992. }
  993. if (typeof options.ipc === "boolean") {
  994. const isWindows = process.platform === "win32";
  995. const pipePrefix = isWindows ? "\\\\.\\pipe\\" : _nodeOs.default.tmpdir();
  996. const pipeName = `${pluginName}.sock`;
  997. options.ipc = _nodePath.default.join(pipePrefix, pipeName);
  998. }
  999. options.liveReload = typeof options.liveReload !== "undefined" ? options.liveReload : true;
  1000. // https://github.com/webpack/webpack-dev-server/issues/1990
  1001. const defaultOpenOptions = {
  1002. wait: false
  1003. };
  1004. /**
  1005. * @param {import("open").Options & { target?: string | string[] } & EXPECTED_ANY} target target
  1006. * @returns {NormalizedOpen[]} normalized open options
  1007. */
  1008. const getOpenItemsFromObject = ({
  1009. target,
  1010. ...rest
  1011. }) => {
  1012. const normalizedOptions = {
  1013. ...defaultOpenOptions,
  1014. ...rest
  1015. };
  1016. if (typeof normalizedOptions.app === "string") {
  1017. normalizedOptions.app = {
  1018. name: normalizedOptions.app
  1019. };
  1020. }
  1021. const normalizedTarget = typeof target === "undefined" ? "<url>" : target;
  1022. if (Array.isArray(normalizedTarget)) {
  1023. return normalizedTarget.map(singleTarget => ({
  1024. target: singleTarget,
  1025. options: normalizedOptions
  1026. }));
  1027. }
  1028. return [{
  1029. target: normalizedTarget,
  1030. options: normalizedOptions
  1031. }];
  1032. };
  1033. if (typeof options.open === "undefined") {
  1034. /** @type {NormalizedOpen[]} */
  1035. options.open = [];
  1036. } else if (typeof options.open === "boolean") {
  1037. /** @type {NormalizedOpen[]} */
  1038. options.open = options.open ? [{
  1039. target: "<url>",
  1040. options: (/** @type {OpenOptions} */defaultOpenOptions)
  1041. }] : [];
  1042. } else if (typeof options.open === "string") {
  1043. /** @type {NormalizedOpen[]} */
  1044. options.open = [{
  1045. target: options.open,
  1046. options: defaultOpenOptions
  1047. }];
  1048. } else if (Array.isArray(options.open)) {
  1049. /**
  1050. * @type {NormalizedOpen[]}
  1051. */
  1052. const result = [];
  1053. for (const item of options.open) {
  1054. if (typeof item === "string") {
  1055. result.push({
  1056. target: item,
  1057. options: defaultOpenOptions
  1058. });
  1059. continue;
  1060. }
  1061. result.push(...getOpenItemsFromObject(item));
  1062. }
  1063. /** @type {NormalizedOpen[]} */
  1064. options.open = result;
  1065. } else {
  1066. /** @type {NormalizedOpen[]} */
  1067. options.open = [...getOpenItemsFromObject(options.open)];
  1068. }
  1069. if (typeof options.port === "string" && options.port !== "auto") {
  1070. options.port = Number(options.port);
  1071. }
  1072. /**
  1073. * Assume a proxy configuration specified as:
  1074. * proxy: { 'context': { options } }
  1075. * OR
  1076. * proxy: { 'context': 'target' }
  1077. */
  1078. if (typeof options.proxy !== "undefined") {
  1079. options.proxy = options.proxy.map(item => {
  1080. if (typeof item === "function") {
  1081. return item;
  1082. }
  1083. if (typeof item.logger === "undefined") {
  1084. item.logger = this.logger;
  1085. }
  1086. return item;
  1087. });
  1088. }
  1089. if (typeof options.setupExitSignals === "undefined") {
  1090. // In plugin mode, the host (e.g. `webpack-cli`) usually owns process
  1091. // signal handling and calls `compiler.close()` on shutdown, which fires
  1092. // our `shutdown` hook. Adding our own SIGINT/SIGTERM listeners on top of
  1093. // that would race with the host's handler and call `compiler.close()`
  1094. // twice.
  1095. options.setupExitSignals = !this.isPlugin;
  1096. }
  1097. if (typeof options.static === "undefined") {
  1098. options.static = [getStaticItem()];
  1099. } else if (typeof options.static === "boolean") {
  1100. options.static = options.static ? [getStaticItem()] : false;
  1101. } else if (typeof options.static === "string") {
  1102. options.static = [getStaticItem(options.static)];
  1103. } else if (Array.isArray(options.static)) {
  1104. options.static = options.static.map(item => getStaticItem(item));
  1105. } else {
  1106. options.static = [getStaticItem(options.static)];
  1107. }
  1108. if (typeof options.watchFiles === "string") {
  1109. options.watchFiles = [{
  1110. paths: options.watchFiles,
  1111. options: getWatchOptions()
  1112. }];
  1113. } else if (typeof options.watchFiles === "object" && options.watchFiles !== null && !Array.isArray(options.watchFiles)) {
  1114. options.watchFiles = [{
  1115. paths: options.watchFiles.paths,
  1116. options: getWatchOptions(options.watchFiles.options || {})
  1117. }];
  1118. } else if (Array.isArray(options.watchFiles)) {
  1119. options.watchFiles = options.watchFiles.map(item => {
  1120. if (typeof item === "string") {
  1121. return {
  1122. paths: item,
  1123. options: getWatchOptions()
  1124. };
  1125. }
  1126. return {
  1127. paths: item.paths,
  1128. options: getWatchOptions(item.options || {})
  1129. };
  1130. });
  1131. } else {
  1132. options.watchFiles = [];
  1133. }
  1134. const defaultWebSocketServerType = "ws";
  1135. const defaultWebSocketServerOptions = {
  1136. path: "/ws"
  1137. };
  1138. if (typeof options.webSocketServer === "undefined") {
  1139. options.webSocketServer = {
  1140. type: defaultWebSocketServerType,
  1141. options: defaultWebSocketServerOptions
  1142. };
  1143. } else if (typeof options.webSocketServer === "boolean" && !options.webSocketServer) {
  1144. options.webSocketServer = false;
  1145. } else if (typeof options.webSocketServer === "string" || typeof options.webSocketServer === "function") {
  1146. options.webSocketServer = {
  1147. type: options.webSocketServer,
  1148. options: defaultWebSocketServerOptions
  1149. };
  1150. } else {
  1151. options.webSocketServer = {
  1152. type: /** @type {WebSocketServerConfiguration} */
  1153. options.webSocketServer.type || defaultWebSocketServerType,
  1154. options: {
  1155. ...defaultWebSocketServerOptions,
  1156. ... /** @type {WebSocketServerConfiguration} */
  1157. options.webSocketServer.options
  1158. }
  1159. };
  1160. const webSocketServer = /** @type {{ type: WebSocketServerConfiguration["type"], options: NonNullable<WebSocketServerConfiguration["options"]> }} */
  1161. options.webSocketServer;
  1162. if (typeof webSocketServer.options.port === "string") {
  1163. webSocketServer.options.port = Number(webSocketServer.options.port);
  1164. }
  1165. }
  1166. }
  1167. /**
  1168. * @returns {{ isColorSupported: () => boolean, colors: import("webpack").Colors }} colors support
  1169. */
  1170. #getColors() {
  1171. const compilerOptions = /** @type {MultiCompiler} */
  1172. this.compiler.compilers ? /** @type {MultiCompiler} */this.compiler.compilers[0].webpack : /** @type {Compiler} */this.compiler.webpack;
  1173. const colors = compilerOptions.cli.createColors({
  1174. useColor: compilerOptions.cli.isColorSupported()
  1175. });
  1176. return {
  1177. isColorSupported: compilerOptions.cli.isColorSupported,
  1178. colors
  1179. };
  1180. }
  1181. /**
  1182. * @private
  1183. * @returns {string} client transport
  1184. */
  1185. getClientTransport() {
  1186. let clientImplementation;
  1187. let clientImplementationFound = true;
  1188. const isKnownWebSocketServerImplementation = this.options.webSocketServer && typeof (/** @type {WebSocketServerConfiguration} */
  1189. this.options.webSocketServer.type) === "string" &&
  1190. // @ts-expect-error
  1191. this.options.webSocketServer.type === "ws";
  1192. let clientTransport;
  1193. if (this.options.client) {
  1194. if (typeof (/** @type {ClientConfiguration} */
  1195. this.options.client.webSocketTransport) !== "undefined") {
  1196. clientTransport = /** @type {ClientConfiguration} */
  1197. this.options.client.webSocketTransport;
  1198. } else if (isKnownWebSocketServerImplementation) {
  1199. clientTransport = /** @type {WebSocketServerConfiguration} */
  1200. this.options.webSocketServer.type;
  1201. } else {
  1202. clientTransport = "ws";
  1203. }
  1204. } else {
  1205. clientTransport = "ws";
  1206. }
  1207. switch (typeof clientTransport) {
  1208. case "string":
  1209. // could be 'ws', or a path that should be resolved
  1210. if (clientTransport === "ws") {
  1211. clientImplementation = cjsRequire.resolve("../client/clients/WebSocketClient.js");
  1212. } else {
  1213. try {
  1214. clientImplementation = cjsRequire.resolve(clientTransport);
  1215. } catch {
  1216. clientImplementationFound = false;
  1217. }
  1218. }
  1219. break;
  1220. default:
  1221. clientImplementationFound = false;
  1222. }
  1223. if (!clientImplementationFound) {
  1224. throw new Error(`${!isKnownWebSocketServerImplementation ? "When you use custom web socket implementation you must explicitly specify client.webSocketTransport. " : ""}client.webSocketTransport must be a string denoting a default implementation (e.g. 'ws') or a resolvable module specifier or absolute file path which exports a class `);
  1225. }
  1226. return /** @type {string} */clientImplementation;
  1227. }
  1228. /**
  1229. * @template T
  1230. * @private
  1231. * @returns {Promise<T>} server transport
  1232. */
  1233. async getServerTransport() {
  1234. let implementation;
  1235. let implementationFound = true;
  1236. switch (typeof (/** @type {WebSocketServerConfiguration} */
  1237. this.options.webSocketServer.type)) {
  1238. case "string":
  1239. // Could be 'ws', or a path that should be required
  1240. if (/** @type {WebSocketServerConfiguration} */this.options.webSocketServer.type === "ws") {
  1241. implementation = (await Promise.resolve().then(() => require("./servers/WebsocketServer.js"))).default;
  1242. } else {
  1243. try {
  1244. const mod = cjsRequire(/** @type {string} */
  1245. /** @type {WebSocketServerConfiguration} */
  1246. this.options.webSocketServer.type);
  1247. implementation = mod.default || mod;
  1248. } catch {
  1249. implementationFound = false;
  1250. }
  1251. }
  1252. break;
  1253. case "function":
  1254. implementation = /** @type {WebSocketServerConfiguration} */
  1255. this.options.webSocketServer.type;
  1256. break;
  1257. default:
  1258. implementationFound = false;
  1259. }
  1260. if (!implementationFound) {
  1261. throw new Error("webSocketServer (webSocketServer.type) must be a string denoting a default implementation (e.g. 'ws'), a resolvable module specifier or absolute file path " + "which exports a class extending BaseServer (webpack-dev-server/lib/servers/BaseServer.js), " + "or the class itself which extends BaseServer");
  1262. }
  1263. return implementation;
  1264. }
  1265. /**
  1266. * @returns {string}
  1267. */
  1268. getClientEntry() {
  1269. return cjsRequire.resolve("../client/index.js");
  1270. }
  1271. /**
  1272. * @returns {string | void} client hot entry
  1273. */
  1274. getClientHotEntry() {
  1275. if (this.options.hot === "only") {
  1276. return cjsRequire.resolve("webpack/hot/only-dev-server");
  1277. } else if (this.options.hot) {
  1278. return cjsRequire.resolve("webpack/hot/dev-server");
  1279. }
  1280. }
  1281. /**
  1282. * @private
  1283. * @returns {void}
  1284. */
  1285. setupProgressPlugin() {
  1286. const {
  1287. ProgressPlugin
  1288. } = /** @type {MultiCompiler} */
  1289. this.compiler.compilers ? /** @type {MultiCompiler} */this.compiler.compilers[0].webpack : /** @type {Compiler} */this.compiler.webpack;
  1290. new ProgressPlugin(
  1291. /**
  1292. * @param {number} percent percent
  1293. * @param {string} msg message
  1294. * @param {string} addInfo extra information
  1295. * @param {string} pluginName plugin name
  1296. */
  1297. (percent, msg, addInfo, pluginName) => {
  1298. percent = Math.floor(percent * 100);
  1299. if (percent === 100) {
  1300. msg = "Compilation completed";
  1301. }
  1302. if (addInfo) {
  1303. msg = `${msg} (${addInfo})`;
  1304. }
  1305. if (this.webSocketServer) {
  1306. this.sendMessage(this.webSocketServer.clients, "progress-update", {
  1307. percent,
  1308. msg,
  1309. pluginName
  1310. });
  1311. }
  1312. if (this.server) {
  1313. this.server.emit("progress-update", {
  1314. percent,
  1315. msg,
  1316. pluginName
  1317. });
  1318. }
  1319. }).apply(/** @type {Compiler | MultiCompiler} */this.compiler);
  1320. }
  1321. /**
  1322. * @private
  1323. * @returns {Promise<void>}
  1324. */
  1325. async initialize() {
  1326. this.setupHooks();
  1327. await this.setupApp();
  1328. await this.createServer();
  1329. if (this.options.webSocketServer) {
  1330. const compilers = /** @type {MultiCompiler} */
  1331. this.compiler.compilers || [this.compiler];
  1332. for (const compiler of compilers) {
  1333. if (compiler.options.devServer === false) {
  1334. continue;
  1335. }
  1336. await this.addAdditionalEntries(compiler);
  1337. const webpack = compiler.webpack || (/** @type {NonNullable<Awaited<ReturnType<typeof loadWebpackPeer>>>} */
  1338. await loadWebpackPeer());
  1339. new webpack.ProvidePlugin({
  1340. __webpack_dev_server_client__: this.getClientTransport()
  1341. }).apply(compiler);
  1342. // For universal targets `webpack/hot/emitter` uses Node's `events`,
  1343. // which breaks in the browser. Swap it for webpack's `EventTarget`
  1344. // emitter when available.
  1345. if (compiler.options.output.module && compiler.platform.web === null && compiler.platform.node === null) {
  1346. let emitter;
  1347. try {
  1348. emitter = cjsRequire.resolve("webpack/hot/emitter-event-target.js");
  1349. } catch {
  1350. // older webpack versions do not ship the `EventTarget` emitter
  1351. }
  1352. if (emitter) {
  1353. new webpack.NormalModuleReplacementPlugin(/emitter(\.js)?$/, result => {
  1354. if (/webpack[/\\]hot|webpack-dev-server[/\\]client/.test(result.context)) {
  1355. result.request = emitter;
  1356. }
  1357. }).apply(compiler);
  1358. }
  1359. }
  1360. if (this.options.hot) {
  1361. const HMRPluginExists = compiler.options.plugins.find(plugin => plugin && plugin.constructor === webpack.HotModuleReplacementPlugin);
  1362. if (HMRPluginExists) {
  1363. this.logger.warn('"hot: true" automatically applies HMR plugin, you don\'t have to add it manually to your webpack configuration.');
  1364. } else {
  1365. // Apply the HMR plugin
  1366. const plugin = new webpack.HotModuleReplacementPlugin();
  1367. plugin.apply(compiler);
  1368. }
  1369. }
  1370. }
  1371. if (this.options.client && /** @type {ClientConfiguration} */this.options.client.progress) {
  1372. this.setupProgressPlugin();
  1373. }
  1374. }
  1375. await this.setupWatchFiles();
  1376. await this.setupWatchStaticFiles();
  1377. await this.setupMiddlewares();
  1378. if (this.options.setupExitSignals) {
  1379. const signals = ["SIGINT", "SIGTERM"];
  1380. let needForceShutdown = false;
  1381. for (const signal of signals) {
  1382. // eslint-disable-next-line no-loop-func
  1383. const listener = () => {
  1384. if (needForceShutdown) {
  1385. // eslint-disable-next-line n/no-process-exit
  1386. process.exit();
  1387. }
  1388. this.logger.info("Gracefully shutting down. To force exit, press ^C again. Please wait...");
  1389. needForceShutdown = true;
  1390. this.stopCallback(() => {
  1391. if (typeof this.compiler?.close === "function") {
  1392. this.compiler.close(() => {
  1393. // eslint-disable-next-line n/no-process-exit
  1394. process.exit();
  1395. });
  1396. } else {
  1397. // eslint-disable-next-line n/no-process-exit
  1398. process.exit();
  1399. }
  1400. });
  1401. };
  1402. this.listeners.push({
  1403. name: signal,
  1404. listener
  1405. });
  1406. process.on(signal, listener);
  1407. }
  1408. }
  1409. // Proxy WebSocket without the initial http request
  1410. // https://github.com/chimurai/http-proxy-middleware#external-websocket-upgrade
  1411. const webSocketProxies = /** @type {RequestHandler[]} */
  1412. this.webSocketProxies;
  1413. const hmrPath = this.options.webSocketServer && /** @type {WebSocketServerConfiguration} */
  1414. this.options.webSocketServer.options && /** @type {NonNullable<WebSocketServerConfiguration["options"]>} */
  1415. (/** @type {WebSocketServerConfiguration} */
  1416. this.options.webSocketServer.options).path;
  1417. for (const webSocketProxy of webSocketProxies) {
  1418. const proxyUpgrade = /** @type {RequestHandler & { upgrade: NonNullable<RequestHandler["upgrade"]> }} */
  1419. webSocketProxy.upgrade;
  1420. /** @type {S} */
  1421. this.server.on("upgrade", (req, socket, head) => {
  1422. if (hmrPath && typeof req.url === "string") {
  1423. // Match the configured HMR path exactly the same way the underlying
  1424. // WebSocket server (`ws`) does in `WebSocketServer#shouldHandle`: a
  1425. // raw, case-sensitive comparison of the request target with the query
  1426. // string stripped. Any normalization here would classify URL variants
  1427. // (`//ws`, `/WS`, …) as the HMR socket even though `ws` refuses them.
  1428. // https://github.com/websockets/ws/blob/8.18.3/lib/websocket-server.js#L214
  1429. const queryIndex = req.url.indexOf("?");
  1430. const pathname = queryIndex !== -1 ? req.url.slice(0, queryIndex) : req.url;
  1431. if (pathname === hmrPath) {
  1432. return;
  1433. }
  1434. }
  1435. proxyUpgrade(req, socket, head);
  1436. });
  1437. }
  1438. }
  1439. /**
  1440. * @private
  1441. * @returns {Promise<void>}
  1442. */
  1443. async setupApp() {
  1444. /** @type {A | undefined} */
  1445. this.app = /** @type {A} */
  1446. typeof this.options.app === "function" ? await this.options.app() : (await getExpress())();
  1447. }
  1448. /**
  1449. * @private
  1450. * @param {Stats | MultiStats} statsObj stats
  1451. * @returns {StatsCompilation} stats of compilation
  1452. */
  1453. getStats(statsObj) {
  1454. const stats = Server.DEFAULT_STATS;
  1455. const compilerOptions = this.getCompilerOptions();
  1456. if (compilerOptions.stats && /** @type {StatsOptions} */compilerOptions.stats.warningsFilter) {
  1457. stats.warningsFilter = /** @type {StatsOptions} */
  1458. compilerOptions.stats.warningsFilter;
  1459. }
  1460. return statsObj.toJson(stats);
  1461. }
  1462. /**
  1463. * @private
  1464. * @returns {void}
  1465. */
  1466. setupHooks() {
  1467. const compiler = /** @type {Compiler | MultiCompiler} */this.compiler;
  1468. compiler.hooks.invalid.tap(pluginName, () => {
  1469. if (this.webSocketServer) {
  1470. this.sendMessage(this.webSocketServer.clients, "invalid");
  1471. }
  1472. });
  1473. compiler.hooks.done.tap(pluginName,
  1474. /**
  1475. * @param {Stats | MultiStats} stats stats
  1476. */
  1477. stats => {
  1478. if (this.webSocketServer) {
  1479. this.sendStats(this.webSocketServer.clients, this.getStats(stats));
  1480. }
  1481. /**
  1482. * @private
  1483. * @type {Stats | MultiStats}
  1484. */
  1485. this.stats = stats;
  1486. });
  1487. }
  1488. /**
  1489. * @private
  1490. * @returns {Promise<void>}
  1491. */
  1492. async setupWatchStaticFiles() {
  1493. const watchFiles = /** @type {NormalizedStatic[]} */this.options.static;
  1494. if (watchFiles.length > 0) {
  1495. for (const item of watchFiles) {
  1496. if (item.watch) {
  1497. await this.watchFiles(item.directory, item.watch);
  1498. }
  1499. }
  1500. }
  1501. }
  1502. /**
  1503. * @private
  1504. * @returns {Promise<void>}
  1505. */
  1506. async setupWatchFiles() {
  1507. const watchFiles = /** @type {WatchFiles[]} */this.options.watchFiles;
  1508. if (watchFiles.length > 0) {
  1509. for (const item of watchFiles) {
  1510. await this.watchFiles(item.paths, item.options);
  1511. }
  1512. }
  1513. }
  1514. /**
  1515. * @private
  1516. * @returns {Promise<void>}
  1517. */
  1518. async setupMiddlewares() {
  1519. if (this.compiler === undefined) return;
  1520. /**
  1521. * @type {Middleware[]}
  1522. */
  1523. let middlewares = [];
  1524. // Register setup host header check for security
  1525. middlewares.push({
  1526. name: "host-header-check",
  1527. /**
  1528. * @param {Request} req request
  1529. * @param {Response} res response
  1530. * @param {NextFunction} next next function
  1531. * @returns {void}
  1532. */
  1533. middleware: (req, res, next) => {
  1534. const headers = /** @type {{ [key: string]: string | undefined }} */
  1535. req.headers;
  1536. const headerName = headers[":authority"] ? ":authority" : "host";
  1537. if (this.isValidHost(headers, headerName)) {
  1538. next();
  1539. return;
  1540. }
  1541. res.statusCode = 403;
  1542. res.end("Invalid Host header");
  1543. }
  1544. });
  1545. // Register setup cross origin request check for security
  1546. middlewares.push({
  1547. name: "cross-origin-header-check",
  1548. /**
  1549. * @param {Request} req request
  1550. * @param {Response} res response
  1551. * @param {NextFunction} next next function
  1552. * @returns {void}
  1553. */
  1554. middleware: (req, res, next) => {
  1555. const headers = /** @type {{ [key: string]: string | undefined }} */
  1556. req.headers;
  1557. const headerName = headers[":authority"] ? ":authority" : "host";
  1558. if (this.isValidHost(headers, headerName, false)) {
  1559. next();
  1560. return;
  1561. }
  1562. if (headers["sec-fetch-mode"] === "no-cors" && headers["sec-fetch-site"] === "cross-site") {
  1563. res.statusCode = 403;
  1564. res.end("Cross-Origin request blocked");
  1565. return;
  1566. }
  1567. // Block cross-origin resource loading when Sec-Fetch-* headers are absent (HTTP origins)
  1568. if (this.options.allowedHosts !== "all" && !this.isUserCORSWildcardEnabled()) {
  1569. res.setHeader("Cross-Origin-Resource-Policy", "same-origin");
  1570. }
  1571. next();
  1572. }
  1573. });
  1574. const isHTTP2 = /** @type {ServerConfiguration<A, S>} */this.options.server.type === "http2";
  1575. if (isHTTP2) {
  1576. // TODO patch for https://github.com/pillarjs/finalhandler/pull/45, need remove then will be resolved
  1577. middlewares.push({
  1578. name: "http2-status-message-patch",
  1579. middleware: /** @type {NextHandleFunction} */
  1580. (_req, res, next) => {
  1581. Object.defineProperty(res, "statusMessage", {
  1582. get() {
  1583. return "";
  1584. },
  1585. set() {}
  1586. });
  1587. next();
  1588. }
  1589. });
  1590. }
  1591. // compress is placed last and uses unshift so that it will be the first middleware used
  1592. if (this.options.compress) {
  1593. const {
  1594. default: compression
  1595. } = await import("compression");
  1596. middlewares.push({
  1597. name: "compression",
  1598. middleware: compression()
  1599. });
  1600. }
  1601. if (typeof this.options.headers !== "undefined") {
  1602. middlewares.push({
  1603. name: "set-headers",
  1604. middleware: this.setHeaders.bind(this)
  1605. });
  1606. }
  1607. middlewares.push({
  1608. name: "webpack-dev-middleware",
  1609. middleware: (/** @type {MiddlewareHandler} */this.middleware)
  1610. });
  1611. middlewares.push({
  1612. name: "webpack-dev-server-invalidate",
  1613. path: "/webpack-dev-server/invalidate",
  1614. /**
  1615. * @param {Request} req request
  1616. * @param {Response} res response
  1617. * @param {NextFunction} next next function
  1618. * @returns {void}
  1619. */
  1620. middleware: (req, res, next) => {
  1621. if (req.method !== "GET" && req.method !== "HEAD") {
  1622. next();
  1623. return;
  1624. }
  1625. if (!this.#isSameOriginRequest(req)) {
  1626. res.statusCode = 403;
  1627. res.end("Cross-Origin request blocked");
  1628. return;
  1629. }
  1630. this.invalidate();
  1631. res.end();
  1632. }
  1633. });
  1634. middlewares.push({
  1635. name: "webpack-dev-server-open-editor",
  1636. path: "/webpack-dev-server/open-editor",
  1637. /**
  1638. * @param {Request} req request
  1639. * @param {Response} res response
  1640. * @param {NextFunction} next next function
  1641. * @returns {Promise<void>}
  1642. */
  1643. middleware: async (req, res, next) => {
  1644. if (req.method !== "GET" && req.method !== "HEAD") {
  1645. next();
  1646. return;
  1647. }
  1648. if (!this.#isSameOriginRequest(req)) {
  1649. res.statusCode = 403;
  1650. res.end("Cross-Origin request blocked");
  1651. return;
  1652. }
  1653. if (!req.url) {
  1654. next();
  1655. return;
  1656. }
  1657. const resolveUrl = new URL(req.url, `http://${req.headers.host}`);
  1658. const params = new URLSearchParams(resolveUrl.search);
  1659. const fileName = params.get("fileName");
  1660. if (typeof fileName === "string") {
  1661. const {
  1662. default: launchEditor
  1663. } = await import("launch-editor");
  1664. launchEditor(fileName);
  1665. }
  1666. res.end();
  1667. }
  1668. });
  1669. middlewares.push({
  1670. name: "webpack-dev-server-assets",
  1671. path: "/webpack-dev-server",
  1672. /**
  1673. * @param {Request} req request
  1674. * @param {Response} res response
  1675. * @param {NextFunction} next next function
  1676. * @returns {void}
  1677. */
  1678. middleware: (req, res, next) => {
  1679. if (req.method !== "GET" && req.method !== "HEAD") {
  1680. next();
  1681. return;
  1682. }
  1683. if (!this.middleware) {
  1684. next();
  1685. return;
  1686. }
  1687. this.middleware.waitUntilValid(stats => {
  1688. res.setHeader("Content-Type", "text/html; charset=utf-8");
  1689. // HEAD requests should not return body content
  1690. if (req.method === "HEAD") {
  1691. res.end();
  1692. return;
  1693. }
  1694. res.write('<!DOCTYPE html><html><head><meta charset="utf-8"/></head><body>');
  1695. /**
  1696. * @type {StatsCompilation[]}
  1697. */
  1698. const statsForPrint = typeof (/** @type {MultiStats} */stats.stats) !== "undefined" ? (/** @type {NonNullable<StatsCompilation["children"]>} */
  1699. /** @type {MultiStats} */stats.toJson().children) : [/** @type {Stats} */stats.toJson()];
  1700. res.write("<h1>Assets Report:</h1>");
  1701. for (const [index, item] of statsForPrint.entries()) {
  1702. res.write("<div>");
  1703. const name = typeof item.name !== "undefined" ? item.name : /** @type {MultiStats} */stats.stats ? `unnamed[${index}]` : "unnamed";
  1704. res.write(`<h2>Compilation: ${name}</h2>`);
  1705. res.write("<ul>");
  1706. const publicPath = item.publicPath === "auto" ? "" : item.publicPath;
  1707. const assets = /** @type {NonNullable<StatsCompilation["assets"]>} */
  1708. item.assets;
  1709. for (const asset of assets) {
  1710. const assetName = asset.name;
  1711. const assetURL = `${publicPath}${assetName}`;
  1712. res.write(`<li>
  1713. <strong><a href="${assetURL}" target="_blank">${assetName}</a></strong>
  1714. </li>`);
  1715. }
  1716. res.write("</ul>");
  1717. res.write("</div>");
  1718. }
  1719. res.end("</body></html>");
  1720. });
  1721. }
  1722. });
  1723. if (this.options.proxy) {
  1724. const {
  1725. createProxyMiddleware
  1726. } = await import("http-proxy-middleware");
  1727. /**
  1728. * @param {ProxyConfigArrayItem} proxyConfig proxy config
  1729. * @returns {RequestHandler | undefined} request handler
  1730. */
  1731. const getProxyMiddleware = proxyConfig => {
  1732. const context = proxyConfig.context || proxyConfig.path || proxyConfig.pathFilter;
  1733. return createProxyMiddleware({
  1734. ...proxyConfig,
  1735. pathFilter: (/** @type {string} */context)
  1736. });
  1737. };
  1738. /**
  1739. * @example
  1740. * Assume a proxy configuration specified as:
  1741. * proxy: [
  1742. * {
  1743. * context: "value",
  1744. * ...options,
  1745. * },
  1746. * // or:
  1747. * function() {
  1748. * return {
  1749. * context: "context",
  1750. * ...options,
  1751. * };
  1752. * }
  1753. * ]
  1754. */
  1755. for (const proxyConfigOrCallback of this.options.proxy) {
  1756. /**
  1757. * @type {RequestHandler}
  1758. */
  1759. let proxyMiddleware;
  1760. let proxyConfig = typeof proxyConfigOrCallback === "function" ? proxyConfigOrCallback() : proxyConfigOrCallback;
  1761. proxyMiddleware = /** @type {RequestHandler} */
  1762. getProxyMiddleware(proxyConfig);
  1763. if (proxyConfig.ws) {
  1764. this.webSocketProxies.push(proxyMiddleware);
  1765. }
  1766. /**
  1767. * @param {Request} req request
  1768. * @param {Response} res response
  1769. * @param {NextFunction} next next function
  1770. * @returns {Promise<void>}
  1771. */
  1772. const handler = async (req, res, next) => {
  1773. if (typeof proxyConfigOrCallback === "function") {
  1774. const newProxyConfig = proxyConfigOrCallback(req, res, next);
  1775. if (newProxyConfig !== proxyConfig) {
  1776. proxyConfig = newProxyConfig;
  1777. const socket = req.socket || req.connection;
  1778. // @ts-expect-error
  1779. const server = socket ? socket.server : null;
  1780. if (server) {
  1781. server.removeAllListeners("close");
  1782. }
  1783. proxyMiddleware = /** @type {RequestHandler} */
  1784. getProxyMiddleware(proxyConfig);
  1785. }
  1786. }
  1787. return proxyMiddleware(req, res, next);
  1788. };
  1789. middlewares.push({
  1790. name: "http-proxy-middleware",
  1791. middleware: handler
  1792. });
  1793. // Also forward error requests to the proxy so it can handle them.
  1794. middlewares.push({
  1795. name: "http-proxy-middleware-error-handler",
  1796. middleware:
  1797. /**
  1798. * @param {Error} error error
  1799. * @param {Request} req request
  1800. * @param {Response} res response
  1801. * @param {NextFunction} next next function
  1802. * @returns {Promise<void>} nothing
  1803. */
  1804. (error, req, res, next) => handler(req, res, next)
  1805. });
  1806. }
  1807. middlewares.push({
  1808. name: "webpack-dev-middleware",
  1809. middleware: (/** @type {MiddlewareHandler} */this.middleware)
  1810. });
  1811. }
  1812. const staticOptions = /** @type {NormalizedStatic[]} */
  1813. this.options.static;
  1814. if (staticOptions.length > 0) {
  1815. for (const staticOption of staticOptions) {
  1816. for (const publicPath of staticOption.publicPath) {
  1817. middlewares.push({
  1818. name: "express-static",
  1819. path: publicPath,
  1820. middleware: (await getExpress()).static(staticOption.directory, staticOption.staticOptions)
  1821. });
  1822. }
  1823. }
  1824. }
  1825. if (this.options.historyApiFallback) {
  1826. const {
  1827. default: connectHistoryApiFallback
  1828. } = await import("connect-history-api-fallback");
  1829. const {
  1830. historyApiFallback
  1831. } = this.options;
  1832. if (typeof (/** @type {ConnectHistoryApiFallbackOptions} */
  1833. historyApiFallback.logger) === "undefined" && !(/** @type {ConnectHistoryApiFallbackOptions} */
  1834. historyApiFallback.verbose)) {
  1835. // @ts-expect-error
  1836. historyApiFallback.logger = this.logger.log.bind(this.logger, "[connect-history-api-fallback]");
  1837. }
  1838. // Fall back to /index.html if nothing else matches.
  1839. middlewares.push({
  1840. name: "connect-history-api-fallback",
  1841. middleware: connectHistoryApiFallback(/** @type {ConnectHistoryApiFallbackOptions} */
  1842. historyApiFallback)
  1843. });
  1844. // include our middleware to ensure
  1845. // it is able to handle '/index.html' request after redirect
  1846. middlewares.push({
  1847. name: "webpack-dev-middleware",
  1848. middleware: (/** @type {MiddlewareHandler} */this.middleware)
  1849. });
  1850. if (staticOptions.length > 0) {
  1851. for (const staticOption of staticOptions) {
  1852. for (const publicPath of staticOption.publicPath) {
  1853. middlewares.push({
  1854. name: "express-static",
  1855. path: publicPath,
  1856. middleware: (await getExpress()).static(staticOption.directory, staticOption.staticOptions)
  1857. });
  1858. }
  1859. }
  1860. }
  1861. }
  1862. if (staticOptions.length > 0) {
  1863. const {
  1864. default: serveIndex
  1865. } = await import("serve-index");
  1866. for (const staticOption of staticOptions) {
  1867. for (const publicPath of staticOption.publicPath) {
  1868. if (staticOption.serveIndex) {
  1869. middlewares.push({
  1870. name: "serve-index",
  1871. path: publicPath,
  1872. /**
  1873. * @param {Request} req request
  1874. * @param {Response} res response
  1875. * @param {NextFunction} next next function
  1876. * @returns {void}
  1877. */
  1878. middleware: (req, res, next) => {
  1879. // serve-index doesn't fallthrough non-get/head request to next middleware
  1880. if (req.method !== "GET" && req.method !== "HEAD") {
  1881. return next();
  1882. }
  1883. serveIndex(staticOption.directory, /** @type {ServeIndexOptions} */
  1884. staticOption.serveIndex)(req, res, next);
  1885. }
  1886. });
  1887. }
  1888. }
  1889. }
  1890. }
  1891. // Register this middleware always as the last one so that it's only used as a
  1892. // fallback when no other middleware responses.
  1893. middlewares.push({
  1894. name: "options-middleware",
  1895. /**
  1896. * @param {Request} req request
  1897. * @param {Response} res response
  1898. * @param {NextFunction} next next function
  1899. * @returns {void}
  1900. */
  1901. middleware: (req, res, next) => {
  1902. if (req.method === "OPTIONS") {
  1903. res.statusCode = 204;
  1904. res.setHeader("Content-Length", "0");
  1905. res.end();
  1906. return;
  1907. }
  1908. next();
  1909. }
  1910. });
  1911. if (typeof this.options.setupMiddlewares === "function") {
  1912. middlewares = this.options.setupMiddlewares(middlewares, this);
  1913. }
  1914. // Lazy init webpack dev middleware
  1915. const lazyInitDevMiddleware = async () => {
  1916. if (!this.middleware) {
  1917. const {
  1918. default: webpackDevMiddleware
  1919. } = await import("webpack-dev-middleware");
  1920. // middleware for serving webpack bundle
  1921. /** @type {import("webpack-dev-middleware").API<Request, Response>} */
  1922. this.middleware = webpackDevMiddleware(
  1923. // @ts-expect-error
  1924. this.compiler, this.options.devMiddleware, this.isPlugin);
  1925. }
  1926. return this.middleware;
  1927. };
  1928. for (const i of middlewares) {
  1929. if (i.name === "webpack-dev-middleware") {
  1930. const item = /** @type {MiddlewareObject} */i;
  1931. if (typeof item.middleware === "undefined") {
  1932. item.middleware = await lazyInitDevMiddleware();
  1933. }
  1934. }
  1935. }
  1936. for (const middleware of middlewares) {
  1937. if (typeof middleware === "function") {
  1938. /** @type {A} */
  1939. this.app.use(/** @type {NextHandleFunction | HandleFunction} */
  1940. middleware);
  1941. } else if (typeof middleware.path !== "undefined") {
  1942. /** @type {A} */
  1943. this.app.use(middleware.path, /** @type {SimpleHandleFunction | NextHandleFunction} */
  1944. middleware.middleware);
  1945. } else {
  1946. /** @type {A} */
  1947. this.app.use(/** @type {NextHandleFunction | HandleFunction} */
  1948. middleware.middleware);
  1949. }
  1950. }
  1951. }
  1952. /**
  1953. * @private
  1954. * @returns {Promise<void>}
  1955. */
  1956. async createServer() {
  1957. const {
  1958. type,
  1959. options
  1960. } = /** @type {ServerConfiguration<A, S>} */
  1961. this.options.server;
  1962. if (typeof type === "function") {
  1963. /** @type {S | undefined} */
  1964. this.server = await type(/** @type {ServerOptions} */
  1965. options, /** @type {A} */
  1966. this.app);
  1967. } else {
  1968. const mod = cjsRequire(/** @type {string} */type);
  1969. const serverType = mod.default || mod;
  1970. /** @type {S | undefined} */
  1971. this.server = type === "http2" ? serverType.createSecureServer({
  1972. ...options,
  1973. allowHTTP1: true
  1974. }, this.app) : serverType.createServer(options, this.app);
  1975. }
  1976. this.isTlsServer = typeof (/** @type {import("tls").Server} */this.server.setSecureContext) !== "undefined";
  1977. /** @type {S} */
  1978. this.server.on("connection",
  1979. /**
  1980. * @param {Socket} socket connected socket
  1981. */
  1982. socket => {
  1983. // Add socket to list
  1984. this.sockets.push(socket);
  1985. socket.once("close", () => {
  1986. // Remove socket from list
  1987. this.sockets.splice(this.sockets.indexOf(socket), 1);
  1988. });
  1989. });
  1990. /** @type {S} */
  1991. this.server.on("error",
  1992. /**
  1993. * @param {Error} error error
  1994. */
  1995. error => {
  1996. throw error;
  1997. });
  1998. }
  1999. /**
  2000. * @private
  2001. * @returns {Promise<void>}
  2002. */
  2003. async createWebSocketServer() {
  2004. /** @type {WebSocketServerImplementation | undefined | null} */
  2005. this.webSocketServer = new (await this.getServerTransport())(this);
  2006. /** @type {WebSocketServerImplementation} */
  2007. this.webSocketServer.implementation.on("connection",
  2008. /**
  2009. * @param {ClientConnection} client client
  2010. * @param {IncomingMessage} request request
  2011. */
  2012. (client, request) => {
  2013. /** @type {{ [key: string]: string | undefined } | undefined} */
  2014. const headers = typeof request !== "undefined" ? (/** @type {{ [key: string]: string | undefined }} */
  2015. request.headers) : undefined;
  2016. if (!headers) {
  2017. this.logger.warn('webSocketServer implementation must pass headers for the "connection" event');
  2018. }
  2019. if (!headers || !this.isValidHost(headers, "host") || !this.isValidHost(headers, "origin") || !this.isSameOrigin(headers)) {
  2020. this.sendMessage([client], "error", "Invalid Host/Origin header");
  2021. // With https enabled, the sendMessage above is encrypted asynchronously so not yet sent
  2022. // Terminate would prevent it sending, so use close to allow it to be sent
  2023. client.close();
  2024. return;
  2025. }
  2026. if (this.options.hot === true || this.options.hot === "only") {
  2027. this.sendMessage([client], "hot");
  2028. }
  2029. if (this.options.liveReload) {
  2030. this.sendMessage([client], "liveReload");
  2031. }
  2032. if (this.options.client && /** @type {ClientConfiguration} */
  2033. this.options.client.progress) {
  2034. this.sendMessage([client], "progress", /** @type {ClientConfiguration} */
  2035. this.options.client.progress);
  2036. }
  2037. if (this.options.client && /** @type {ClientConfiguration} */
  2038. this.options.client.reconnect) {
  2039. this.sendMessage([client], "reconnect", /** @type {ClientConfiguration} */
  2040. this.options.client.reconnect);
  2041. }
  2042. if (this.options.client && /** @type {ClientConfiguration} */
  2043. this.options.client.overlay) {
  2044. const overlayConfig = /** @type {ClientConfiguration} */
  2045. this.options.client.overlay;
  2046. this.sendMessage([client], "overlay", typeof overlayConfig === "object" ? {
  2047. ...overlayConfig,
  2048. errors: overlayConfig.errors && encodeOverlaySettings(overlayConfig.errors),
  2049. warnings: overlayConfig.warnings && encodeOverlaySettings(overlayConfig.warnings),
  2050. runtimeErrors: overlayConfig.runtimeErrors && encodeOverlaySettings(overlayConfig.runtimeErrors)
  2051. } : overlayConfig);
  2052. }
  2053. if (!this.stats) {
  2054. return;
  2055. }
  2056. this.sendStats([client], this.getStats(this.stats), true);
  2057. });
  2058. }
  2059. /**
  2060. * @private
  2061. * @param {string} defaultOpenTarget default open target
  2062. * @returns {Promise<void>}
  2063. */
  2064. async openBrowser(defaultOpenTarget) {
  2065. const open = (await import("open")).default;
  2066. Promise.all(/** @type {NormalizedOpen[]} */
  2067. this.options.open.map(item => {
  2068. /**
  2069. * @type {string}
  2070. */
  2071. let openTarget;
  2072. if (item.target === "<url>") {
  2073. openTarget = defaultOpenTarget;
  2074. } else {
  2075. openTarget = Server.isAbsoluteURL(item.target) ? item.target : new URL(item.target, defaultOpenTarget).toString();
  2076. }
  2077. return open(openTarget, item.options).catch(() => {
  2078. this.logger.warn(`Unable to open "${openTarget}" page${item.options.app ? ` in "${ /** @type {import("open").App} */
  2079. item.options.app.name}" app${ /** @type {import("open").App} */
  2080. item.options.app.arguments ? ` with "${ /** @type {import("open").App} */
  2081. item.options.app.arguments.join(" ")}" arguments` : ""}` : ""}. If you are running in a headless environment, please do not use the "open" option or related flags like "--open", "--open-target", and "--open-app-name".`);
  2082. });
  2083. }));
  2084. }
  2085. /**
  2086. * @private
  2087. * @returns {Promise<void>}
  2088. */
  2089. async runBonjour() {
  2090. const {
  2091. Bonjour
  2092. } = await import("bonjour-service");
  2093. const type = this.isTlsServer ? "https" : "http";
  2094. /**
  2095. * @private
  2096. * @type {Bonjour | undefined}
  2097. */
  2098. this.bonjour = new Bonjour();
  2099. this.bonjour.publish({
  2100. name: `Webpack Dev Server ${_nodeOs.default.hostname()}:${this.options.port}`,
  2101. port: (/** @type {number} */this.options.port),
  2102. type,
  2103. subtypes: ["webpack"],
  2104. ...(/** @type {Partial<BonjourOptions>} */this.options.bonjour)
  2105. });
  2106. }
  2107. /**
  2108. * @private
  2109. * @param {() => void} callback callback
  2110. * @returns {void}
  2111. */
  2112. stopBonjour(callback = () => {}) {
  2113. /** @type {Bonjour} */
  2114. this.bonjour.unpublishAll(() => {
  2115. /** @type {Bonjour} */
  2116. this.bonjour.destroy();
  2117. if (callback) {
  2118. callback();
  2119. }
  2120. });
  2121. }
  2122. /**
  2123. * @private
  2124. * @returns {Promise<void>}
  2125. */
  2126. async logStatus() {
  2127. const {
  2128. isColorSupported,
  2129. colors: {
  2130. cyan,
  2131. red
  2132. }
  2133. } = this.#getColors();
  2134. /**
  2135. * @param {Compiler["options"]} compilerOptions compiler options
  2136. * @returns {boolean} value of the color option
  2137. */
  2138. const getColorsOption = compilerOptions => {
  2139. /**
  2140. * @type {boolean}
  2141. */
  2142. let colorsEnabled;
  2143. if (compilerOptions.stats && typeof (/** @type {StatsOptions} */compilerOptions.stats.colors) !== "undefined") {
  2144. colorsEnabled = /** @type {boolean} */
  2145. /** @type {StatsOptions} */compilerOptions.stats.colors;
  2146. } else {
  2147. colorsEnabled = isColorSupported();
  2148. }
  2149. return colorsEnabled;
  2150. };
  2151. const colors = {
  2152. /**
  2153. * @param {boolean} useColor need to use color?
  2154. * @param {string} msg message
  2155. * @returns {string} message with color
  2156. */
  2157. info(useColor, msg) {
  2158. if (useColor) {
  2159. return cyan(msg);
  2160. }
  2161. return msg;
  2162. },
  2163. /**
  2164. * @param {boolean} useColor need to use color?
  2165. * @param {string} msg message
  2166. * @returns {string} message with colors
  2167. */
  2168. error(useColor, msg) {
  2169. if (useColor) {
  2170. return red(msg);
  2171. }
  2172. return msg;
  2173. }
  2174. };
  2175. const useColor = getColorsOption(this.getCompilerOptions());
  2176. const server = /** @type {S} */this.server;
  2177. if (this.options.ipc) {
  2178. this.logger.info(`Project is running at: "${server.address()}"`);
  2179. } else {
  2180. const protocol = this.isTlsServer ? "https" : "http";
  2181. const {
  2182. address,
  2183. port
  2184. } = /** @type {import("net").AddressInfo} */
  2185. server.address();
  2186. /**
  2187. * @param {string} newHostname new hostname
  2188. * @returns {string} prettified URL
  2189. */
  2190. const prettyPrintURL = newHostname => _nodeUrl.default.format({
  2191. protocol,
  2192. hostname: newHostname,
  2193. port,
  2194. pathname: "/"
  2195. });
  2196. let host;
  2197. let localhost;
  2198. let loopbackIPv4;
  2199. let loopbackIPv6;
  2200. let networkUrlIPv4;
  2201. let networkUrlIPv6;
  2202. if (this.options.host) {
  2203. if (this.options.host === "localhost") {
  2204. localhost = prettyPrintURL("localhost");
  2205. } else {
  2206. let isIP;
  2207. try {
  2208. isIP = _ipaddr.default.parse(this.options.host);
  2209. } catch {
  2210. // Ignore
  2211. }
  2212. if (!isIP) {
  2213. host = prettyPrintURL(this.options.host);
  2214. }
  2215. }
  2216. }
  2217. const parsedIP = _ipaddr.default.parse(address);
  2218. if (parsedIP.range() === "unspecified") {
  2219. localhost = prettyPrintURL("localhost");
  2220. loopbackIPv6 = prettyPrintURL("::1");
  2221. const networkIPv4 = Server.findIp("v4", false);
  2222. if (networkIPv4) {
  2223. networkUrlIPv4 = prettyPrintURL(networkIPv4);
  2224. }
  2225. const networkIPv6 = Server.findIp("v6", false);
  2226. if (networkIPv6) {
  2227. networkUrlIPv6 = prettyPrintURL(networkIPv6);
  2228. }
  2229. } else if (parsedIP.range() === "loopback") {
  2230. if (parsedIP.kind() === "ipv4") {
  2231. loopbackIPv4 = prettyPrintURL(parsedIP.toString());
  2232. } else if (parsedIP.kind() === "ipv6") {
  2233. loopbackIPv6 = prettyPrintURL(parsedIP.toString());
  2234. }
  2235. } else {
  2236. networkUrlIPv4 = parsedIP.kind() === "ipv6" && /** @type {IPv6} */
  2237. parsedIP.isIPv4MappedAddress() ? prettyPrintURL(/** @type {IPv6} */
  2238. parsedIP.toIPv4Address().toString()) : prettyPrintURL(address);
  2239. if (parsedIP.kind() === "ipv6") {
  2240. networkUrlIPv6 = prettyPrintURL(address);
  2241. }
  2242. }
  2243. this.logger.info("Project is running at:");
  2244. if (host) {
  2245. this.logger.info(`Server: ${colors.info(useColor, host)}`);
  2246. }
  2247. if (localhost || loopbackIPv4 || loopbackIPv6) {
  2248. const loopbacks = [];
  2249. if (localhost) {
  2250. loopbacks.push([colors.info(useColor, localhost)]);
  2251. }
  2252. if (loopbackIPv4) {
  2253. loopbacks.push([colors.info(useColor, loopbackIPv4)]);
  2254. }
  2255. if (loopbackIPv6) {
  2256. loopbacks.push([colors.info(useColor, loopbackIPv6)]);
  2257. }
  2258. this.logger.info(`Loopback: ${loopbacks.join(", ")}`);
  2259. }
  2260. if (networkUrlIPv4) {
  2261. this.logger.info(`On Your Network (IPv4): ${colors.info(useColor, networkUrlIPv4)}`);
  2262. }
  2263. if (networkUrlIPv6) {
  2264. this.logger.info(`On Your Network (IPv6): ${colors.info(useColor, networkUrlIPv6)}`);
  2265. }
  2266. if (/** @type {NormalizedOpen[]} */this.options.open.length > 0) {
  2267. const openTarget = prettyPrintURL(!this.options.host || this.options.host === "0.0.0.0" || this.options.host === "::" ? "localhost" : this.options.host);
  2268. await this.openBrowser(openTarget);
  2269. }
  2270. }
  2271. if (/** @type {NormalizedStatic[]} */this.options.static.length > 0) {
  2272. this.logger.info(`Content not from webpack is served from '${colors.info(useColor, /** @type {NormalizedStatic[]} */
  2273. this.options.static.map(staticOption => staticOption.directory).join(", "))}' directory`);
  2274. }
  2275. if (this.options.historyApiFallback) {
  2276. this.logger.info(`404s will fallback to '${colors.info(useColor, /** @type {ConnectHistoryApiFallbackOptions} */this.options.historyApiFallback.index || "/index.html")}'`);
  2277. }
  2278. if (this.options.bonjour) {
  2279. const bonjourProtocol = /** @type {BonjourOptions} */
  2280. this.options.bonjour.type || this.isTlsServer ? "https" : "http";
  2281. this.logger.info(`Broadcasting "${bonjourProtocol}" with subtype of "webpack" via ZeroConf DNS (Bonjour)`);
  2282. }
  2283. }
  2284. /**
  2285. * @private
  2286. * @param {Request} req request
  2287. * @param {Response} res response
  2288. * @param {NextFunction} next next function
  2289. */
  2290. setHeaders(req, res, next) {
  2291. let {
  2292. headers
  2293. } = this.options;
  2294. if (headers) {
  2295. if (typeof headers === "function") {
  2296. headers = headers(req, res, this.middleware ? this.middleware.context : undefined);
  2297. }
  2298. /**
  2299. * @type {{ key: string, value: string }[]}
  2300. */
  2301. const allHeaders = [];
  2302. if (!Array.isArray(headers)) {
  2303. for (const name in headers) {
  2304. allHeaders.push({
  2305. key: name,
  2306. value: (/** @type {string} */headers[name])
  2307. });
  2308. }
  2309. headers = allHeaders;
  2310. }
  2311. for (const {
  2312. key,
  2313. value
  2314. } of headers) {
  2315. res.setHeader(key, value);
  2316. }
  2317. }
  2318. next();
  2319. }
  2320. /**
  2321. * @private
  2322. * @param {string} value value
  2323. * @returns {boolean} true when host allowed, otherwise false
  2324. */
  2325. isHostAllowed(value) {
  2326. const {
  2327. allowedHosts
  2328. } = this.options;
  2329. // allow user to opt out of this security check, at their own risk
  2330. // by explicitly enabling allowedHosts
  2331. if (allowedHosts === "all") {
  2332. return true;
  2333. }
  2334. // always allow localhost host, for convenience
  2335. // allow if value is in allowedHosts
  2336. if (Array.isArray(allowedHosts) && allowedHosts.length > 0) {
  2337. for (const allowedHost of allowedHosts) {
  2338. if (allowedHost === value) {
  2339. return true;
  2340. }
  2341. // support "." as a subdomain wildcard
  2342. // e.g. ".example.com" will allow "example.com", "www.example.com", "subdomain.example.com", etc
  2343. if (allowedHost.startsWith(".") && (
  2344. // "example.com" (value === allowedHost.substring(1))
  2345. // "*.example.com" (value.endsWith(allowedHost))
  2346. value === allowedHost.slice(1) || /** @type {string} */
  2347. value.endsWith(allowedHost))) {
  2348. return true;
  2349. }
  2350. }
  2351. }
  2352. // Also allow if `client.webSocketURL.hostname` provided
  2353. if (this.options.client && typeof (/** @type {ClientConfiguration} */
  2354. this.options.client.webSocketURL) !== "undefined") {
  2355. return /** @type {WebSocketURL} */(/** @type {ClientConfiguration} */this.options.client.webSocketURL).hostname === value;
  2356. }
  2357. return false;
  2358. }
  2359. /**
  2360. * Extracts and normalizes the hostname from a header, removing brackets for IPv6.
  2361. * @param {string} header header value
  2362. * @returns {string | null} hostname or null
  2363. */
  2364. #parseHostnameFromHeader = function (header) {
  2365. if (!header) return null;
  2366. try {
  2367. // If the header does not have a scheme, prepend // so URL can parse it
  2368. const parseUrl = new URL(/^(.+:)?\/\//.test(header) ? header : `//${header}`, "http://localhost/");
  2369. let hostname = parseUrl.hostname;
  2370. // Normalize IPv6: remove brackets if present
  2371. if (hostname.startsWith("[") && hostname.endsWith("]")) {
  2372. hostname = hostname.slice(1, -1);
  2373. }
  2374. return hostname;
  2375. } catch {
  2376. return null;
  2377. }
  2378. };
  2379. /**
  2380. * @private
  2381. * @returns {boolean} true when the user has configured a wildcard
  2382. * Access-Control-Allow-Origin header (opting into fully open cross-origin access)
  2383. */
  2384. isUserCORSWildcardEnabled() {
  2385. const {
  2386. headers
  2387. } = this.options;
  2388. if (!headers) {
  2389. return false;
  2390. }
  2391. if (typeof headers === "function") {
  2392. return false;
  2393. }
  2394. /**
  2395. * @param {string | string[]} value header value
  2396. * @returns {boolean} true when value is the "*" wildcard
  2397. */
  2398. const isWildcard = value => {
  2399. if (typeof value === "string") {
  2400. return value.trim() === "*";
  2401. }
  2402. if (Array.isArray(value)) {
  2403. return value.length === 1 && isWildcard(value[0]);
  2404. }
  2405. return false;
  2406. };
  2407. if (Array.isArray(headers)) {
  2408. return headers.some(header => header.key.toLowerCase() === "access-control-allow-origin" && isWildcard(header.value));
  2409. }
  2410. return Object.entries(headers).some(([key, value]) => key.toLowerCase() === "access-control-allow-origin" && isWildcard(value));
  2411. }
  2412. /**
  2413. * @private
  2414. * @param {{ [key: string]: string | undefined }} headers headers
  2415. * @param {string} headerToCheck header to check
  2416. * @param {boolean} validateHost need to validate host
  2417. * @returns {boolean} true when host is valid, otherwise false
  2418. */
  2419. isValidHost(headers, headerToCheck, validateHost = true) {
  2420. if (this.options.allowedHosts === "all") {
  2421. return true;
  2422. }
  2423. // get the Host header and extract hostname
  2424. // we don't care about port not matching
  2425. const header = headers[headerToCheck];
  2426. if (!header) {
  2427. return false;
  2428. }
  2429. if (DEFAULT_ALLOWED_PROTOCOLS.test(header)) {
  2430. return true;
  2431. }
  2432. const hostname = this.#parseHostnameFromHeader(header);
  2433. if (hostname === null) {
  2434. return false;
  2435. }
  2436. if (this.isHostAllowed(hostname)) {
  2437. return true;
  2438. }
  2439. // always allow requests with explicit IPv4 or IPv6-address.
  2440. // A note on IPv6 addresses:
  2441. // header will always contain the brackets denoting
  2442. // an IPv6-address in URLs,
  2443. // these aren't removed from the hostname in new URL(),
  2444. // For convenience, always allow localhost (hostname === 'localhost')
  2445. // and its subdomains (hostname.endsWith(".localhost")).
  2446. // allow hostname of listening address (hostname === this.options.host)
  2447. const isValidHostname = validateHost ? _ipaddr.default.IPv4.isValid(hostname) || _ipaddr.default.IPv6.isValid(hostname) || hostname === "localhost" || hostname.endsWith(".localhost") || hostname === this.options.host : false;
  2448. return isValidHostname;
  2449. }
  2450. /**
  2451. * @private
  2452. * @param {{ [key: string]: string | undefined }} headers headers
  2453. * @returns {boolean} true when is same origin, otherwise false
  2454. */
  2455. isSameOrigin(headers) {
  2456. if (this.options.allowedHosts === "all") {
  2457. return true;
  2458. }
  2459. const originHeader = headers.origin;
  2460. if (!originHeader) {
  2461. return this.options.allowedHosts === "all";
  2462. }
  2463. if (DEFAULT_ALLOWED_PROTOCOLS.test(originHeader)) {
  2464. return true;
  2465. }
  2466. const origin = this.#parseHostnameFromHeader(originHeader);
  2467. if (origin === null) {
  2468. return false;
  2469. }
  2470. if (this.isHostAllowed(origin)) {
  2471. return true;
  2472. }
  2473. const hostHeader = headers.host;
  2474. if (!hostHeader) {
  2475. return this.options.allowedHosts === "all";
  2476. }
  2477. if (DEFAULT_ALLOWED_PROTOCOLS.test(hostHeader)) {
  2478. return true;
  2479. }
  2480. const host = this.#parseHostnameFromHeader(hostHeader);
  2481. if (host === null) {
  2482. return false;
  2483. }
  2484. if (this.isHostAllowed(host)) {
  2485. return true;
  2486. }
  2487. // Treat all loopback aliases as equivalent: localhost may resolve to
  2488. // 127.0.0.1 or ::1 depending on the OS, causing a false mismatch.
  2489. // Only widen when allowedHosts is "auto" (default) or already permits a
  2490. // loopback alias, so an explicit allow-list excluding loopback is honored.
  2491. const loopbacks = new Set(["localhost", "127.0.0.1", "::1"]);
  2492. const loopbackPermitted = this.options.allowedHosts === "auto" || [...loopbacks].some(alias => this.isHostAllowed(alias));
  2493. if (loopbacks.has(origin) && loopbacks.has(host) && loopbackPermitted) {
  2494. return true;
  2495. }
  2496. return origin === host;
  2497. }
  2498. /**
  2499. * Determines whether a request was initiated from the dev server's own
  2500. * origin, to reject cross-site request forgery on state-changing endpoints.
  2501. * @param {Request} req request
  2502. * @returns {boolean} true when the request can be trusted as same-origin
  2503. */
  2504. #isSameOriginRequest(req) {
  2505. const headers = /** @type {{ [key: string]: string | undefined }} */
  2506. req.headers;
  2507. const secFetchSite = headers["sec-fetch-site"];
  2508. // Prefer `Sec-Fetch-Site`: browsers send it even for same-origin GET
  2509. // `fetch`es (which omit `Origin`), and a cross-site page cannot forge it.
  2510. // `none` is a user-initiated navigation.
  2511. if (typeof secFetchSite === "string") {
  2512. return secFetchSite === "same-origin" || secFetchSite === "none";
  2513. }
  2514. // Without `Sec-Fetch-*` metadata (non-browser/legacy clients): a request
  2515. // with no `Origin` header was not initiated by another origin's script;
  2516. // otherwise fall back to the `Origin`/`Host` comparison.
  2517. if (typeof headers.origin !== "string") {
  2518. return true;
  2519. }
  2520. return this.isSameOrigin(headers);
  2521. }
  2522. /**
  2523. * @param {ClientConnection[]} clients clients
  2524. * @param {string} type type
  2525. * @param {EXPECTED_ANY=} data data
  2526. * @param {EXPECTED_ANY=} params params
  2527. */
  2528. sendMessage(clients, type, data, params) {
  2529. for (const client of clients) {
  2530. // `ws` uses `WebSocket.OPEN`, which is `1`
  2531. if (client.readyState === 1) {
  2532. client.send(JSON.stringify({
  2533. type,
  2534. data,
  2535. params
  2536. }));
  2537. }
  2538. }
  2539. }
  2540. // Send stats to a socket or multiple sockets
  2541. /**
  2542. * @private
  2543. * @param {ClientConnection[]} clients clients
  2544. * @param {StatsCompilation} stats stats
  2545. * @param {boolean=} force force
  2546. */
  2547. sendStats(clients, stats, force) {
  2548. const shouldEmit = !force && stats && (!stats.errors || stats.errors.length === 0) && (!stats.warnings || stats.warnings.length === 0) && this.currentHash === stats.hash;
  2549. if (shouldEmit) {
  2550. this.sendMessage(clients, "still-ok");
  2551. return;
  2552. }
  2553. this.currentHash = stats.hash;
  2554. this.sendMessage(clients, "hash", stats.hash);
  2555. if (/** @type {NonNullable<StatsCompilation["errors"]>} */
  2556. stats.errors.length > 0 || /** @type {NonNullable<StatsCompilation["warnings"]>} */
  2557. stats.warnings.length > 0) {
  2558. const hasErrors = /** @type {NonNullable<StatsCompilation["errors"]>} */
  2559. stats.errors.length > 0;
  2560. if (/** @type {NonNullable<StatsCompilation["warnings"]>} */
  2561. stats.warnings.length > 0) {
  2562. let params;
  2563. if (hasErrors) {
  2564. params = {
  2565. preventReloading: true
  2566. };
  2567. }
  2568. this.sendMessage(clients, "warnings", stats.warnings, params);
  2569. }
  2570. if (/** @type {NonNullable<StatsCompilation["errors"]>} */stats.errors.length > 0) {
  2571. this.sendMessage(clients, "errors", stats.errors);
  2572. }
  2573. } else {
  2574. this.sendMessage(clients, "ok");
  2575. }
  2576. }
  2577. /**
  2578. * @param {string | string[]} watchPath watch path
  2579. * @param {WatchOptions=} watchOptions watch options
  2580. * @returns {Promise<void>}
  2581. */
  2582. async watchFiles(watchPath, watchOptions = {}) {
  2583. const {
  2584. default: chokidar
  2585. } = await import("chokidar");
  2586. const {
  2587. globSync,
  2588. isDynamicPattern
  2589. } = await import("tinyglobby");
  2590. const isWin = _nodePath.default.sep === "\\";
  2591. const toPosix = (/** @type {string} */filePath) => isWin ? filePath.split(_nodePath.default.sep).join("/") : filePath;
  2592. const toNative = (/** @type {string} */filePath) => isWin ? filePath.split("/").join(_nodePath.default.sep) : filePath;
  2593. const cwd = watchOptions.cwd ? toPosix(watchOptions.cwd) : undefined;
  2594. const expand = (/** @type {string} */item) => {
  2595. const posix = toPosix(item);
  2596. return isDynamicPattern(posix) ? globSync(posix, {
  2597. cwd,
  2598. absolute: true
  2599. }).map(toNative) : item;
  2600. };
  2601. const resolveGlobs = (/** @type {string | string[]} */input) => (Array.isArray(input) ? input : [input]).flatMap(item => typeof item === "string" ? expand(item) : item);
  2602. const resolvedPaths = resolveGlobs(watchPath);
  2603. if (typeof watchOptions.ignored === "string") {
  2604. watchOptions.ignored = resolveGlobs(watchOptions.ignored);
  2605. } else if (Array.isArray(watchOptions.ignored)) {
  2606. watchOptions.ignored = watchOptions.ignored.flatMap(item => typeof item === "string" ? expand(item) : item);
  2607. }
  2608. const watcher = chokidar.watch(resolvedPaths, watchOptions);
  2609. // disabling refreshing on changing the content
  2610. if (this.options.liveReload) {
  2611. watcher.on("change", item => {
  2612. if (this.webSocketServer) {
  2613. this.sendMessage(this.webSocketServer.clients, "static-changed", item);
  2614. }
  2615. });
  2616. }
  2617. this.staticWatchers.push(watcher);
  2618. }
  2619. /**
  2620. * @param {import("webpack-dev-middleware").Callback=} callback callback
  2621. */
  2622. invalidate(callback = () => {}) {
  2623. // In plugin mode the host owns `compiler.watch()`, so the middleware has no
  2624. // `watching` of its own — invalidate the host's watching(s) directly to
  2625. // trigger a rebuild (each child's own `watching` for a `MultiCompiler`).
  2626. if (this.isPlugin) {
  2627. const compilers = /** @type {MultiCompiler} */this.compiler.compilers || (/** @type {Compiler[]} */[this.compiler]);
  2628. /** @type {NonNullable<Compiler["watching"]>[]} */
  2629. const watchings = [];
  2630. for (const compiler of compilers) {
  2631. if (compiler.watching) {
  2632. watchings.push(compiler.watching);
  2633. }
  2634. }
  2635. if (watchings.length === 0) {
  2636. callback();
  2637. return;
  2638. }
  2639. let pending = watchings.length;
  2640. const onInvalidated = () => {
  2641. pending -= 1;
  2642. if (pending === 0) {
  2643. callback();
  2644. }
  2645. };
  2646. for (const watching of watchings) {
  2647. watching.invalidate(onInvalidated);
  2648. }
  2649. return;
  2650. }
  2651. if (this.middleware) {
  2652. this.middleware.invalidate(callback);
  2653. }
  2654. }
  2655. /**
  2656. * @returns {Promise<void>}
  2657. */
  2658. async start() {
  2659. await this.setup();
  2660. await this.listen();
  2661. }
  2662. /**
  2663. * @private
  2664. * @returns {Promise<void>}
  2665. */
  2666. async setup() {
  2667. await this.normalizeOptions();
  2668. if (this.options.ipc) {
  2669. const {
  2670. default: net
  2671. } = await import("node:net");
  2672. await (/** @type {Promise<void>} */
  2673. new Promise((resolve, reject) => {
  2674. const socket = new net.Socket();
  2675. socket.on("error",
  2676. /**
  2677. * @param {Error & { code?: string }} error error
  2678. */
  2679. error => {
  2680. if (error.code === "ECONNREFUSED") {
  2681. // No other server listening on this socket, so it can be safely removed
  2682. _gracefulFs.default.unlinkSync(/** @type {string} */this.options.ipc);
  2683. resolve();
  2684. return;
  2685. } else if (error.code === "ENOENT") {
  2686. resolve();
  2687. return;
  2688. }
  2689. reject(error);
  2690. });
  2691. socket.connect({
  2692. path: (/** @type {string} */this.options.ipc)
  2693. }, () => {
  2694. throw new Error(`IPC "${this.options.ipc}" is already used`);
  2695. });
  2696. }));
  2697. } else {
  2698. this.options.host = await Server.getHostname(/** @type {Host} */this.options.host);
  2699. this.options.port = await Server.getFreePort(/** @type {Port} */this.options.port, this.options.host);
  2700. }
  2701. await this.initialize();
  2702. }
  2703. /**
  2704. * @private
  2705. * @returns {Promise<void>}
  2706. */
  2707. async listen() {
  2708. const listenOptions = this.options.ipc ? {
  2709. path: this.options.ipc
  2710. } : {
  2711. host: this.options.host,
  2712. port: this.options.port
  2713. };
  2714. await (/** @type {Promise<void>} */
  2715. new Promise(resolve => {
  2716. /** @type {S} */
  2717. this.server.listen(listenOptions, () => {
  2718. resolve();
  2719. });
  2720. }));
  2721. if (this.options.ipc) {
  2722. // chmod 666 (rw rw rw)
  2723. const READ_WRITE = 438;
  2724. await _gracefulFs.default.promises.chmod(/** @type {string} */this.options.ipc, READ_WRITE);
  2725. }
  2726. if (this.options.webSocketServer) {
  2727. await this.createWebSocketServer();
  2728. }
  2729. if (this.options.bonjour) {
  2730. await this.runBonjour();
  2731. }
  2732. await this.logStatus();
  2733. if (typeof this.options.onListening === "function") {
  2734. this.options.onListening(this);
  2735. }
  2736. }
  2737. /**
  2738. * @param {((err?: Error) => void)=} callback callback
  2739. */
  2740. startCallback(callback = () => {}) {
  2741. this.start().then(() => callback(), callback).catch(callback);
  2742. }
  2743. /**
  2744. * @returns {Promise<void>}
  2745. */
  2746. async stop() {
  2747. if (this.bonjour) {
  2748. await (/** @type {Promise<void>} */
  2749. new Promise(resolve => {
  2750. this.stopBonjour(() => {
  2751. resolve();
  2752. });
  2753. }));
  2754. }
  2755. this.webSocketProxies = [];
  2756. await Promise.all(this.staticWatchers.map(watcher => watcher.close()));
  2757. this.staticWatchers = [];
  2758. if (this.webSocketServer) {
  2759. await (/** @type {Promise<void>} */
  2760. new Promise(resolve => {
  2761. /** @type {WebSocketServerImplementation} */
  2762. this.webSocketServer.implementation.close(() => {
  2763. this.webSocketServer = null;
  2764. resolve();
  2765. });
  2766. for (const client of /** @type {WebSocketServerImplementation} */this.webSocketServer.clients) {
  2767. client.terminate();
  2768. }
  2769. /** @type {WebSocketServerImplementation} */
  2770. this.webSocketServer.clients = [];
  2771. }));
  2772. }
  2773. if (this.server) {
  2774. await (/** @type {Promise<void>} */
  2775. new Promise(resolve => {
  2776. /** @type {S} */
  2777. this.server.close(() => {
  2778. this.server = undefined;
  2779. resolve();
  2780. });
  2781. for (const socket of this.sockets) {
  2782. socket.destroy();
  2783. }
  2784. this.sockets = [];
  2785. // Force-close any remaining connections that aren't in `this.sockets`
  2786. // (HTTP keep-alive idle connections from upstream clients,
  2787. // long-poll requests, sockets that were upgraded out of `connection`
  2788. // tracking, etc). Without this, `server.close()`'s callback can hang
  2789. // indefinitely waiting for connections that never close on their own
  2790. // — a problem that surfaces in test suites that rapidly start/stop
  2791. // servers on the same port (next start hits EADDRINUSE).
  2792. // `closeAllConnections` is available since Node.js 18.2.
  2793. /** @type {S & { closeAllConnections?: () => void }} */
  2794. this.server.closeAllConnections?.();
  2795. }));
  2796. if (this.middleware) {
  2797. // In plugin mode the middleware has no `watching` of its own to close
  2798. // (the host's `compiler.close()` handles it).
  2799. if (!this.isPlugin) {
  2800. await (/** @type {Promise<void>} */
  2801. new Promise((resolve, reject) => {
  2802. /** @type {import("webpack-dev-middleware").API<Request, Response>} */
  2803. this.middleware.close(error => {
  2804. if (error) {
  2805. reject(error);
  2806. return;
  2807. }
  2808. resolve();
  2809. });
  2810. }));
  2811. }
  2812. this.middleware = undefined;
  2813. }
  2814. }
  2815. // We add listeners to signals when creating a new Server instance
  2816. // So ensure they are removed to prevent EventEmitter memory leak warnings
  2817. for (const item of this.listeners) {
  2818. process.removeListener(item.name, item.listener);
  2819. }
  2820. }
  2821. /**
  2822. * @param {((err?: Error) => void)=} callback callback
  2823. */
  2824. stopCallback(callback = () => {}) {
  2825. this.stop().then(() => callback(), callback).catch(callback);
  2826. }
  2827. /**
  2828. * @param {Compiler | MultiCompiler} compiler compiler
  2829. * @returns {void}
  2830. */
  2831. apply(compiler) {
  2832. this.compiler = compiler;
  2833. this.isPlugin = true;
  2834. this.logger = this.compiler.getInfrastructureLogger(pluginName);
  2835. /** @type {Promise<void> | undefined} */
  2836. let setupPromise;
  2837. let inWatchMode = false;
  2838. let listening = false;
  2839. let stopped = false;
  2840. // `setup()` boots webpack-dev-middleware, which replaces the compiler's
  2841. // `outputFileSystem` with an in-memory one. That swap has to happen before
  2842. // the first compilation writes its assets, otherwise the first build lands
  2843. // on the real disk — so it runs on `watchRun`, at the start of a watch run,
  2844. // before anything is emitted. Guarded so the async `setup()` runs at most
  2845. // once across rebuilds.
  2846. /**
  2847. * @returns {Promise<void>} promise
  2848. */
  2849. const ensureSetup = () => {
  2850. if (!setupPromise) {
  2851. setupPromise = this.setup();
  2852. }
  2853. return setupPromise;
  2854. };
  2855. // `watchRun` and `done` are tapped on the compiler directly — no iteration
  2856. // over `MultiCompiler.compilers`:
  2857. // - `watchRun` is a `MultiHook` on a `MultiCompiler`, so the tap is forwarded
  2858. // to every child and awaited. It stays `tapPromise` so a failing `setup()`
  2859. // rejects the user's `watch()` callback. It only fires in watch mode, which
  2860. // is how we know a one-shot `compiler.run()` build is not in play.
  2861. // - `done` on a `MultiCompiler` is the aggregate `SyncHook` that fires once
  2862. // after every child finishes, so the server starts exactly once. Being a
  2863. // `SyncHook`, it can only be `tap`ped; `listen()` runs detached.
  2864. const {
  2865. hooks
  2866. } = /** @type {Compiler} */compiler;
  2867. hooks.watchRun.tapPromise(pluginName, () => {
  2868. inWatchMode = true;
  2869. return ensureSetup();
  2870. });
  2871. hooks.done.tap(pluginName, () => {
  2872. // `done` also fires for a one-shot `compiler.run()` build, where no
  2873. // `watchRun` ran; staying passive lets that build finish and exit.
  2874. if (listening || !inWatchMode) return;
  2875. listening = true;
  2876. ensureSetup().then(() => this.listen()).catch(error => {
  2877. this.logger.error(error);
  2878. });
  2879. });
  2880. /**
  2881. * @returns {Promise<void>} promise
  2882. */
  2883. const onShutdown = async () => {
  2884. if (stopped) return;
  2885. stopped = true;
  2886. await this.stop();
  2887. };
  2888. // Teardown is the one place a loop is unavoidable. A `MultiCompiler` has no
  2889. // `shutdown` hook, and its aggregate `watchClose` does NOT fire on
  2890. // `compiler.close()` (only on `watching.close()`), so the only signal that
  2891. // survives `compiler.close()` is each child's own `shutdown`. Tapping it
  2892. // with `tapPromise` also lets `compiler.close()` await the server actually
  2893. // stopping, so the port is released before the next start.
  2894. const childCompilers = /** @type {MultiCompiler} */compiler.compilers || [compiler];
  2895. for (const childCompiler of childCompilers) {
  2896. childCompiler.hooks.shutdown.tapPromise(pluginName, onShutdown);
  2897. }
  2898. }
  2899. }
  2900. var _default = exports.default = Server;module.exports = exports.default;
  2901. module.exports.default = exports.default;