Server.js 98 KB

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