Server.js 99 KB

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