From 28f95978f6494885a5f6402ec14e9290bcae6bf4 Mon Sep 17 00:00:00 2001 From: Anshuman Verma Date: Wed, 12 May 2021 00:20:54 +0530 Subject: [PATCH] feat: support fn in headers option (#3267) Headers option now support function with req, res, context as params --- lib/Server.js | 10 +++++-- lib/options.json | 9 +++++- .../validate-options.test.js.snap.webpack4 | 21 +++++++++++-- .../validate-options.test.js.snap.webpack5 | 21 +++++++++++-- test/server/headers-option.test.js | 30 +++++++++++++++---- test/validate-options.test.js | 4 +-- 6 files changed, 78 insertions(+), 17 deletions(-) diff --git a/lib/Server.js b/lib/Server.js index 26d4790c9f..da24355e9e 100644 --- a/lib/Server.js +++ b/lib/Server.js @@ -851,10 +851,14 @@ class Server { } setContentHeaders(req, res, next) { - if (this.options.headers) { + let { headers } = this.options; + if (headers) { + if (typeof headers === 'function') { + headers = headers(req, res, this.middleware.context); + } // eslint-disable-next-line guard-for-in - for (const name in this.options.headers) { - res.setHeader(name, this.options.headers[name]); + for (const name in headers) { + res.setHeader(name, headers[name]); } } diff --git a/lib/options.json b/lib/options.json index d17e819457..4904163ac7 100644 --- a/lib/options.json +++ b/lib/options.json @@ -258,7 +258,14 @@ "description": "Defines routes which are enabled by default, on by default and allows localhost. https://webpack.js.org/configuration/dev-server/#devserverfirewall" }, "headers": { - "type": "object", + "anyOf": [ + { + "type": "object" + }, + { + "instanceof": "Function" + } + ], "description": "Adds headers to all responses. https://webpack.js.org/configuration/dev-server/#devserverheaders" }, "historyApiFallback": { diff --git a/test/__snapshots__/validate-options.test.js.snap.webpack4 b/test/__snapshots__/validate-options.test.js.snap.webpack4 index 538a6912e6..e11bd87c74 100644 --- a/test/__snapshots__/validate-options.test.js.snap.webpack4 +++ b/test/__snapshots__/validate-options.test.js.snap.webpack4 @@ -122,11 +122,26 @@ exports[`options validate should throw an error on the "firewall" option with '[ - configuration.firewall should be an non-empty array." `; +exports[`options validate should throw an error on the "headers" option with '1' value 1`] = ` +"ValidationError: Invalid configuration object. Object has been initialized using a configuration object that does not match the API schema. + - configuration.headers should be one of these: + object { … } | function + -> Adds headers to all responses. https://webpack.js.org/configuration/dev-server/#devserverheaders + Details: + * configuration.headers should be an object: + object { … } + * configuration.headers should be an instance of function." +`; + exports[`options validate should throw an error on the "headers" option with 'false' value 1`] = ` "ValidationError: Invalid configuration object. Object has been initialized using a configuration object that does not match the API schema. - - configuration.headers should be an object: - object { … } - -> Adds headers to all responses. https://webpack.js.org/configuration/dev-server/#devserverheaders" + - configuration.headers should be one of these: + object { … } | function + -> Adds headers to all responses. https://webpack.js.org/configuration/dev-server/#devserverheaders + Details: + * configuration.headers should be an object: + object { … } + * configuration.headers should be an instance of function." `; exports[`options validate should throw an error on the "historyApiFallback" option with '' value 1`] = ` diff --git a/test/__snapshots__/validate-options.test.js.snap.webpack5 b/test/__snapshots__/validate-options.test.js.snap.webpack5 index 538a6912e6..e11bd87c74 100644 --- a/test/__snapshots__/validate-options.test.js.snap.webpack5 +++ b/test/__snapshots__/validate-options.test.js.snap.webpack5 @@ -122,11 +122,26 @@ exports[`options validate should throw an error on the "firewall" option with '[ - configuration.firewall should be an non-empty array." `; +exports[`options validate should throw an error on the "headers" option with '1' value 1`] = ` +"ValidationError: Invalid configuration object. Object has been initialized using a configuration object that does not match the API schema. + - configuration.headers should be one of these: + object { … } | function + -> Adds headers to all responses. https://webpack.js.org/configuration/dev-server/#devserverheaders + Details: + * configuration.headers should be an object: + object { … } + * configuration.headers should be an instance of function." +`; + exports[`options validate should throw an error on the "headers" option with 'false' value 1`] = ` "ValidationError: Invalid configuration object. Object has been initialized using a configuration object that does not match the API schema. - - configuration.headers should be an object: - object { … } - -> Adds headers to all responses. https://webpack.js.org/configuration/dev-server/#devserverheaders" + - configuration.headers should be one of these: + object { … } | function + -> Adds headers to all responses. https://webpack.js.org/configuration/dev-server/#devserverheaders + Details: + * configuration.headers should be an object: + object { … } + * configuration.headers should be an instance of function." `; exports[`options validate should throw an error on the "historyApiFallback" option with '' value 1`] = ` diff --git a/test/server/headers-option.test.js b/test/server/headers-option.test.js index f40590089f..2b8efa1207 100644 --- a/test/server/headers-option.test.js +++ b/test/server/headers-option.test.js @@ -46,11 +46,31 @@ describe('headers option', () => { it('GET request with headers as an array', (done) => { // https://github.com/webpack/webpack-dev-server/pull/1650#discussion_r254217027 - const expected = ['v7', 'v8', 'v9'].includes( - process.version.split('.')[0] - ) - ? 'key1=value1,key2=value2' - : 'key1=value1, key2=value2'; + const expected = 'key1=value1, key2=value2'; + req.get('/main').expect('X-Bar', expected).expect(200, done); + }); + }); + + describe('as a function', () => { + beforeAll((done) => { + server = testServer.start( + config, + { + headers: () => { + return { 'X-Bar': ['key1=value1', 'key2=value2'] }; + }, + port, + }, + done + ); + req = request(server.app); + }); + + afterAll(testServer.close); + + it('GET request with headers as a function', (done) => { + // https://github.com/webpack/webpack-dev-server/pull/1650#discussion_r254217027 + const expected = 'key1=value1, key2=value2'; req.get('/main').expect('X-Bar', expected).expect(200, done); }); }); diff --git a/test/validate-options.test.js b/test/validate-options.test.js index c6bced83aa..fd2fb70df1 100644 --- a/test/validate-options.test.js +++ b/test/validate-options.test.js @@ -156,8 +156,8 @@ const tests = { failure: ['', []], }, headers: { - success: [{}, { foo: 'bar' }], - failure: [false], + success: [{}, { foo: 'bar' }, () => {}], + failure: [false, 1], }, historyApiFallback: { success: [{}, true],