webpack-dev-middleware
Advanced tools
Comparing version 6.0.2 to 6.1.0
@@ -22,2 +22,3 @@ "use strict"; | ||
/** @typedef {import("webpack").MultiStats} MultiStats */ | ||
/** @typedef {import("fs").ReadStream} ReadStream */ | ||
@@ -62,4 +63,21 @@ /** | ||
/** | ||
* @typedef {Object} ResponseData | ||
* @property {string | Buffer | ReadStream} data | ||
* @property {number} byteLength | ||
*/ | ||
/** | ||
* @template {IncomingMessage} RequestInternal | ||
* @template {ServerResponse} ResponseInternal | ||
* @callback ModifyResponseData | ||
* @param {RequestInternal} req | ||
* @param {ResponseInternal} res | ||
* @param {string | Buffer | ReadStream} data | ||
* @param {number} byteLength | ||
* @return {ResponseData} | ||
*/ | ||
/** | ||
* @template {IncomingMessage} RequestInternal | ||
* @template {ServerResponse} ResponseInternal | ||
* @typedef {Object} Context | ||
@@ -87,2 +105,3 @@ * @property {boolean} state | ||
* @property {{[key: string]: string}} [mimeTypes] | ||
* @property {string | undefined} [mimeTypeDefault] | ||
* @property {boolean | ((targetPath: string) => boolean)} [writeToDisk] | ||
@@ -96,2 +115,3 @@ * @property {string} [methods] | ||
* @property {boolean | string} [index] | ||
* @property {ModifyResponseData<RequestInternal, ResponseInternal>} [modifyResponseData] | ||
*/ | ||
@@ -98,0 +118,0 @@ |
@@ -5,3 +5,2 @@ "use strict"; | ||
const mime = require("mime-types"); | ||
const parseRange = require("range-parser"); | ||
const getFilenameFromUrl = require("./utils/getFilenameFromUrl"); | ||
@@ -121,2 +120,4 @@ const { | ||
setHeaderForResponse(res, "Content-Type", contentType); | ||
} else if (context.options.mimeTypeDefault) { | ||
setHeaderForResponse(res, "Content-Type", context.options.mimeTypeDefault); | ||
} | ||
@@ -141,3 +142,5 @@ } | ||
}); | ||
const parsedRanges = parseRange(size, rangeHeader, { | ||
// eslint-disable-next-line global-require | ||
const parsedRanges = require("range-parser")(size, rangeHeader, { | ||
combine: true | ||
@@ -155,5 +158,13 @@ }); | ||
setHeaderForResponse(res, "Content-Type", "text/html; charset=utf-8"); | ||
const document = createHtmlDocument(416, `Error: ${message}`); | ||
const byteLength = Buffer.byteLength(document); | ||
/** @type {string | Buffer | import("fs").ReadStream} */ | ||
let document = createHtmlDocument(416, `Error: ${message}`); | ||
let byteLength = Buffer.byteLength(document); | ||
setHeaderForResponse(res, "Content-Length", Buffer.byteLength(document)); | ||
if (context.options.modifyResponseData) { | ||
({ | ||
data: document, | ||
byteLength | ||
} = context.options.modifyResponseData(req, res, document, byteLength)); | ||
} | ||
send(req, res, document, byteLength); | ||
@@ -177,7 +188,7 @@ return; | ||
const isFsSupportsStream = typeof context.outputFileSystem.createReadStream === "function"; | ||
let bufferOtStream; | ||
let bufferOrStream; | ||
let byteLength; | ||
try { | ||
if (typeof start !== "undefined" && typeof end !== "undefined" && isFsSupportsStream) { | ||
bufferOtStream = /** @type {import("fs").createReadStream} */ | ||
bufferOrStream = /** @type {import("fs").createReadStream} */ | ||
context.outputFileSystem.createReadStream(filename, { | ||
@@ -189,6 +200,6 @@ start, | ||
} else { | ||
bufferOtStream = /** @type {import("fs").readFileSync} */context.outputFileSystem.readFileSync(filename); | ||
bufferOrStream = /** @type {import("fs").readFileSync} */context.outputFileSystem.readFileSync(filename); | ||
({ | ||
byteLength | ||
} = bufferOtStream); | ||
} = bufferOrStream); | ||
} | ||
@@ -199,3 +210,9 @@ } catch (_ignoreError) { | ||
} | ||
send(req, res, bufferOtStream, byteLength); | ||
if (context.options.modifyResponseData) { | ||
({ | ||
data: bufferOrStream, | ||
byteLength | ||
} = context.options.modifyResponseData(req, res, bufferOrStream, byteLength)); | ||
} | ||
send(req, res, bufferOrStream, byteLength); | ||
} | ||
@@ -202,0 +219,0 @@ }; |
@@ -9,2 +9,7 @@ { | ||
}, | ||
"mimeTypeDefault": { | ||
"description": "Allows a user to register a default mime type when we can't determine the content type.", | ||
"link": "https://github.com/webpack/webpack-dev-middleware#mimetypedefault", | ||
"type": "string" | ||
}, | ||
"writeToDisk": { | ||
@@ -123,2 +128,7 @@ "description": "Allows to write generated files on disk.", | ||
] | ||
}, | ||
"modifyResponseData": { | ||
"description": "Allows to set up a callback to change the response data.", | ||
"link": "https://github.com/webpack/webpack-dev-middleware#modifyresponsedata", | ||
"instanceof": "Function" | ||
} | ||
@@ -125,0 +135,0 @@ }, |
"use strict"; | ||
const { | ||
isColorSupported | ||
} = require("colorette"); | ||
/** @typedef {import("webpack").Configuration} Configuration */ | ||
@@ -119,3 +115,5 @@ /** @typedef {import("webpack").Compiler} Compiler */ | ||
// eslint-disable-next-line no-param-reassign | ||
childStatsOptions.colors = isColorSupported; | ||
childStatsOptions.colors = | ||
// eslint-disable-next-line global-require | ||
require("colorette").isColorSupported; | ||
} | ||
@@ -128,3 +126,4 @@ return childStatsOptions; | ||
if (typeof statsOptions.colors === "undefined") { | ||
statsOptions.colors = isColorSupported; | ||
// eslint-disable-next-line global-require | ||
statsOptions.colors = require("colorette").isColorSupported; | ||
} | ||
@@ -131,0 +130,0 @@ } |
@@ -21,4 +21,23 @@ "use strict"; | ||
outputFileSystem = outputFileSystemFromOptions; | ||
} | ||
// Don't use `memfs` when developer wants to write everything to a disk, because it doesn't make sense. | ||
else if (context.options.writeToDisk !== true) { | ||
outputFileSystem = memfs.createFsFromVolume(new memfs.Volume()); | ||
} else { | ||
outputFileSystem = memfs.createFsFromVolume(new memfs.Volume()); | ||
const isMultiCompiler = /** @type {MultiCompiler} */ | ||
context.compiler.compilers; | ||
if (isMultiCompiler) { | ||
// Prefer compiler with `devServer` option or fallback on the first | ||
// TODO we need to support webpack-dev-server as a plugin or revisit it | ||
const compiler = /** @type {MultiCompiler} */ | ||
context.compiler.compilers.filter(item => Object.prototype.hasOwnProperty.call(item.options, "devServer")); | ||
({ | ||
outputFileSystem | ||
} = compiler[0] || /** @type {MultiCompiler} */ | ||
context.compiler.compilers[0]); | ||
} else { | ||
({ | ||
outputFileSystem | ||
} = context.compiler); | ||
} | ||
} | ||
@@ -25,0 +44,0 @@ const compilers = /** @type {MultiCompiler} */ |
@@ -24,7 +24,3 @@ "use strict"; | ||
for (const compiler of compilers) { | ||
compiler.hooks.emit.tap("DevMiddleware", | ||
/** | ||
* @param {Compilation} compilation | ||
*/ | ||
compilation => { | ||
compiler.hooks.emit.tap("DevMiddleware", () => { | ||
// @ts-ignore | ||
@@ -35,32 +31,7 @@ if (compiler.hasWebpackDevMiddlewareAssetEmittedCallback) { | ||
compiler.hooks.assetEmitted.tapAsync("DevMiddleware", (file, info, callback) => { | ||
/** | ||
* @type {string} | ||
*/ | ||
let targetPath; | ||
/** | ||
* @type {Buffer} | ||
*/ | ||
let content; | ||
// webpack@5 | ||
if (info.compilation) { | ||
({ | ||
targetPath, | ||
content | ||
} = info); | ||
} else { | ||
let targetFile = file; | ||
const queryStringIdx = targetFile.indexOf("?"); | ||
if (queryStringIdx >= 0) { | ||
targetFile = targetFile.slice(0, queryStringIdx); | ||
} | ||
let { | ||
outputPath | ||
} = compiler; | ||
outputPath = compilation.getPath(outputPath, {}); | ||
// @ts-ignore | ||
content = info; | ||
targetPath = path.join(outputPath, targetFile); | ||
} | ||
const { | ||
targetPath, | ||
content | ||
} = info; | ||
const { | ||
writeToDisk: filter | ||
@@ -67,0 +38,0 @@ } = context.options; |
{ | ||
"name": "webpack-dev-middleware", | ||
"version": "6.0.2", | ||
"version": "6.1.0", | ||
"description": "A development middleware for webpack", | ||
@@ -5,0 +5,0 @@ "license": "MIT", |
104
README.md
@@ -63,13 +63,15 @@ <div align="center"> | ||
| Name | Type | Default | Description | | ||
| :-----------------------------------------: | :-----------------------: | :-------------------------------------------: | :------------------------------------------------------------------------------------------------------------------- | | ||
| **[`methods`](#methods)** | `Array` | `[ 'GET', 'HEAD' ]` | Allows to pass the list of HTTP request methods accepted by the middleware | | ||
| **[`headers`](#headers)** | `Array\|Object\|Function` | `undefined` | Allows to pass custom HTTP headers on each request. | | ||
| **[`index`](#index)** | `Boolean\|String` | `index.html` | If `false` (but not `undefined`), the server will not respond to requests to the root URL. | | ||
| **[`mimeTypes`](#mimetypes)** | `Object` | `undefined` | Allows to register custom mime types or extension mappings. | | ||
| **[`publicPath`](#publicpath)** | `String` | `output.publicPath` (from a configuration) | The public path that the middleware is bound to. | | ||
| **[`stats`](#stats)** | `Boolean\|String\|Object` | `stats` (from a configuration) | Stats options object or preset name. | | ||
| **[`serverSideRender`](#serversiderender)** | `Boolean` | `undefined` | Instructs the module to enable or disable the server-side rendering mode. | | ||
| **[`writeToDisk`](#writetodisk)** | `Boolean\|Function` | `false` | Instructs the module to write files to the configured location on disk as specified in your `webpack` configuration. | | ||
| **[`outputFileSystem`](#outputfilesystem)** | `Object` | [`memfs`](https://github.com/streamich/memfs) | Set the default file system which will be used by webpack as primary destination of generated files. | | ||
| Name | Type | Default | Description | | ||
| :---------------------------------------------: | :-----------------------: | :-------------------------------------------: | :------------------------------------------------------------------------------------------------------------------- | | ||
| **[`methods`](#methods)** | `Array` | `[ 'GET', 'HEAD' ]` | Allows to pass the list of HTTP request methods accepted by the middleware | | ||
| **[`headers`](#headers)** | `Array\|Object\|Function` | `undefined` | Allows to pass custom HTTP headers on each request. | | ||
| **[`index`](#index)** | `Boolean\|String` | `index.html` | If `false` (but not `undefined`), the server will not respond to requests to the root URL. | | ||
| **[`mimeTypes`](#mimetypes)** | `Object` | `undefined` | Allows to register custom mime types or extension mappings. | | ||
| **[`mimeTypeDefault`](#mimetypedefault)** | `String` | `undefined` | Allows to register a default mime type when we can't determine the content type. | | ||
| **[`publicPath`](#publicpath)** | `String` | `output.publicPath` (from a configuration) | The public path that the middleware is bound to. | | ||
| **[`stats`](#stats)** | `Boolean\|String\|Object` | `stats` (from a configuration) | Stats options object or preset name. | | ||
| **[`serverSideRender`](#serversiderender)** | `Boolean` | `undefined` | Instructs the module to enable or disable the server-side rendering mode. | | ||
| **[`writeToDisk`](#writetodisk)** | `Boolean\|Function` | `false` | Instructs the module to write files to the configured location on disk as specified in your `webpack` configuration. | | ||
| **[`outputFileSystem`](#outputfilesystem)** | `Object` | [`memfs`](https://github.com/streamich/memfs) | Set the default file system which will be used by webpack as primary destination of generated files. | | ||
| **[`modifyResponseData`](#modifyresponsedata)** | `Function` | `undefined` | Allows to set up a callback to change the response data. | | ||
@@ -122,10 +124,9 @@ The middleware accepts an `options` Object. The following is a property reference for the Object. | ||
key: "X-custom-header", | ||
value: "foo" | ||
value: "foo", | ||
}, | ||
{ | ||
key: "Y-custom-header", | ||
value: "bar" | ||
} | ||
] | ||
}, | ||
value: "bar", | ||
}, | ||
], | ||
}); | ||
@@ -141,10 +142,9 @@ ``` | ||
key: "X-custom-header", | ||
value: "foo" | ||
value: "foo", | ||
}, | ||
{ | ||
key: "Y-custom-header", | ||
value: "bar" | ||
} | ||
] | ||
}, | ||
value: "bar", | ||
}, | ||
], | ||
}); | ||
@@ -170,2 +170,9 @@ ``` | ||
### mimeTypeDefault | ||
Type: `String` | ||
Default: `undefined` | ||
This property allows a user to register a default mime type when we can't determine the content type. | ||
### publicPath | ||
@@ -249,2 +256,24 @@ | ||
### modifyResponseData | ||
Allows to set up a callback to change the response data. | ||
```js | ||
const webpack = require("webpack"); | ||
const configuration = { | ||
/* Webpack configuration */ | ||
}; | ||
const compiler = webpack(configuration); | ||
middleware(compiler, { | ||
// Note - if you send the `Range` header you will have `ReadStream` | ||
// Also `data` can be `string` or `Buffer` | ||
modifyResponseData: (req, res, data, byteLength) => { | ||
// Your logic | ||
// Don't use `res.end()` or `res.send()` here | ||
return { data, byteLength }; | ||
}, | ||
}); | ||
``` | ||
## API | ||
@@ -388,12 +417,31 @@ | ||
## Known Issues | ||
## FAQ | ||
### Multiple Successive Builds | ||
### Avoid blocking requests to non-webpack resources. | ||
Watching will frequently cause multiple compilations | ||
as the bundle changes during compilation. This is due in part to cross-platform | ||
differences in file watchers, so that webpack doesn't loose file changes when | ||
watched files change rapidly. If you run into this situation, please make use of | ||
the [`TimeFixPlugin`](https://github.com/egoist/time-fix-plugin). | ||
Since `output.publicPath` and `output.filename`/`output.chunkFilename` can be dynamic, it's not possible to know which files are webpack bundles (and they public paths) and which are not, so we can't avoid blocking requests. | ||
But there is a solution to avoid it - mount the middleware to a non-root route, for example: | ||
```js | ||
const webpack = require("webpack"); | ||
const middleware = require("webpack-dev-middleware"); | ||
const compiler = webpack({ | ||
// webpack options | ||
}); | ||
const express = require("express"); | ||
const app = express(); | ||
// Mounting the middleware to the non-root route allows avoids this. | ||
// Note - check your public path, if you want to handle `/dist/`, you need to setup `output.publicPath` to `/` value. | ||
app.use( | ||
"/dist/", | ||
middleware(compiler, { | ||
// webpack-dev-middleware options | ||
}) | ||
); | ||
app.listen(3000, () => console.log("Example app listening on port 3000!")); | ||
``` | ||
## Server-Side Rendering | ||
@@ -400,0 +448,0 @@ |
@@ -10,2 +10,3 @@ /// <reference types="node" /> | ||
/** @typedef {import("webpack").MultiStats} MultiStats */ | ||
/** @typedef {import("fs").ReadStream} ReadStream */ | ||
/** | ||
@@ -40,4 +41,19 @@ * @typedef {Object} ExtendedServerResponse | ||
/** | ||
* @typedef {Object} ResponseData | ||
* @property {string | Buffer | ReadStream} data | ||
* @property {number} byteLength | ||
*/ | ||
/** | ||
* @template {IncomingMessage} RequestInternal | ||
* @template {ServerResponse} ResponseInternal | ||
* @callback ModifyResponseData | ||
* @param {RequestInternal} req | ||
* @param {ResponseInternal} res | ||
* @param {string | Buffer | ReadStream} data | ||
* @param {number} byteLength | ||
* @return {ResponseData} | ||
*/ | ||
/** | ||
* @template {IncomingMessage} RequestInternal | ||
* @template {ServerResponse} ResponseInternal | ||
* @typedef {Object} Context | ||
@@ -63,2 +79,3 @@ * @property {boolean} state | ||
* @property {{[key: string]: string}} [mimeTypes] | ||
* @property {string | undefined} [mimeTypeDefault] | ||
* @property {boolean | ((targetPath: string) => boolean)} [writeToDisk] | ||
@@ -72,2 +89,3 @@ * @property {string} [methods] | ||
* @property {boolean | string} [index] | ||
* @property {ModifyResponseData<RequestInternal, ResponseInternal>} [modifyResponseData] | ||
*/ | ||
@@ -137,2 +155,3 @@ /** | ||
MultiStats, | ||
ReadStream, | ||
ExtendedServerResponse, | ||
@@ -148,2 +167,4 @@ IncomingMessage, | ||
Callback, | ||
ResponseData, | ||
ModifyResponseData, | ||
Context, | ||
@@ -173,2 +194,3 @@ Headers, | ||
| undefined; | ||
mimeTypeDefault?: string | undefined; | ||
writeToDisk?: boolean | ((targetPath: string) => boolean) | undefined; | ||
@@ -182,2 +204,5 @@ methods?: string | undefined; | ||
index?: string | boolean | undefined; | ||
modifyResponseData?: | ||
| ModifyResponseData<RequestInternal, ResponseInternal> | ||
| undefined; | ||
}; | ||
@@ -193,2 +218,3 @@ type API< | ||
type MultiStats = import("webpack").MultiStats; | ||
type ReadStream = import("fs").ReadStream; | ||
type ExtendedServerResponse = { | ||
@@ -222,2 +248,15 @@ locals?: | ||
) => any; | ||
type ResponseData = { | ||
data: string | Buffer | ReadStream; | ||
byteLength: number; | ||
}; | ||
type ModifyResponseData< | ||
RequestInternal extends import("http").IncomingMessage, | ||
ResponseInternal extends ServerResponse | ||
> = ( | ||
req: RequestInternal, | ||
res: ResponseInternal, | ||
data: string | Buffer | ReadStream, | ||
byteLength: number | ||
) => ResponseData; | ||
type Context< | ||
@@ -224,0 +263,0 @@ RequestInternal extends import("http").IncomingMessage, |
82110
1713
590