@@ -18,9 +18,6 @@ const schema = require("./options.json");
1818/** @typedef {import("webpack").Stats } Stats */
1919/** @typedef {import("webpack").MultiStats } MultiStats */
2020/** @typedef {import("os").NetworkInterfaceInfo } NetworkInterfaceInfo */
21- /** @typedef {import("express").NextFunction } NextFunction */
22- /** @typedef {import("express").RequestHandler } ExpressRequestHandler */
23- /** @typedef {import("express").ErrorRequestHandler } ExpressErrorRequestHandler */
2421/** @typedef {import("chokidar").WatchOptions } WatchOptions */
2522/** @typedef {import("chokidar").FSWatcher } FSWatcher */
2623/** @typedef {import("connect-history-api-fallback").Options } ConnectHistoryApiFallbackOptions */
@@ -37,11 +34,28 @@ const schema = require("./options.json");
3734/** @typedef {import("http").IncomingMessage } IncomingMessage */
3835/** @typedef {import("http").ServerResponse } ServerResponse */
3936/** @typedef {import("open").Options } OpenOptions */
37+ /** @typedef {import("express").Application } ExpressApplication */
38+ /** @typedef {import("express").RequestHandler } ExpressRequestHandler */
39+ /** @typedef {import("express").ErrorRequestHandler } ExpressErrorRequestHandler */
40+ /** @typedef {import("express").Request } ExpressRequest */
41+ /** @typedef {import("express").Response } ExpressResponse */
42+
43+ /** @typedef {(err?: any) => void } NextFunction */
44+ /** @typedef {(req: IncomingMessage, res: ServerResponse) => void } SimpleHandleFunction */
45+ /** @typedef {(req: IncomingMessage, res: ServerResponse, next: NextFunction) => void } NextHandleFunction */
46+ /** @typedef {(err: any, req: IncomingMessage, res: ServerResponse, next: NextFunction) => void } ErrorHandleFunction */
47+ /** @typedef {SimpleHandleFunction | NextHandleFunction | ErrorHandleFunction } HandleFunction */
4048
4149/** @typedef {import("https").ServerOptions & { spdy?: { plain?: boolean | undefined, ssl?: boolean | undefined, 'x-forwarded-for'?: string | undefined, protocol?: string | undefined, protocols?: string[] | undefined }} } ServerOptions */
4250
43- /** @typedef {import("express").Request } Request */
44- /** @typedef {import("express").Response } Response */
51+ /**
52+ * @template {BasicApplication} [T=ExpressApplication]
53+ * @typedef {T extends ExpressApplication ? ExpressRequest : IncomingMessage } Request
54+ */
55+ /**
56+ * @template {BasicApplication} [T=ExpressApplication]
57+ * @typedef {T extends ExpressApplication ? ExpressResponse : ServerResponse } Response
58+ */
4559
4660/**
4761 * @template {Request} T
@@ -173,10 +187,16 @@ const schema = require("./options.json");
173187 */
174188
175189/**
176- * @typedef {{ name?: string, path?: string, middleware: ExpressRequestHandler | ExpressErrorRequestHandler } | ExpressRequestHandler | ExpressErrorRequestHandler } Middleware
190+ * @template {BasicApplication} [T=ExpressApplication]
191+ * @typedef {T extends ExpressApplication ? ExpressRequestHandler | ExpressErrorRequestHandler : HandleFunction } MiddlewareHandler
192+ */
193+
194+ /**
195+ * @typedef {{ name?: string, path?: string, middleware: MiddlewareHandler } | MiddlewareHandler } Middleware
177196 */
178197
179198/**
199+ * @template {BasicApplication} [T=ExpressApplication]
180200 * @typedef {Object } Configuration
181201 * @property {boolean | string } [ipc]
182202 * @property {Host } [host]
@@ -191,16 +211,16 @@ const schema = require("./options.json");
191211 * @property {string | string[] | WatchFiles | Array<string | WatchFiles> } [watchFiles]
192212 * @property {boolean | string | Static | Array<string | Static> } [static]
193213 * @property {boolean | ServerOptions } [https]
194- * @property {boolean } [http2]
195214 * @property {"http" | "https" | "spdy" | string | ServerConfiguration } [server]
215+ * @property {() => Promise<T> } [app]
196216 * @property {boolean | "sockjs" | "ws" | string | WebSocketServerConfiguration } [webSocketServer]
197217 * @property {ProxyConfigArray } [proxy]
198218 * @property {boolean | string | Open | Array<string | Open> } [open]
199219 * @property {boolean } [setupExitSignals]
200220 * @property {boolean | ClientConfiguration } [client]
201221 * @property {Headers | ((req: Request, res: Response, context: DevMiddlewareContext<Request, Response>) => Headers) } [headers]
202- * @property {(devServer: Server) => void } [onListening]
203- * @property {(middlewares: Middleware[], devServer: Server) => Middleware[] } [setupMiddlewares]
222+ * @property {(devServer: Server<T> ) => void } [onListening]
223+ * @property {(middlewares: Middleware[], devServer: Server<T> ) => Middleware[] } [setupMiddlewares]
204224 */
205225
206226if ( ! process . env . WEBPACK_SERVE ) {
@@ -245,23 +265,58 @@ const encodeOverlaySettings = (setting) =>
245265 ? encodeURIComponent ( setting . toString ( ) )
246266 : setting ;
247267
268+ // Working for overload, because typescript doesn't support this yes
269+ /**
270+ * @overload
271+ * @param {NextHandleFunction } fn
272+ * @returns {BasicApplication }
273+ */
274+ /**
275+ * @overload
276+ * @param {HandleFunction } fn
277+ * @returns {BasicApplication }
278+ */
279+ /**
280+ * @overload
281+ * @param {string } route
282+ * @param {NextHandleFunction } fn
283+ * @returns {BasicApplication }
284+ */
285+ /**
286+ * @param {string } route
287+ * @param {HandleFunction } fn
288+ * @returns {BasicApplication }
289+ */
290+ // eslint-disable-next-line no-unused-vars
291+ function useFn ( route , fn ) {
292+ return /** @type {BasicApplication } */ ( { } ) ;
293+ }
294+
295+ /**
296+ * @typedef {Object } BasicApplication
297+ * @property {typeof useFn } use
298+ */
299+
300+ /**
301+ * @template {BasicApplication} [T=ExpressApplication]
302+ */
248303class Server {
249304 /**
250- * @param {Configuration | Compiler | MultiCompiler } options
251- * @param {Compiler | MultiCompiler | Configuration } compiler
305+ * @param {Configuration<T> } options
306+ * @param {Compiler | MultiCompiler } compiler
252307 */
253308 constructor ( options = { } , compiler ) {
254309 validate ( /** @type {Schema } */ ( schema ) , options , {
255310 name : "Dev Server" ,
256311 baseDataPath : "options" ,
257312 } ) ;
258313
259- this . compiler = /** @type { Compiler | MultiCompiler } */ ( compiler ) ;
314+ this . compiler = compiler ;
260315 /**
261316 * @type {ReturnType<Compiler["getInfrastructureLogger"]> }
262317 * */
263318 this . logger = this . compiler . getInfrastructureLogger ( "webpack-dev-server" ) ;
264- this . options = /** @type { Configuration } */ ( options ) ;
319+ this . options = options ;
265320 /**
266321 * @type {FSWatcher[] }
267322 */
@@ -1670,7 +1725,7 @@ class Server {
16701725 }
16711726
16721727 this . setupHooks ( ) ;
1673- this . setupApp ( ) ;
1728+ await this . setupApp ( ) ;
16741729 this . setupHostHeaderCheck ( ) ;
16751730 this . setupDevMiddleware ( ) ;
16761731 // Should be after `webpack-dev-middleware`, otherwise other middlewares might rewrite response
@@ -1729,11 +1784,14 @@ class Server {
17291784
17301785 /**
17311786 * @private
1732- * @returns {void }
1787+ * @returns {Promise< void> }
17331788 */
1734- setupApp ( ) {
1735- /** @type {import("express").Application | undefined }*/
1736- this . app = new /** @type {any } */ ( getExpress ( ) ) ( ) ;
1789+ async setupApp ( ) {
1790+ /** @type {T | undefined }*/
1791+ this . app =
1792+ typeof this . options . app === "function"
1793+ ? await this . options . app ( )
1794+ : getExpress ( ) ( ) ;
17371795 }
17381796
17391797 /**
@@ -1788,29 +1846,22 @@ class Server {
17881846 * @returns {void }
17891847 */
17901848 setupHostHeaderCheck ( ) {
1791- /** @type {import("express").Application } */
1792- ( this . app ) . all (
1793- "*" ,
1794- /**
1795- * @param {Request } req
1796- * @param {Response } res
1797- * @param {NextFunction } next
1798- * @returns {void }
1799- */
1800- ( req , res , next ) => {
1801- if (
1802- this . checkHeader (
1803- /** @type {{ [key: string]: string | undefined } } */
1804- ( req . headers ) ,
1805- "host" ,
1806- )
1807- ) {
1808- return next ( ) ;
1809- }
1849+ /** @type {T } */
1850+ ( this . app ) . use ( ( req , res , next ) => {
1851+ if (
1852+ this . checkHeader (
1853+ /** @type {{ [key: string]: string | undefined } } */
1854+ ( req . headers ) ,
1855+ "host" ,
1856+ )
1857+ ) {
1858+ next ( ) ;
1859+ return ;
1860+ }
18101861
1811- res . send ( "Invalid Host header" ) ;
1812- } ,
1813- ) ;
1862+ res . statusCode = 403 ;
1863+ res . end ( "Invalid Host header" ) ;
1864+ } ) ;
18141865 }
18151866
18161867 /**
@@ -1834,45 +1885,103 @@ class Server {
18341885 setupBuiltInRoutes ( ) {
18351886 const { app, middleware } = this ;
18361887
1837- /** @type {import("express").Application } */
1838- ( app ) . get ( "/__webpack_dev_server__/sockjs.bundle.js" , ( req , res ) => {
1839- res . setHeader ( "Content-Type" , "application/javascript" ) ;
1888+ /** @type {T } */
1889+ ( app ) . use ( "/__webpack_dev_server__/sockjs.bundle.js" , ( req , res , next ) => {
1890+ if ( req . method !== "GET" && req . method !== "HEAD" ) {
1891+ next ( ) ;
1892+ return ;
1893+ }
1894+
1895+ const clientPath = path . join (
1896+ __dirname ,
1897+ ".." ,
1898+ "client/modules/sockjs-client/index.js" ,
1899+ ) ;
1900+
1901+ // Express send Etag and other headers by default, so let's keep them for compatibility reasons
1902+ // @ts -ignore
1903+ if ( typeof res . sendFile === "function" ) {
1904+ // @ts -ignore
1905+ res . sendFile ( clientPath ) ;
1906+ return ;
1907+ }
1908+
1909+ let stats ;
18401910
1841- const clientPath = path . join ( __dirname , ".." , "client" ) ;
1911+ try {
1912+ // TODO implement `inputFileSystem.createReadStream` in webpack
1913+ stats = fs . statSync ( clientPath ) ;
1914+ } catch ( err ) {
1915+ next ( ) ;
1916+ return ;
1917+ }
18421918
1843- res . sendFile ( path . join ( clientPath , "modules/sockjs-client/index.js" ) ) ;
1919+ res . setHeader ( "Content-Type" , "application/javascript; charset=UTF-8" ) ;
1920+ res . setHeader ( "Content-Length" , stats . size ) ;
1921+
1922+ if ( req . method === "HEAD" ) {
1923+ res . end ( ) ;
1924+ return ;
1925+ }
1926+
1927+ fs . createReadStream ( clientPath ) . pipe ( res ) ;
18441928 } ) ;
18451929
1846- /** @type {import("express").Application } */
1847- ( app ) . get ( "/webpack-dev-server/invalidate" , ( _req , res ) => {
1930+ /** @type {T } */
1931+ ( app ) . use ( "/webpack-dev-server/invalidate" , ( req , res , next ) => {
1932+ if ( req . method !== "GET" && req . method !== "HEAD" ) {
1933+ next ( ) ;
1934+ return ;
1935+ }
1936+
18481937 this . invalidate ( ) ;
18491938
18501939 res . end ( ) ;
18511940 } ) ;
18521941
1853- /** @type {import("express").Application } */
1854- ( app ) . get ( "/webpack-dev-server/open-editor" , ( req , res ) => {
1855- const fileName = req . query . fileName ;
1942+ /** @type {T } */
1943+ ( app ) . use ( "/webpack-dev-server/open-editor" , ( req , res , next ) => {
1944+ if ( req . method !== "GET" && req . method !== "HEAD" ) {
1945+ next ( ) ;
1946+ return ;
1947+ }
1948+
1949+ if ( ! req . url ) {
1950+ next ( ) ;
1951+ return ;
1952+ }
1953+
1954+ const resolveUrl = new URL ( req . url , `http://${ req . headers . host } ` ) ;
1955+ const params = new URLSearchParams ( resolveUrl . search ) ;
1956+ const fileName = params . get ( "fileName" ) ;
18561957
18571958 if ( typeof fileName === "string" ) {
18581959 // @ts -ignore
18591960 const launchEditor = require ( "launch-editor" ) ;
1961+
18601962 launchEditor ( fileName ) ;
18611963 }
18621964
18631965 res . end ( ) ;
18641966 } ) ;
18651967
1866- /** @type {import("express").Application } */
1867- ( app ) . get ( "/webpack-dev-server" , ( req , res ) => {
1968+ /** @type {T } */
1969+ ( app ) . use ( "/webpack-dev-server" , ( req , res , next ) => {
1970+ if ( req . method !== "GET" && req . method !== "HEAD" ) {
1971+ next ( ) ;
1972+ return ;
1973+ }
1974+
18681975 /** @type {import("webpack-dev-middleware").API<Request, Response> }*/
18691976 ( middleware ) . waitUntilValid ( ( stats ) => {
1870- res . setHeader ( "Content-Type" , "text/html" ) ;
1977+ res . setHeader ( "Content-Type" , "text/html; charset=utf-8" ) ;
1978+
18711979 // HEAD requests should not return body content
18721980 if ( req . method === "HEAD" ) {
18731981 res . end ( ) ;
18741982 return ;
18751983 }
1984+
18761985 res . write (
18771986 '<!DOCTYPE html><html><head><meta charset="utf-8"/></head><body>' ,
18781987 ) ;
@@ -1975,7 +2084,6 @@ class Server {
19752084 if ( typeof this . options . headers !== "undefined" ) {
19762085 middlewares . push ( {
19772086 name : "set-headers" ,
1978- path : "*" ,
19792087 middleware : this . setHeaders . bind ( this ) ,
19802088 } ) ;
19812089 }
@@ -2104,8 +2212,8 @@ class Server {
21042212
21052213 if ( typeof bypassUrl === "boolean" ) {
21062214 // skip the proxy
2107- // @ts -ignore
2108- req . url = null ;
2215+ res . statusCode = 404 ;
2216+ req . url = "" ;
21092217 next ( ) ;
21102218 } else if ( typeof bypassUrl === "string" ) {
21112219 // byPass to that url
@@ -2255,7 +2363,6 @@ class Server {
22552363 // fallback when no other middleware responses.
22562364 middlewares . push ( {
22572365 name : "options-middleware" ,
2258- path : "*" ,
22592366 /**
22602367 * @param {Request } req
22612368 * @param {Response } res
@@ -2279,14 +2386,24 @@ class Server {
22792386
22802387 middlewares . forEach ( ( middleware ) => {
22812388 if ( typeof middleware === "function" ) {
2282- /** @type {import("express").Application } */
2283- ( this . app ) . use ( middleware ) ;
2389+ /** @type {T } */
2390+ ( this . app ) . use (
2391+ /** @type {NextHandleFunction | HandleFunction } */
2392+ ( middleware ) ,
2393+ ) ;
22842394 } else if ( typeof middleware . path !== "undefined" ) {
2285- /** @type {import("express").Application } */
2286- ( this . app ) . use ( middleware . path , middleware . middleware ) ;
2395+ /** @type {T } */
2396+ ( this . app ) . use (
2397+ middleware . path ,
2398+ /** @type {SimpleHandleFunction | NextHandleFunction } */
2399+ ( middleware . middleware ) ,
2400+ ) ;
22872401 } else {
2288- /** @type {import("express").Application } */
2289- ( this . app ) . use ( middleware . middleware ) ;
2402+ /** @type {T } */
2403+ ( this . app ) . use (
2404+ /** @type {NextHandleFunction | HandleFunction } */
2405+ ( middleware . middleware ) ,
2406+ ) ;
22902407 }
22912408 } ) ;
22922409 }
@@ -2794,7 +2911,7 @@ class Server {
27942911 headers = headers (
27952912 req ,
27962913 res ,
2797- /** @type {import("webpack-dev-middleware").API<IncomingMessage, ServerResponse > }*/
2914+ /** @type {import("webpack-dev-middleware").API<Request, Response > }*/
27982915 ( this . middleware ) . context ,
27992916 ) ;
28002917 }
0 commit comments