Server.js 100 KB

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