Server.js 101 KB

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