@@ -18,9 +18,6 @@ const schema = require("./options.json");
18
18
/** @typedef {import("webpack").Stats } Stats */
19
19
/** @typedef {import("webpack").MultiStats } MultiStats */
20
20
/** @typedef {import("os").NetworkInterfaceInfo } NetworkInterfaceInfo */
21
- /** @typedef {import("express").NextFunction } NextFunction */
22
- /** @typedef {import("express").RequestHandler } ExpressRequestHandler */
23
- /** @typedef {import("express").ErrorRequestHandler } ExpressErrorRequestHandler */
24
21
/** @typedef {import("chokidar").WatchOptions } WatchOptions */
25
22
/** @typedef {import("chokidar").FSWatcher } FSWatcher */
26
23
/** @typedef {import("connect-history-api-fallback").Options } ConnectHistoryApiFallbackOptions */
@@ -37,11 +34,28 @@ const schema = require("./options.json");
37
34
/** @typedef {import("http").IncomingMessage } IncomingMessage */
38
35
/** @typedef {import("http").ServerResponse } ServerResponse */
39
36
/** @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 */
40
48
41
49
/** @typedef {import("https").ServerOptions & { spdy?: { plain?: boolean | undefined, ssl?: boolean | undefined, 'x-forwarded-for'?: string | undefined, protocol?: string | undefined, protocols?: string[] | undefined }} } ServerOptions */
42
50
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
+ */
45
59
46
60
/**
47
61
* @template {Request} T
@@ -173,10 +187,16 @@ const schema = require("./options.json");
173
187
*/
174
188
175
189
/**
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
177
196
*/
178
197
179
198
/**
199
+ * @template {BasicApplication} [T=ExpressApplication]
180
200
* @typedef {Object } Configuration
181
201
* @property {boolean | string } [ipc]
182
202
* @property {Host } [host]
@@ -191,16 +211,16 @@ const schema = require("./options.json");
191
211
* @property {string | string[] | WatchFiles | Array<string | WatchFiles> } [watchFiles]
192
212
* @property {boolean | string | Static | Array<string | Static> } [static]
193
213
* @property {boolean | ServerOptions } [https]
194
- * @property {boolean } [http2]
195
214
* @property {"http" | "https" | "spdy" | string | ServerConfiguration } [server]
215
+ * @property {() => Promise<T> } [app]
196
216
* @property {boolean | "sockjs" | "ws" | string | WebSocketServerConfiguration } [webSocketServer]
197
217
* @property {ProxyConfigArray } [proxy]
198
218
* @property {boolean | string | Open | Array<string | Open> } [open]
199
219
* @property {boolean } [setupExitSignals]
200
220
* @property {boolean | ClientConfiguration } [client]
201
221
* @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]
204
224
*/
205
225
206
226
if ( ! process . env . WEBPACK_SERVE ) {
@@ -245,23 +265,58 @@ const encodeOverlaySettings = (setting) =>
245
265
? encodeURIComponent ( setting . toString ( ) )
246
266
: setting ;
247
267
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
+ */
248
303
class Server {
249
304
/**
250
- * @param {Configuration | Compiler | MultiCompiler } options
251
- * @param {Compiler | MultiCompiler | Configuration } compiler
305
+ * @param {Configuration<T> } options
306
+ * @param {Compiler | MultiCompiler } compiler
252
307
*/
253
308
constructor ( options = { } , compiler ) {
254
309
validate ( /** @type {Schema } */ ( schema ) , options , {
255
310
name : "Dev Server" ,
256
311
baseDataPath : "options" ,
257
312
} ) ;
258
313
259
- this . compiler = /** @type { Compiler | MultiCompiler } */ ( compiler ) ;
314
+ this . compiler = compiler ;
260
315
/**
261
316
* @type {ReturnType<Compiler["getInfrastructureLogger"]> }
262
317
* */
263
318
this . logger = this . compiler . getInfrastructureLogger ( "webpack-dev-server" ) ;
264
- this . options = /** @type { Configuration } */ ( options ) ;
319
+ this . options = options ;
265
320
/**
266
321
* @type {FSWatcher[] }
267
322
*/
@@ -1670,7 +1725,7 @@ class Server {
1670
1725
}
1671
1726
1672
1727
this . setupHooks ( ) ;
1673
- this . setupApp ( ) ;
1728
+ await this . setupApp ( ) ;
1674
1729
this . setupHostHeaderCheck ( ) ;
1675
1730
this . setupDevMiddleware ( ) ;
1676
1731
// Should be after `webpack-dev-middleware`, otherwise other middlewares might rewrite response
@@ -1729,11 +1784,14 @@ class Server {
1729
1784
1730
1785
/**
1731
1786
* @private
1732
- * @returns {void }
1787
+ * @returns {Promise< void> }
1733
1788
*/
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 ( ) ( ) ;
1737
1795
}
1738
1796
1739
1797
/**
@@ -1788,29 +1846,22 @@ class Server {
1788
1846
* @returns {void }
1789
1847
*/
1790
1848
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
+ }
1810
1861
1811
- res . send ( "Invalid Host header" ) ;
1812
- } ,
1813
- ) ;
1862
+ res . statusCode = 403 ;
1863
+ res . end ( "Invalid Host header" ) ;
1864
+ } ) ;
1814
1865
}
1815
1866
1816
1867
/**
@@ -1834,45 +1885,103 @@ class Server {
1834
1885
setupBuiltInRoutes ( ) {
1835
1886
const { app, middleware } = this ;
1836
1887
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 ;
1840
1910
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
+ }
1842
1918
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 ) ;
1844
1928
} ) ;
1845
1929
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
+
1848
1937
this . invalidate ( ) ;
1849
1938
1850
1939
res . end ( ) ;
1851
1940
} ) ;
1852
1941
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" ) ;
1856
1957
1857
1958
if ( typeof fileName === "string" ) {
1858
1959
// @ts -ignore
1859
1960
const launchEditor = require ( "launch-editor" ) ;
1961
+
1860
1962
launchEditor ( fileName ) ;
1861
1963
}
1862
1964
1863
1965
res . end ( ) ;
1864
1966
} ) ;
1865
1967
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
+
1868
1975
/** @type {import("webpack-dev-middleware").API<Request, Response> }*/
1869
1976
( middleware ) . waitUntilValid ( ( stats ) => {
1870
- res . setHeader ( "Content-Type" , "text/html" ) ;
1977
+ res . setHeader ( "Content-Type" , "text/html; charset=utf-8" ) ;
1978
+
1871
1979
// HEAD requests should not return body content
1872
1980
if ( req . method === "HEAD" ) {
1873
1981
res . end ( ) ;
1874
1982
return ;
1875
1983
}
1984
+
1876
1985
res . write (
1877
1986
'<!DOCTYPE html><html><head><meta charset="utf-8"/></head><body>' ,
1878
1987
) ;
@@ -1975,7 +2084,6 @@ class Server {
1975
2084
if ( typeof this . options . headers !== "undefined" ) {
1976
2085
middlewares . push ( {
1977
2086
name : "set-headers" ,
1978
- path : "*" ,
1979
2087
middleware : this . setHeaders . bind ( this ) ,
1980
2088
} ) ;
1981
2089
}
@@ -2104,8 +2212,8 @@ class Server {
2104
2212
2105
2213
if ( typeof bypassUrl === "boolean" ) {
2106
2214
// skip the proxy
2107
- // @ts -ignore
2108
- req . url = null ;
2215
+ res . statusCode = 404 ;
2216
+ req . url = "" ;
2109
2217
next ( ) ;
2110
2218
} else if ( typeof bypassUrl === "string" ) {
2111
2219
// byPass to that url
@@ -2255,7 +2363,6 @@ class Server {
2255
2363
// fallback when no other middleware responses.
2256
2364
middlewares . push ( {
2257
2365
name : "options-middleware" ,
2258
- path : "*" ,
2259
2366
/**
2260
2367
* @param {Request } req
2261
2368
* @param {Response } res
@@ -2279,14 +2386,24 @@ class Server {
2279
2386
2280
2387
middlewares . forEach ( ( middleware ) => {
2281
2388
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
+ ) ;
2284
2394
} 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
+ ) ;
2287
2401
} 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
+ ) ;
2290
2407
}
2291
2408
} ) ;
2292
2409
}
@@ -2794,7 +2911,7 @@ class Server {
2794
2911
headers = headers (
2795
2912
req ,
2796
2913
res ,
2797
- /** @type {import("webpack-dev-middleware").API<IncomingMessage, ServerResponse > }*/
2914
+ /** @type {import("webpack-dev-middleware").API<Request, Response > }*/
2798
2915
( this . middleware ) . context ,
2799
2916
) ;
2800
2917
}
0 commit comments