Skip to content

Commit

Permalink
feat: added modifyResponseData option (#1529)
Browse files Browse the repository at this point in the history
  • Loading branch information
alexander-akait committed Apr 27, 2023
1 parent f48e442 commit 35dac70
Show file tree
Hide file tree
Showing 10 changed files with 234 additions and 77 deletions.
65 changes: 43 additions & 22 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,18 +60,19 @@ See [below](#other-servers) for an example of use with fastify.

## Options

| 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. |
| 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. |

The middleware accepts an `options` Object. The following is a property reference for the Object.

Expand Down Expand Up @@ -119,14 +120,13 @@ webpackDevMiddleware(compiler, {
headers: [
{
key: "X-custom-header",
value: "foo"
value: "foo",
},
{
key: "Y-custom-header",
value: "bar"
}
]
},
value: "bar",
},
],
});
```

Expand All @@ -137,14 +137,13 @@ webpackDevMiddleware(compiler, {
headers: () => [
{
key: "X-custom-header",
value: "foo"
value: "foo",
},
{
key: "Y-custom-header",
value: "bar"
}
]
},
value: "bar",
},
],
});
```

Expand Down Expand Up @@ -250,6 +249,28 @@ const compiler = webpack({
middleware(compiler, { outputFileSystem: myOutputFileSystem });
```

### 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

`webpack-dev-middleware` also provides convenience methods that can be use to
Expand Down
19 changes: 19 additions & 0 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ const noop = () => {};
/** @typedef {import("webpack").Configuration} Configuration */
/** @typedef {import("webpack").Stats} Stats */
/** @typedef {import("webpack").MultiStats} MultiStats */
/** @typedef {import("fs").ReadStream} ReadStream */

/**
* @typedef {Object} ExtendedServerResponse
Expand Down Expand Up @@ -55,6 +56,23 @@ const noop = () => {};
* @param {Stats | MultiStats} [stats]
*/

/**
* @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
Expand Down Expand Up @@ -89,6 +107,7 @@ const noop = () => {};
* @property {boolean} [serverSideRender]
* @property {OutputFileSystem} [outputFileSystem]
* @property {boolean | string} [index]
* @property {ModifyResponseData<RequestInternal, ResponseInternal>} [modifyResponseData]
*/

/**
Expand Down
35 changes: 28 additions & 7 deletions src/middleware.js
Original file line number Diff line number Diff line change
Expand Up @@ -202,15 +202,26 @@ function wrapper(context) {
);
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);

return;
Expand Down Expand Up @@ -244,7 +255,7 @@ function wrapper(context) {
const isFsSupportsStream =
typeof context.outputFileSystem.createReadStream === "function";

let bufferOtStream;
let bufferOrStream;
let byteLength;

try {
Expand All @@ -253,26 +264,36 @@ function wrapper(context) {
typeof end !== "undefined" &&
isFsSupportsStream
) {
bufferOtStream =
bufferOrStream =
/** @type {import("fs").createReadStream} */
(context.outputFileSystem.createReadStream)(filename, {
start,
end,
});
byteLength = end - start + 1;
} else {
bufferOtStream = /** @type {import("fs").readFileSync} */ (
bufferOrStream = /** @type {import("fs").readFileSync} */ (
context.outputFileSystem.readFileSync
)(filename);
({ byteLength } = bufferOtStream);
({ byteLength } = bufferOrStream);
}
} catch (_ignoreError) {
await goNext();

return;
}

send(req, res, bufferOtStream, byteLength);
if (context.options.modifyResponseData) {
({ data: bufferOrStream, byteLength } =
context.options.modifyResponseData(
req,
res,
bufferOrStream,
byteLength
));
}

send(req, res, bufferOrStream, byteLength);
}
};
}
Expand Down
5 changes: 5 additions & 0 deletions src/options.json
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,11 @@
"minLength": 1
}
]
},
"modifyResponseData": {
"description": "Allows to set up a callback to change the response data.",
"link": "https://github.com/webpack/webpack-dev-middleware#modifyresponsedata",
"instanceof": "Function"
}
},
"additionalProperties": false
Expand Down
14 changes: 14 additions & 0 deletions test/__snapshots__/validation-options.test.js.snap.webpack5
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,13 @@ exports[`validation should throw an error on the "methods" option with "true" va
-> Read more at https://github.com/webpack/webpack-dev-middleware#methods"
`;

exports[`validation should throw an error on the "mimeTypeDefault" option with "0" value 1`] = `
"Invalid options object. Dev Middleware has been initialized using an options object that does not match the API schema.
- options.mimeTypeDefault should be a string.
-> Allows a user to register a default mime type when we can't determine the content type.
-> Read more at https://github.com/webpack/webpack-dev-middleware#mimetypedefault"
`;

exports[`validation should throw an error on the "mimeTypes" option with "foo" value 1`] = `
"Invalid options object. Dev Middleware has been initialized using an options object that does not match the API schema.
- options.mimeTypes should be an object:
Expand All @@ -85,6 +92,13 @@ exports[`validation should throw an error on the "mimeTypes" option with "foo" v
-> Read more at https://github.com/webpack/webpack-dev-middleware#mimetypes"
`;

exports[`validation should throw an error on the "modifyResponseData" option with "true" value 1`] = `
"Invalid options object. Dev Middleware has been initialized using an options object that does not match the API schema.
- options.modifyResponseData should be an instance of function.
-> Allows to set up a callback to change the response data.
-> Read more at https://github.com/webpack/webpack-dev-middleware#modifyresponsedata"
`;

exports[`validation should throw an error on the "outputFileSystem" option with "false" value 1`] = `
"Invalid options object. Dev Middleware has been initialized using an options object that does not match the API schema.
- options.outputFileSystem should be an object:
Expand Down
3 changes: 0 additions & 3 deletions test/helpers/isWebpack5.js

This file was deleted.

0 comments on commit 35dac70

Please sign in to comment.