Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
18 changes: 15 additions & 3 deletions src/middleware.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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 {
Expand All @@ -600,7 +612,7 @@ function wrapper(context) {

cacheControlValue = `public, max-age=${maxAge}`;

if (cacheControl.immutable) {
if (cacheControl.immutable !== false && hasCacheImmutable) {
cacheControlValue += ", immutable";
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we refactor this place? Convert boolean/number/string to object value to avoid logic duplication?

}
Expand Down
299 changes: 293 additions & 6 deletions test/middleware.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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",
);
});
});
Expand All @@ -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",
);
});
});

Expand Down Expand Up @@ -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",
);
});
});

Expand Down Expand Up @@ -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",
);
});
});
Expand All @@ -6129,7 +6133,6 @@ describe.each([
name,
framework,
compiler,
{ cacheImmutable: true },
);
});

Expand Down Expand Up @@ -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",
);
});

Expand All @@ -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",
);
});
});
});
});
});