Server.js 102 KB

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