Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

feat(isr): add compression #1763

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

Closed
wants to merge 23 commits into from
Closed
Show file tree
Hide file tree
Changes from 3 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
ba1cbbe
chore(isr): update CacheHandler and IsrServiceInterface typings
maxisam Aug 20, 2024
f7755ac
refactor: fix eslint issues
maxisam Aug 20, 2024
0af4f56
fix(isr): filter urlsOnHold by cacheKey instead of url
maxisam Aug 20, 2024
244bff7
feat: add allowed query params options #1743
maxisam Aug 22, 2024
50643b3
feat: handle query string for filesystem cache #1690
maxisam Aug 22, 2024
9dc5a70
fix(isr): in memory cache handler should use extends #1736
maxisam Aug 22, 2024
76aa916
Merge branch 'fix/instanceof' into dev
maxisam Aug 23, 2024
fcb59b2
Merge branch 'refactor/es-lint' into dev
maxisam Aug 23, 2024
cc19ee3
Merge branch 'feat/allowed-query-params' into dev
maxisam Aug 23, 2024
c37269b
refactor(isr): rename CacheRegeneration to CacheGeneration
maxisam Aug 23, 2024
039f09c
refactor(isr): rename CacheRegeneration to CacheGeneration
maxisam Aug 23, 2024
c95d1c6
fix(isr): handle modifyGeneratedHtml behavior consistantly #1758
maxisam Aug 29, 2024
e7ed6a5
Merge branch 'main' into dev
maxisam Aug 29, 2024
cb0261a
refactor(isr): use modifyGeneratedHtml instead
maxisam Aug 29, 2024
2dd9551
feat(isr): update the example to show modifyGeneratedHtml usage
maxisam Aug 29, 2024
9f4f0cd
Merge branch 'main' into feat/callback-cachemsg
maxisam Aug 29, 2024
5b7603e
Merge pull request #2 from maxisam/feat/consolidate-cache-generation
maxisam Aug 29, 2024
2c88c4f
feat(isr): add non-blocking-render option
maxisam Aug 29, 2024
dcc61f9
feat(isr): add background revalidation option
maxisam Aug 29, 2024
fe0bac2
feat(isr): enable background revalidation and non-blocking render
maxisam Aug 29, 2024
a88b5ca
Merge pull request #3 from maxisam/feat/response-first
maxisam Aug 29, 2024
2ae8857
feat(isr): add compression #1755
maxisam Aug 30, 2024
85b0dc3
chore(isr): rename HTML compression method
maxisam Aug 30, 2024
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
7 changes: 7 additions & 0 deletions 7 libs/isr/models/src/isr-handler-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,13 @@ export interface ISRHandlerConfig {
* ],
*/
variants?: RenderVariant[];

/**
* This array of query params will be allowed to be part of the cache key.
* If not provided, which is null, all query params will be part of the cache key.
* If provided as an empty array, no query params will be part of the cache key.
*/
allowedQueryParams?: string[];
}

