diff --git a/src/middleware.js b/src/middleware.js index ed858d6f9..2bc326fc6 100644 --- a/src/middleware.js +++ b/src/middleware.js @@ -571,9 +571,13 @@ function wrapper(context) { } if (!getResponseHeader(res, "Cache-Control")) { - // TODO enable the `cacheImmutable` by default for the next major release + const hasCacheImmutable = + context.options.cacheImmutable === undefined + ? true + : context.options.cacheImmutable; + const cacheControl = - context.options.cacheImmutable && extra.immutable + hasCacheImmutable && extra.immutable ? { immutable: true } : context.options.cacheControl; @@ -582,12 +586,20 @@ function wrapper(context) { if (typeof cacheControl === "boolean") { cacheControlValue = "public, max-age=31536000"; + + if (hasCacheImmutable) { + cacheControlValue += ", immutable"; + } } else if (typeof cacheControl === "number") { const maxAge = Math.floor( Math.min(Math.max(0, cacheControl), MAX_MAX_AGE) / 1000, ); cacheControlValue = `public, max-age=${maxAge}`; + + if (hasCacheImmutable) { + cacheControlValue += ", immutable"; + } } else if (typeof cacheControl === "string") { cacheControlValue = cacheControl; } else { @@ -600,7 +612,7 @@ function wrapper(context) { cacheControlValue = `public, max-age=${maxAge}`; - if (cacheControl.immutable) { + if (cacheControl.immutable !== false && hasCacheImmutable) { cacheControlValue += ", immutable"; } } diff --git a/test/middleware.test.js b/test/middleware.test.js index 091528f0f..ad1eb844e 100644 --- a/test/middleware.test.js +++ b/test/middleware.test.js @@ -5943,7 +5943,7 @@ describe.each([ expect(response.statusCode).toBe(200); expect(response.headers["cache-control"]).toBeDefined(); expect(response.headers["cache-control"]).toBe( - "public, max-age=31536000", + "public, max-age=31536000, immutable", ); }); }); @@ -5969,7 +5969,9 @@ describe.each([ expect(response.statusCode).toBe(200); expect(response.headers["cache-control"]).toBeDefined(); - expect(response.headers["cache-control"]).toBe("public, max-age=100"); + expect(response.headers["cache-control"]).toBe( + "public, max-age=100, immutable", + ); }); }); @@ -6086,7 +6088,9 @@ describe.each([ expect(response.statusCode).toBe(200); expect(response.headers["cache-control"]).toBeDefined(); - expect(response.headers["cache-control"]).toBe("public, max-age=100"); + expect(response.headers["cache-control"]).toBe( + "public, max-age=100, immutable", + ); }); }); @@ -6114,7 +6118,7 @@ describe.each([ expect(response.statusCode).toBe(200); expect(response.headers["cache-control"]).toBeDefined(); expect(response.headers["cache-control"]).toBe( - "public, max-age=31536000", + "public, max-age=31536000, immutable", ); }); }); @@ -6129,7 +6133,6 @@ describe.each([ name, framework, compiler, - { cacheImmutable: true }, ); }); @@ -6221,7 +6224,7 @@ describe.each([ expect(response.statusCode).toBe(200); expect(response.headers["cache-control"]).toBeDefined(); expect(response.headers["cache-control"]).toBe( - "public, max-age=1000", + "public, max-age=1000, immutable", ); }); @@ -6235,6 +6238,290 @@ describe.each([ ); }); }); + + describe("should not generate `Cache-Control` header for immutable assets when cacheImmutable is false", () => { + beforeEach(async () => { + const compiler = getCompiler({ + ...webpackConfigImmutable, + output: { + path: path.resolve(__dirname, "./outputs/basic"), + }, + }); + + [server, req, instance] = await frameworkFactory( + name, + framework, + compiler, + { cacheImmutable: false }, + ); + }); + + afterEach(async () => { + await close(server, instance); + }); + + it('should return the "200" code for the "GET" request to the bundle file and don\'t generate `Cache-Control` header', async () => { + const response = await req.get("/main.js"); + + expect(response.statusCode).toBe(200); + expect(response.headers["cache-control"]).toBeUndefined(); + }); + + it('should return the "200" code for the "GET" request to the immutable asset and don\'t generate `Cache-Control` header', async () => { + const response = await req.get("/6076fc274f403ebb2d09.svg"); + + expect(response.statusCode).toBe(200); + expect(response.headers["cache-control"]).toBeUndefined(); + }); + }); + + describe("should use cacheControl option when cacheImmutable is false even for immutable assets", () => { + beforeEach(async () => { + const compiler = getCompiler({ + ...webpackConfigImmutable, + output: { + path: path.resolve(__dirname, "./outputs/basic"), + }, + }); + + [server, req, instance] = await frameworkFactory( + name, + framework, + compiler, + { cacheImmutable: false, cacheControl: 1000000 }, + ); + }); + + afterEach(async () => { + await close(server, instance); + }); + + it('should return the "200" code for the "GET" request to the bundle file and generate `Cache-Control` header from cacheControl option', async () => { + const response = await req.get("/main.js"); + + expect(response.statusCode).toBe(200); + expect(response.headers["cache-control"]).toBeDefined(); + expect(response.headers["cache-control"]).toBe( + "public, max-age=1000", + ); + }); + + it('should return the "200" code for the "GET" request to the immutable asset and generate `Cache-Control` header from cacheControl option (not immutable)', async () => { + const response = await req.get("/6076fc274f403ebb2d09.svg"); + + expect(response.statusCode).toBe(200); + expect(response.headers["cache-control"]).toBeDefined(); + expect(response.headers["cache-control"]).toBe( + "public, max-age=1000", + ); + }); + }); + + describe("should use cacheControl string option when cacheImmutable is false", () => { + beforeEach(async () => { + const compiler = getCompiler({ + ...webpackConfigImmutable, + output: { + path: path.resolve(__dirname, "./outputs/basic"), + }, + }); + + [server, req, instance] = await frameworkFactory( + name, + framework, + compiler, + { cacheImmutable: false, cacheControl: "max-age=500" }, + ); + }); + + afterEach(async () => { + await close(server, instance); + }); + + it('should return the "200" code for the "GET" request to the bundle file and generate `Cache-Control` header from cacheControl string option without immutable', async () => { + const response = await req.get("/main.js"); + + expect(response.statusCode).toBe(200); + expect(response.headers["cache-control"]).toBeDefined(); + expect(response.headers["cache-control"]).toBe("max-age=500"); + }); + + it('should return the "200" code for the "GET" request to the immutable asset and generate `Cache-Control` header from cacheControl string option without immutable', async () => { + const response = await req.get("/6076fc274f403ebb2d09.svg"); + + expect(response.statusCode).toBe(200); + expect(response.headers["cache-control"]).toBeDefined(); + expect(response.headers["cache-control"]).toBe("max-age=500"); + }); + }); + + describe("should use cacheControl object option when cacheImmutable is false", () => { + beforeEach(async () => { + const compiler = getCompiler({ + ...webpackConfigImmutable, + output: { + path: path.resolve(__dirname, "./outputs/basic"), + }, + }); + + [server, req, instance] = await frameworkFactory( + name, + framework, + compiler, + { cacheImmutable: false, cacheControl: { maxAge: 2000000 } }, + ); + }); + + afterEach(async () => { + await close(server, instance); + }); + + it('should return the "200" code for the "GET" request to the bundle file and generate `Cache-Control` header from cacheControl object option without immutable', async () => { + const response = await req.get("/main.js"); + + expect(response.statusCode).toBe(200); + expect(response.headers["cache-control"]).toBeDefined(); + expect(response.headers["cache-control"]).toBe( + "public, max-age=2000", + ); + }); + + it('should return the "200" code for the "GET" request to the immutable asset and generate `Cache-Control` header from cacheControl object option without immutable', async () => { + const response = await req.get("/6076fc274f403ebb2d09.svg"); + + expect(response.statusCode).toBe(200); + expect(response.headers["cache-control"]).toBeDefined(); + expect(response.headers["cache-control"]).toBe( + "public, max-age=2000", + ); + }); + }); + + describe("should use cacheControl object option with explicit immutable false", () => { + beforeEach(async () => { + const compiler = getCompiler({ + ...webpackConfigImmutable, + output: { + path: path.resolve(__dirname, "./outputs/basic"), + }, + }); + + [server, req, instance] = await frameworkFactory( + name, + framework, + compiler, + { cacheControl: { maxAge: 3000000, immutable: false } }, + ); + }); + + afterEach(async () => { + await close(server, instance); + }); + + it('should return the "200" code for the "GET" request to the bundle file and generate `Cache-Control` header without immutable when explicitly set to false', async () => { + const response = await req.get("/main.js"); + + expect(response.statusCode).toBe(200); + expect(response.headers["cache-control"]).toBeDefined(); + expect(response.headers["cache-control"]).toBe( + "public, max-age=3000", + ); + }); + + it('should return the "200" code for the "GET" request to the immutable asset and generate `Cache-Control` header without immutable when explicitly set to false', async () => { + const response = await req.get("/6076fc274f403ebb2d09.svg"); + + expect(response.statusCode).toBe(200); + expect(response.headers["cache-control"]).toBeDefined(); + expect(response.headers["cache-control"]).toBe( + "public, max-age=31536000, immutable", + ); + }); + }); + + describe("should use cacheControl boolean option when cacheImmutable is false", () => { + beforeEach(async () => { + const compiler = getCompiler({ + ...webpackConfigImmutable, + output: { + path: path.resolve(__dirname, "./outputs/basic"), + }, + }); + + [server, req, instance] = await frameworkFactory( + name, + framework, + compiler, + { cacheImmutable: false, cacheControl: true }, + ); + }); + + afterEach(async () => { + await close(server, instance); + }); + + it('should return the "200" code for the "GET" request to the bundle file and generate `Cache-Control` header from cacheControl boolean option without immutable', async () => { + const response = await req.get("/main.js"); + + expect(response.statusCode).toBe(200); + expect(response.headers["cache-control"]).toBeDefined(); + expect(response.headers["cache-control"]).toBe( + "public, max-age=31536000", + ); + }); + + it('should return the "200" code for the "GET" request to the immutable asset and generate `Cache-Control` header from cacheControl boolean option without immutable', async () => { + const response = await req.get("/6076fc274f403ebb2d09.svg"); + + expect(response.statusCode).toBe(200); + expect(response.headers["cache-control"]).toBeDefined(); + expect(response.headers["cache-control"]).toBe( + "public, max-age=31536000", + ); + }); + }); + + describe("should use cacheControl number option when cacheImmutable is false without immutable", () => { + beforeEach(async () => { + const compiler = getCompiler({ + ...webpackConfigImmutable, + output: { + path: path.resolve(__dirname, "./outputs/basic"), + }, + }); + + [server, req, instance] = await frameworkFactory( + name, + framework, + compiler, + { cacheImmutable: false, cacheControl: 5000000 }, + ); + }); + + afterEach(async () => { + await close(server, instance); + }); + + it('should return the "200" code for the "GET" request to the bundle file and generate `Cache-Control` header from cacheControl number option without immutable', async () => { + const response = await req.get("/main.js"); + + expect(response.statusCode).toBe(200); + expect(response.headers["cache-control"]).toBeDefined(); + expect(response.headers["cache-control"]).toBe( + "public, max-age=5000", + ); + }); + + it('should return the "200" code for the "GET" request to the immutable asset and generate `Cache-Control` header from cacheControl number option without immutable', async () => { + const response = await req.get("/6076fc274f403ebb2d09.svg"); + + expect(response.statusCode).toBe(200); + expect(response.headers["cache-control"]).toBeDefined(); + expect(response.headers["cache-control"]).toBe( + "public, max-age=5000", + ); + }); + }); }); }); });