Skip to content

Commit 5a6a3f0

Browse files
authored
feat: allow array for headers option (#1042)
1 parent 961e0ac commit 5a6a3f0

7 files changed

+223
-12
lines changed

README.md

+37-1
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ This property allows a user to pass the list of HTTP request methods accepted by
7070

7171
### headers
7272

73-
Type: `Object|Function`
73+
Type: `Array|Object|Function`
7474
Default: `undefined`
7575

7676
This property allows a user to pass custom HTTP headers on each request.
@@ -98,6 +98,42 @@ webpackDevMiddleware(compiler, {
9898
});
9999
```
100100

101+
or
102+
103+
```js
104+
webpackDevMiddleware(compiler, {
105+
headers: [
106+
{
107+
key: "X-custom-header"
108+
value: "foo"
109+
},
110+
{
111+
key: "Y-custom-header",
112+
value: "bar"
113+
}
114+
]
115+
},
116+
});
117+
```
118+
119+
or
120+
121+
```js
122+
webpackDevMiddleware(compiler, {
123+
headers: () => [
124+
{
125+
key: "X-custom-header"
126+
value: "foo"
127+
},
128+
{
129+
key: "Y-custom-header",
130+
value: "bar"
131+
}
132+
]
133+
},
134+
});
135+
```
136+
101137
### index
102138

103139
Type: `Boolean|String`

src/middleware.js

+10-4
Original file line numberDiff line numberDiff line change
@@ -87,14 +87,20 @@ export default function wrapper(context) {
8787
headers = headers(req, res, context);
8888
}
8989

90-
if (headers) {
91-
const names = Object.keys(headers);
90+
const allHeaders = [];
9291

93-
for (const name of names) {
94-
setHeaderForResponse(res, name, headers[name]);
92+
if (!Array.isArray(headers)) {
93+
// eslint-disable-next-line guard-for-in
94+
for (const name in headers) {
95+
allHeaders.push({ key: name, value: headers[name] });
9596
}
97+
headers = allHeaders;
9698
}
9799

100+
headers.forEach((header) => {
101+
setHeaderForResponse(res, header.key, header.value);
102+
});
103+
98104
if (!getHeaderFromResponse(res, "Content-Type")) {
99105
// content-type name(like application/javascript; charset=utf-8) or false
100106
const contentType = mime.contentType(path.extname(filename));

src/options.json

+18
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,24 @@
2929
},
3030
"headers": {
3131
"anyOf": [
32+
{
33+
"type": "array",
34+
"items": {
35+
"type": "object",
36+
"additionalProperties": false,
37+
"properties": {
38+
"key": {
39+
"description": "key of header.",
40+
"type": "string"
41+
},
42+
"value": {
43+
"description": "value of header.",
44+
"type": "string"
45+
}
46+
}
47+
},
48+
"minItems": 1
49+
},
3250
{
3351
"type": "object"
3452
},

test/__snapshots__/validation-options.test.js.snap.webpack4

+17-2
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,25 @@
11
// Jest Snapshot v1, https://goo.gl/fbAQLP
22

3+
exports[`validation should throw an error on the "headers" option with "[]" value 1`] = `
4+
"Invalid options object. Dev Middleware has been initialized using an options object that does not match the API schema.
5+
- options.headers should be a non-empty array."
6+
`;
7+
8+
exports[`validation should throw an error on the "headers" option with "[{"foo":"bar"}]" value 1`] = `
9+
"Invalid options object. Dev Middleware has been initialized using an options object that does not match the API schema.
10+
- options.headers[0] has an unknown property 'foo'. These properties are valid:
11+
object { key?, value? }"
12+
`;
13+
314
exports[`validation should throw an error on the "headers" option with "1" value 1`] = `
415
"Invalid options object. Dev Middleware has been initialized using an options object that does not match the API schema.
516
- options.headers should be one of these:
6-
object { … } | function
17+
[object { key?, value? }, ...] (should not have fewer than 1 item) | object { … } | function
718
-> Allows to pass custom HTTP headers on each request
819
-> Read more at https://github.com/webpack/webpack-dev-middleware#headers
920
Details:
21+
* options.headers should be an array:
22+
[object { key?, value? }, ...] (should not have fewer than 1 item)
1023
* options.headers should be an object:
1124
object { … }
1225
* options.headers should be an instance of function."
@@ -15,10 +28,12 @@ exports[`validation should throw an error on the "headers" option with "1" value
1528
exports[`validation should throw an error on the "headers" option with "true" value 1`] = `
1629
"Invalid options object. Dev Middleware has been initialized using an options object that does not match the API schema.
1730
- options.headers should be one of these:
18-
object { … } | function
31+
[object { key?, value? }, ...] (should not have fewer than 1 item) | object { … } | function
1932
-> Allows to pass custom HTTP headers on each request
2033
-> Read more at https://github.com/webpack/webpack-dev-middleware#headers
2134
Details:
35+
* options.headers should be an array:
36+
[object { key?, value? }, ...] (should not have fewer than 1 item)
2237
* options.headers should be an object:
2338
object { … }
2439
* options.headers should be an instance of function."

test/__snapshots__/validation-options.test.js.snap.webpack5

+17-2
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,25 @@
11
// Jest Snapshot v1, https://goo.gl/fbAQLP
22

3+
exports[`validation should throw an error on the "headers" option with "[]" value 1`] = `
4+
"Invalid options object. Dev Middleware has been initialized using an options object that does not match the API schema.
5+
- options.headers should be a non-empty array."
6+
`;
7+
8+
exports[`validation should throw an error on the "headers" option with "[{"foo":"bar"}]" value 1`] = `
9+
"Invalid options object. Dev Middleware has been initialized using an options object that does not match the API schema.
10+
- options.headers[0] has an unknown property 'foo'. These properties are valid:
11+
object { key?, value? }"
12+
`;
13+
314
exports[`validation should throw an error on the "headers" option with "1" value 1`] = `
415
"Invalid options object. Dev Middleware has been initialized using an options object that does not match the API schema.
516
- options.headers should be one of these:
6-
object { … } | function
17+
[object { key?, value? }, ...] (should not have fewer than 1 item) | object { … } | function
718
-> Allows to pass custom HTTP headers on each request
819
-> Read more at https://github.com/webpack/webpack-dev-middleware#headers
920
Details:
21+
* options.headers should be an array:
22+
[object { key?, value? }, ...] (should not have fewer than 1 item)
1023
* options.headers should be an object:
1124
object { … }
1225
* options.headers should be an instance of function."
@@ -15,10 +28,12 @@ exports[`validation should throw an error on the "headers" option with "1" value
1528
exports[`validation should throw an error on the "headers" option with "true" value 1`] = `
1629
"Invalid options object. Dev Middleware has been initialized using an options object that does not match the API schema.
1730
- options.headers should be one of these:
18-
object { … } | function
31+
[object { key?, value? }, ...] (should not have fewer than 1 item) | object { … } | function
1932
-> Allows to pass custom HTTP headers on each request
2033
-> Read more at https://github.com/webpack/webpack-dev-middleware#headers
2134
Details:
35+
* options.headers should be an array:
36+
[object { key?, value? }, ...] (should not have fewer than 1 item)
2237
* options.headers should be an object:
2338
object { … }
2439
* options.headers should be an instance of function."

test/middleware.test.js

+118-1
Original file line numberDiff line numberDiff line change
@@ -1012,6 +1012,7 @@ describe.each([
10121012
instance = middleware(compiler);
10131013

10141014
app = framework();
1015+
// eslint-disable-next-line no-shadow
10151016
app.use((req, res, next) => {
10161017
// Express API
10171018
if (res.set) {
@@ -2133,6 +2134,7 @@ describe.each([
21332134
app = framework();
21342135
app.use(instance);
21352136

2137+
// eslint-disable-next-line no-shadow
21362138
app.use("/file.jpg", (req, res) => {
21372139
// Express API
21382140
if (res.send) {
@@ -2762,6 +2764,7 @@ describe.each([
27622764
});
27632765

27642766
it('should return the "200" code for the "GET" request to path not in outputFileSystem but not return headers', async () => {
2767+
// eslint-disable-next-line no-shadow
27652768
app.use("/file.jpg", (req, res) => {
27662769
// Express API
27672770
if (res.send) {
@@ -2779,6 +2782,62 @@ describe.each([
27792782
expect(res.headers["X-nonsense-2"]).toBeUndefined();
27802783
});
27812784
});
2785+
2786+
describe("works with array of objects", () => {
2787+
beforeEach((done) => {
2788+
const compiler = getCompiler(webpackConfig);
2789+
2790+
instance = middleware(compiler, {
2791+
headers: [
2792+
{
2793+
key: "X-Foo",
2794+
value: "value1",
2795+
},
2796+
{
2797+
key: "X-Bar",
2798+
value: "value2",
2799+
},
2800+
],
2801+
});
2802+
2803+
app = framework();
2804+
app.use(instance);
2805+
2806+
listen = listenShorthand(done);
2807+
2808+
req = request(app);
2809+
});
2810+
2811+
afterEach(close);
2812+
2813+
it('should return the "200" code for the "GET" request to the bundle file and return headers', async () => {
2814+
const response = await req.get(`/bundle.js`);
2815+
2816+
expect(response.statusCode).toEqual(200);
2817+
expect(response.headers["x-foo"]).toEqual("value1");
2818+
expect(response.headers["x-bar"]).toEqual("value2");
2819+
});
2820+
2821+
it('should return the "200" code for the "GET" request to path not in outputFileSystem but not return headers', async () => {
2822+
// eslint-disable-next-line no-shadow
2823+
app.use("/file.jpg", (req, res) => {
2824+
// Express API
2825+
if (res.send) {
2826+
res.send("welcome");
2827+
}
2828+
// Connect API
2829+
else {
2830+
res.end("welcome");
2831+
}
2832+
});
2833+
2834+
const res = await request(app).get("/file.jpg");
2835+
expect(res.statusCode).toEqual(200);
2836+
expect(res.headers["x-foo"]).toBeUndefined();
2837+
expect(res.headers["x-bar"]).toBeUndefined();
2838+
});
2839+
});
2840+
27822841
describe("works with function", () => {
27832842
beforeEach((done) => {
27842843
const compiler = getCompiler(webpackConfig);
@@ -2808,6 +2867,7 @@ describe.each([
28082867
});
28092868

28102869
it('should return the "200" code for the "GET" request to path not in outputFileSystem but not return headers', async () => {
2870+
// eslint-disable-next-line no-shadow
28112871
app.use("/file.jpg", (req, res) => {
28122872
// Express API
28132873
if (res.send) {
@@ -2826,12 +2886,67 @@ describe.each([
28262886
});
28272887
});
28282888

2889+
describe("works with function returning an array", () => {
2890+
beforeEach((done) => {
2891+
const compiler = getCompiler(webpackConfig);
2892+
2893+
instance = middleware(compiler, {
2894+
headers: () => [
2895+
{
2896+
key: "X-Foo",
2897+
value: "value1",
2898+
},
2899+
{
2900+
key: "X-Bar",
2901+
value: "value2",
2902+
},
2903+
],
2904+
});
2905+
2906+
app = framework();
2907+
app.use(instance);
2908+
2909+
listen = listenShorthand(done);
2910+
2911+
req = request(app);
2912+
});
2913+
2914+
afterEach(close);
2915+
2916+
it('should return the "200" code for the "GET" request to the bundle file and return headers', async () => {
2917+
const response = await req.get(`/bundle.js`);
2918+
2919+
expect(response.statusCode).toEqual(200);
2920+
expect(response.headers["x-foo"]).toEqual("value1");
2921+
expect(response.headers["x-bar"]).toEqual("value2");
2922+
});
2923+
2924+
it('should return the "200" code for the "GET" request to path not in outputFileSystem but not return headers', async () => {
2925+
// eslint-disable-next-line no-shadow
2926+
app.use("/file.jpg", (req, res) => {
2927+
// Express API
2928+
if (res.send) {
2929+
res.send("welcome");
2930+
}
2931+
// Connect API
2932+
else {
2933+
res.end("welcome");
2934+
}
2935+
});
2936+
2937+
const res = await req.get("/file.jpg");
2938+
expect(res.statusCode).toEqual(200);
2939+
expect(res.headers["x-foo"]).toBeUndefined();
2940+
expect(res.headers["x-bar"]).toBeUndefined();
2941+
});
2942+
});
2943+
28292944
describe("works with headers function with params", () => {
28302945
beforeEach((done) => {
28312946
const compiler = getCompiler(webpackConfig);
28322947

28332948
instance = middleware(compiler, {
2834-
// eslint-disable-next-line no-unused-vars
2949+
// eslint-disable-next-line no-unused-vars, no-shadow
28352950
headers: (req, res, context) => {
28362951
res.setHeader("X-nonsense-1", "yes");
28372952
res.setHeader("X-nonsense-2", "no");
@@ -2857,6 +2972,7 @@ describe.each([
28572972
});
28582973

28592974
it('should return the "200" code for the "GET" request to path not in outputFileSystem but not return headers', async () => {
2975+
// eslint-disable-next-line no-shadow
28602976
app.use("/file.jpg", (req, res) => {
28612977
// Express API
28622978
if (res.send) {
@@ -2934,6 +3050,7 @@ describe.each([
29343050

29353051
app = framework();
29363052
app.use(instance);
3053+
// eslint-disable-next-line no-shadow
29373054
app.use((req, res) => {
29383055
// eslint-disable-next-line prefer-destructuring
29393056
locals = res.locals;

test/validation-options.test.js

+6-2
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,12 @@ describe("validation", () => {
2828
failure: [{}, true],
2929
},
3030
headers: {
31-
success: [{ "X-Custom-Header": "yes" }, () => {}],
32-
failure: [true, 1],
31+
success: [
32+
{ "X-Custom-Header": "yes" },
33+
() => {},
34+
[{ key: "foo", value: "bar" }],
35+
],
36+
failure: [true, 1, [], [{ foo: "bar" }]],
3337
},
3438
publicPath: {
3539
success: ["/foo", "", "auto", () => "/public/path"],

0 commit comments

Comments
 (0)