export interface ServeFromCacheConfig {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import {
convertFileNameToRoute,
convertRouteToFileName,
} from './filesystem-cache-handler';

// Use the functions as needed
describe('Route and File Name Conversion', () => {
describe('convertRouteToFileName', () => {
it('should convert a simple route without query parameters', () => {
const route = '/users/profile';
const expectedFileName = '__users__profile';
expect(convertRouteToFileName(route)).toEqual(expectedFileName);
});

it('should convert a route with query parameters', () => {
const route = '/search?query=test';
const expectedFileName = '__search++query=test';
expect(convertRouteToFileName(route)).toEqual(expectedFileName);
});
});

describe('convertFileNameToRoute', () => {
it('should convert a simple file name back to a route', () => {
const fileName = '__users__profile';
const expectedRoute = '/users/profile';
expect(convertFileNameToRoute(fileName)).toEqual(expectedRoute);
});

it('should convert a file name with "++" back to a route with a query parameter', () => {
const fileName = '__search++query=test';
const expectedRoute = '/search?query=test';
expect(convertFileNameToRoute(fileName)).toEqual(expectedRoute);
});
});
});
12 changes: 8 additions & 4 deletions 12 libs/isr/server/src/cache-handlers/filesystem-cache-handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -344,16 +344,20 @@ function getFileFullPath(fileName: string, cacheFolderPath: string): string {
* @param {string} route - The string representing the route to be converted into a file name.
* @returns {string} The modified string representing the file name obtained by replacing '/' characters with '__'.
*/
function convertRouteToFileName(route: string): string {
export function convertRouteToFileName(route: string): string {
// replace all occurrences of '/' character in the 'route' string with '__' using regular expression
return route.replace(new RegExp('/', 'g'), '__');
return route
.replace(new RegExp('/', 'g'), '__')
.replace(new RegExp('\\?', 'g'), '++');
}

/**
* This function takes a string parameter 'fileName' and replaces all '__' strings in it with '/' and returns the modified string.
* @param fileName - The string representing the file name to be converted into a route.
*/
function convertFileNameToRoute(fileName: string): string {
export function convertFileNameToRoute(fileName: string): string {
// replace all occurrences of '__' string in the 'fileName' string with '/' using regular expression
return fileName.replace(new RegExp('__', 'g'), '/');
return fileName
.replace(new RegExp('\\+\\+', 'g'), '?')
.replace(new RegExp('__', 'g'), '/');
}
6 changes: 5 additions & 1 deletion 6 libs/isr/server/src/cache-regeneration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,11 @@ export class CacheRegeneration {
): Promise<void> {
const { url } = req;
const variant = getVariant(req, this.isrConfig);
const cacheKey = getCacheKey(url, variant);
const cacheKey = getCacheKey(
url,
this.isrConfig.allowedQueryParams,
variant,
);

if (this.urlsOnHold.includes(cacheKey)) {
logger.log('Another regeneration is on-going for this url...');
Expand Down
21 changes: 13 additions & 8 deletions 21 libs/isr/server/src/isr-handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@ export class ISRHandler {
for (const variant of variants) {
result.push({
url,
cacheKey: getCacheKey(url, variant),
cacheKey: getCacheKey(url, this.config.allowedQueryParams, variant),
reqSimulator: variant.simulateVariant
? variant.simulateVariant
: defaultVariant,
Expand All @@ -187,8 +187,9 @@ export class ISRHandler {
): Promise<Response | void> {
try {
const variant = this.getVariant(req);

const cacheData = await this.cache.get(getCacheKey(req.url, variant));
const cacheData = await this.cache.get(
getCacheKey(req.url, this.config.allowedQueryParams, variant),
);
const { html, options: cacheConfig, createdAt } = cacheData;

const cacheHasBuildId =
Expand Down Expand Up @@ -228,7 +229,7 @@ export class ISRHandler {
// Cache exists. Send it.
this.logger.log(
`Page was retrieved from cache: `,
getCacheKey(req.url, variant),
getCacheKey(req.url, this.config.allowedQueryParams, variant),
);
return res.send(finalHtml);
} catch (error) {
Expand Down Expand Up @@ -281,10 +282,14 @@ export class ISRHandler {
const variant = this.getVariant(req);

// Cache the rendered `html` for this request url to use for subsequent requests
await this.cache.add(getCacheKey(req.url, variant), finalHtml, {
revalidate,
buildId: this.config.buildId,
});
await this.cache.add(
getCacheKey(req.url, this.config.allowedQueryParams, variant),
finalHtml,
{
revalidate,
buildId: this.config.buildId,
},
);
return res.send(finalHtml);
}

Expand Down
38 changes: 38 additions & 0 deletions 38 libs/isr/server/src/utils/cache-utils.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { RenderVariant } from '../../../models/src';
import { getCacheKey } from './cache-utils';

describe('getCacheKey', () => {
it('should return the URL without query parameters when none are allowed', () => {
const url = '/page?param1=value1&param2=value2';
const result = getCacheKey(url, [], null);
expect(result).toBe('/page');
});

it('should return the URL with query parameters when it is null or undefined', () => {
const url = '/page?param1=value1&param2=value2';
const result = getCacheKey(url, null, null);
expect(result).toBe('/page?param1=value1&param2=value2');
});

it('should include only allowed query parameters in the result', () => {
const url = '/page?allowed=value&disallowed=value';
const result = getCacheKey(url, ['allowed'], null);
expect(result).toBe('/page?allowed=value');
});

it('should exclude disallowed query parameters', () => {
const url = '/page?allowed=value&disallowed=value';
const result = getCacheKey(url, ['allowed'], null);
expect(result).not.toContain('disallowed=value');
});

it('should append the variant identifier when a variant is provided', () => {
const url = '/page?param=value';
const variant: RenderVariant = {
identifier: 'variant123',
detectVariant: () => true,
};
const result = getCacheKey(url, ['param'], variant);
expect(result).toBe('/page?param=value<variantId:variant123>');
});
});
21 changes: 19 additions & 2 deletions 21 libs/isr/server/src/utils/cache-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,27 @@ import { Request } from 'express';

export const getCacheKey = (
url: string,
allowedQueryParams: string[] | null | undefined,
variant: RenderVariant | null,
): string => {
if (!variant) return url;
return `${url}<variantId:${variant.identifier}>`;
let normalizedUrl = url;
if (allowedQueryParams) {
// Normalize the URL by removing disallowed query parameters
// using http://localhost as the base URL to parse the URL
// since the URL constructor requires a base URL to parse relative URLs
// it will not be used in the final cache key
const urlObj = new URL(url, 'http://localhost');
const searchParams = urlObj.searchParams;
const filteredSearchParams = new URLSearchParams();
searchParams.forEach((value, key) => {
if (allowedQueryParams.includes(key)) {
filteredSearchParams.append(key, value);
}
});
normalizedUrl = `${urlObj.pathname}${filteredSearchParams.toString() ? '?' + filteredSearchParams.toString() : ''}`;
}
if (!variant) return normalizedUrl;
return `${normalizedUrl}<variantId:${variant.identifier}>`;
};

export const getVariant = (
Expand Down
Morty Proxy This is a proxified and sanitized view of the page, visit original site.