Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: allow array for headers option #1042

Merged
merged 4 commits into from
Sep 21, 2021
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 10 additions & 4 deletions src/middleware.js
Original file line number Diff line number Diff line change
Expand Up @@ -87,14 +87,20 @@ export default function wrapper(context) {
headers = headers(req, res, context);
}

if (headers) {
const names = Object.keys(headers);
const allHeaders = [];

for (const name of names) {
setHeaderForResponse(res, name, headers[name]);
if (!Array.isArray(headers)) {
// eslint-disable-next-line guard-for-in
for (const name in headers) {
allHeaders.push({ key: name, value: headers[name] });
}
headers = allHeaders;
}

headers.forEach((header) => {
setHeaderForResponse(res, header.key, header.value);
});

if (!getHeaderFromResponse(res, "Content-Type")) {
// content-type name(like application/javascript; charset=utf-8) or false
const contentType = mime.contentType(path.extname(filename));
Expand Down
18 changes: 18 additions & 0 deletions src/options.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,24 @@
},
"headers": {
"anyOf": [
{
"type": "array",
"items": {
"type": "object",
"additionalProperties": false,
"properties": {
"key": {
"description": "key of header.",
"type": "string"
},
"value": {
"description": "value of header.",
"type": "string"
}
}
},
"minItems": 1
},
{
"type": "object"
},
Expand Down
19 changes: 17 additions & 2 deletions test/__snapshots__/validation-options.test.js.snap.webpack4
Original file line number Diff line number Diff line change
@@ -1,12 +1,25 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`validation should throw an error on the "headers" option with "[]" value 1`] = `
"Invalid options object. Dev Middleware has been initialized using an options object that does not match the API schema.
- options.headers should be a non-empty array."
`;

exports[`validation should throw an error on the "headers" option with "[{"foo":"bar"}]" value 1`] = `
"Invalid options object. Dev Middleware has been initialized using an options object that does not match the API schema.
- options.headers[0] has an unknown property 'foo'. These properties are valid:
object { key?, value? }"
`;

exports[`validation should throw an error on the "headers" option with "1" value 1`] = `
"Invalid options object. Dev Middleware has been initialized using an options object that does not match the API schema.
- options.headers should be one of these:
object { … } | function
[object { key?, value? }, ...] (should not have fewer than 1 item) | object { … } | function
-> Allows to pass custom HTTP headers on each request
-> Read more at https://github.com/webpack/webpack-dev-middleware#headers
Details:
* options.headers should be an array:
[object { key?, value? }, ...] (should not have fewer than 1 item)
* options.headers should be an object:
object { … }
* options.headers should be an instance of function."
Expand All @@ -15,10 +28,12 @@ exports[`validation should throw an error on the "headers" option with "1" value
exports[`validation should throw an error on the "headers" 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.headers should be one of these:
object { … } | function
[object { key?, value? }, ...] (should not have fewer than 1 item) | object { … } | function
-> Allows to pass custom HTTP headers on each request
-> Read more at https://github.com/webpack/webpack-dev-middleware#headers
Details:
* options.headers should be an array:
[object { key?, value? }, ...] (should not have fewer than 1 item)
* options.headers should be an object:
object { … }
* options.headers should be an instance of function."
Expand Down
19 changes: 17 additions & 2 deletions test/__snapshots__/validation-options.test.js.snap.webpack5
Original file line number Diff line number Diff line change
@@ -1,12 +1,25 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`validation should throw an error on the "headers" option with "[]" value 1`] = `
"Invalid options object. Dev Middleware has been initialized using an options object that does not match the API schema.
- options.headers should be a non-empty array."
`;

exports[`validation should throw an error on the "headers" option with "[{"foo":"bar"}]" value 1`] = `
"Invalid options object. Dev Middleware has been initialized using an options object that does not match the API schema.
- options.headers[0] has an unknown property 'foo'. These properties are valid:
object { key?, value? }"
`;

exports[`validation should throw an error on the "headers" option with "1" value 1`] = `
"Invalid options object. Dev Middleware has been initialized using an options object that does not match the API schema.
- options.headers should be one of these:
object { … } | function
[object { key?, value? }, ...] (should not have fewer than 1 item) | object { … } | function
-> Allows to pass custom HTTP headers on each request
-> Read more at https://github.com/webpack/webpack-dev-middleware#headers
Details:
* options.headers should be an array:
[object { key?, value? }, ...] (should not have fewer than 1 item)
* options.headers should be an object:
object { … }
* options.headers should be an instance of function."
Expand All @@ -15,10 +28,12 @@ exports[`validation should throw an error on the "headers" option with "1" value
exports[`validation should throw an error on the "headers" 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.headers should be one of these:
object { … } | function
[object { key?, value? }, ...] (should not have fewer than 1 item) | object { … } | function
-> Allows to pass custom HTTP headers on each request
-> Read more at https://github.com/webpack/webpack-dev-middleware#headers
Details:
* options.headers should be an array:
[object { key?, value? }, ...] (should not have fewer than 1 item)
* options.headers should be an object:
object { … }
* options.headers should be an instance of function."
Expand Down
119 changes: 118 additions & 1 deletion test/middleware.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -1012,6 +1012,7 @@ describe.each([
instance = middleware(compiler);

app = framework();
// eslint-disable-next-line no-shadow
app.use((req, res, next) => {
// Express API
if (res.set) {
Expand Down Expand Up @@ -2133,6 +2134,7 @@ describe.each([
app = framework();
app.use(instance);

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

it('should return the "200" code for the "GET" request to path not in outputFileSystem but not return headers', async () => {
// eslint-disable-next-line no-shadow
app.use("/file.jpg", (req, res) => {
// Express API
if (res.send) {
Expand All @@ -2779,6 +2782,62 @@ describe.each([
expect(res.headers["X-nonsense-2"]).toBeUndefined();
});
});

describe("works with array of objects", () => {
beforeEach((done) => {
const compiler = getCompiler(webpackConfig);

instance = middleware(compiler, {
headers: [
{
key: "X-Foo",
value: "value1",
},
{
key: "X-Bar",
value: "value2",
},
],
});

app = framework();
app.use(instance);

listen = listenShorthand(done);

req = request(app);
});

afterEach(close);

it('should return the "200" code for the "GET" request to the bundle file and return headers', async () => {
const response = await req.get(`/bundle.js`);

expect(response.statusCode).toEqual(200);
expect(response.headers["x-foo"]).toEqual("value1");
expect(response.headers["x-bar"]).toEqual("value2");
});

it('should return the "200" code for the "GET" request to path not in outputFileSystem but not return headers', async () => {
// eslint-disable-next-line no-shadow
app.use("/file.jpg", (req, res) => {
// Express API
if (res.send) {
res.send("welcome");
}
// Connect API
else {
res.end("welcome");
}
});

const res = await request(app).get("/file.jpg");
expect(res.statusCode).toEqual(200);
expect(res.headers["x-foo"]).toBeUndefined();
expect(res.headers["x-bar"]).toBeUndefined();
});
});

describe("works with function", () => {
beforeEach((done) => {
const compiler = getCompiler(webpackConfig);
Expand Down Expand Up @@ -2808,6 +2867,7 @@ describe.each([
});

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

describe("works with function returning an array", () => {
beforeEach((done) => {
const compiler = getCompiler(webpackConfig);

instance = middleware(compiler, {
headers: () => [
{
key: "X-Foo",
value: "value1",
},
{
key: "X-Bar",
value: "value2",
},
],
});

app = framework();
app.use(instance);

listen = listenShorthand(done);

req = request(app);
});

afterEach(close);

it('should return the "200" code for the "GET" request to the bundle file and return headers', async () => {
const response = await req.get(`/bundle.js`);

expect(response.statusCode).toEqual(200);
expect(response.headers["x-foo"]).toEqual("value1");
expect(response.headers["x-bar"]).toEqual("value2");
});

it('should return the "200" code for the "GET" request to path not in outputFileSystem but not return headers', async () => {
// eslint-disable-next-line no-shadow
app.use("/file.jpg", (req, res) => {
// Express API
if (res.send) {
res.send("welcome");
}
// Connect API
else {
res.end("welcome");
}
});

const res = await req.get("/file.jpg");
expect(res.statusCode).toEqual(200);
expect(res.headers["x-foo"]).toBeUndefined();
expect(res.headers["x-bar"]).toBeUndefined();
});
});

describe("works with headers function with params", () => {
beforeEach((done) => {
const compiler = getCompiler(webpackConfig);

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

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

app = framework();
app.use(instance);
// eslint-disable-next-line no-shadow
app.use((req, res) => {
// eslint-disable-next-line prefer-destructuring
locals = res.locals;
Expand Down
8 changes: 6 additions & 2 deletions test/validation-options.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,12 @@ describe("validation", () => {
failure: [{}, true],
},
headers: {
success: [{ "X-Custom-Header": "yes" }, () => {}],
failure: [true, 1],
success: [
{ "X-Custom-Header": "yes" },
() => {},
[{ key: "foo", value: "bar" }],
],
failure: [true, 1, [], [{ foo: "bar" }]],
},
publicPath: {
success: ["/foo", "", "auto", () => "/public/path"],
Expand Down