Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 8329179

Browse files
committed
feat(isr): add compression
1 parent 9ae21ca commit 8329179

File tree

9 files changed

+158
-38
lines changed

9 files changed

+158
-38
lines changed

apps/ssr-isr/server.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
import { CommonEngine } from '@angular/ssr';
22
import { ModifyHtmlCallbackFn } from '@rx-angular/isr/models';
3-
import { ISRHandler } from '@rx-angular/isr/server';
3+
import {
4+
compressHtml,
5+
CompressStaticFilter,
6+
ISRHandler,
7+
} from '@rx-angular/isr/server';
8+
import compression from 'compression';
49
import express, { Request } from 'express';
510
import { dirname, join, resolve } from 'node:path';
611
import { fileURLToPath } from 'node:url';
@@ -34,9 +39,12 @@ export function app(): express.Express {
3439
backgroundRevalidation: true, // will revalidate in background and serve the cache page first
3540
nonBlockingRender: true, // will serve page first and store in cache in background
3641
modifyGeneratedHtml: customModifyGeneratedHtml,
42+
compressHtml: compressHtml, // compress the html before storing in cache
43+
// cacheHtmlCompressionMethod: 'gzip', // compression method for cache
3744
// cache: fsCacheHandler,
3845
});
39-
46+
// compress js|css files
47+
server.use(compression({ filter: CompressStaticFilter }));
4048
server.use(express.json());
4149

4250
server.post(

libs/isr/models/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ export {
77
VariantRebuildItem,
88
} from './cache-handler';
99
export {
10+
CompressHtmlFn,
1011
InvalidateConfig,
1112
ISRHandlerConfig,
1213
ModifyHtmlCallbackFn,

libs/isr/models/src/isr-handler-config.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,19 @@ export interface ISRHandlerConfig {
124124
* If set to true, the server will provide the cached HTML as soon as possible and will revalidate the cache in the background.
125125
*/
126126
backgroundRevalidation?: boolean;
127+
128+
/**
129+
* compression callback to compress the html before storing it in the cache.
130+
* If not provided, the html will not be compressed.
131+
* If provided, the html will be compressed before storing it as base64 in the cache,
132+
* also this will disable the modifyCachedHtml callback, because html is compressed and can't be modified.
133+
*/
134+
compressHtml?: CompressHtmlFn;
135+
136+
/**
137+
* Cached Html compression method, it will use gzip by default if not provided.
138+
*/
139+
htmlCompressionMethod?: string;
127140
}
128141

129142
export interface ServeFromCacheConfig {
@@ -181,3 +194,5 @@ export interface RenderConfig {
181194
export interface RouteISRConfig {
182195
revalidate?: number | null;
183196
}
197+
198+
export type CompressHtmlFn = (html: string) => Promise<Buffer>;

libs/isr/server/src/cache-generation.ts

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,12 @@ import { Request, Response } from 'express';
44
import { ISRLogger } from './isr-logger';
55
import { defaultModifyGeneratedHtml } from './modify-generated-html';
66
import { getCacheKey, getVariant } from './utils/cache-utils';
7+
import { bufferToString } from './utils/compression-utils';
78
import { getRouteISRDataFromHTML } from './utils/get-isr-options';
89
import { renderUrl, RenderUrlConfig } from './utils/render-url';
910

1011
export interface IGeneratedResult {
11-
html?: string;
12+
html?: string | Buffer;
1213
errors?: string[];
1314
}
1415

@@ -22,7 +23,6 @@ export class CacheGeneration {
2223
public cache: CacheHandler,
2324
public logger: ISRLogger,
2425
) {}
25-
2626
async generate(
2727
req: Request,
2828
res: Response,
@@ -39,7 +39,6 @@ export class CacheGeneration {
3939

4040
return this.generateWithCacheKey(req, res, cacheKey, providers, mode);
4141
}
42-
4342
async generateWithCacheKey(
4443
req: Request,
4544
res: Response,
@@ -60,7 +59,6 @@ export class CacheGeneration {
6059

6160
this.urlsOnHold.push(cacheKey);
6261
}
63-
6462
const renderUrlConfig: RenderUrlConfig = {
6563
req,
6664
res,
@@ -72,17 +70,21 @@ export class CacheGeneration {
7270
browserDistFolder: this.isrConfig.browserDistFolder,
7371
inlineCriticalCss: this.isrConfig.inlineCriticalCss,
7472
};
75-
7673
try {
7774
const html = await renderUrl(renderUrlConfig);
7875
const { revalidate, errors } = getRouteISRDataFromHTML(html);
7976

8077
// Apply the modify generation callback
8178
// If undefined, use the default modifyGeneratedHtml function
82-
const finalHtml = this.isrConfig.modifyGeneratedHtml
79+
let finalHtml: string | Buffer = this.isrConfig.modifyGeneratedHtml
8380
? this.isrConfig.modifyGeneratedHtml(req, html, revalidate)
8481
: defaultModifyGeneratedHtml(req, html, revalidate);
85-
82+
let cacheString: string = finalHtml;
83+
// Apply the compressHtml callback
84+
if (this.isrConfig.compressHtml) {
85+
finalHtml = await this.isrConfig.compressHtml(finalHtml);
86+
cacheString = bufferToString(finalHtml);
87+
}
8688
// if there are errors, don't add the page to cache
8789
if (errors?.length && this.isrConfig.skipCachingOnHttpError) {
8890
// remove url from urlsOnHold because we want to try to regenerate it again
@@ -106,7 +108,7 @@ export class CacheGeneration {
106108

107109
// add the regenerated page to cache
108110
const addToCache = () => {
109-
return this.cache.add(cacheKey, finalHtml, {
111+
return this.cache.add(cacheKey, cacheString, {
110112
revalidate,
111113
buildId: this.isrConfig.buildId,
112114
});

libs/isr/server/src/compress-html.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import { CompressHtmlFn } from '@rx-angular/isr/models';
2+
import * as zlib from 'zlib';
3+
4+
export const compressHtml: CompressHtmlFn = (html: string): Promise<Buffer> => {
5+
return new Promise((resolve, reject) => {
6+
zlib.gzip(html, (err, buffer) => {
7+
if (err) {
8+
reject(err);
9+
} else {
10+
resolve(buffer);
11+
}
12+
});
13+
});
14+
};

libs/isr/server/src/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@ export {
33
FileSystemCacheOptions,
44
} from './cache-handlers/filesystem-cache-handler';
55
export { InMemoryCacheHandler } from './cache-handlers/in-memory-cache-handler';
6+
export { compressHtml } from './compress-html';
67
export { IsrModule } from './isr.module';
78
export { ISRHandler } from './isr-handler';
89
export { IsrServerService } from './isr-server.service';
910
export { isrHttpInterceptors, provideISR } from './provide-isr';
11+
export { CompressStaticFilter } from './utils/compression-utils';

libs/isr/server/src/isr-handler.ts

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import { CacheGeneration } from './cache-generation';
1212
import { InMemoryCacheHandler } from './cache-handlers/in-memory-cache-handler';
1313
import { ISRLogger } from './isr-logger';
1414
import { getCacheKey, getVariant } from './utils/cache-utils';
15+
import { setCompressHeader, stringToBuffer } from './utils/compression-utils';
1516

1617
export class ISRHandler {
1718
protected cache!: CacheHandler;
@@ -190,7 +191,11 @@ export class ISRHandler {
190191

191192
// Cache exists. Send it.
192193
this.logger.log(`Page was retrieved from cache: `, cacheKey);
193-
let finalHtml = html;
194+
let finalHtml: string | Buffer = html;
195+
196+
if (this.isrConfig.compressHtml) {
197+
finalHtml = stringToBuffer(finalHtml);
198+
}
194199

195200
// if the cache is expired, we will regenerate it
196201
if (cacheConfig.revalidate && cacheConfig.revalidate > 0) {
@@ -224,6 +229,20 @@ export class ISRHandler {
224229
}
225230
}
226231

232+
if (!this.isrConfig.compressHtml) {
233+
// Apply the callback if given
234+
// It doesn't work with compressed html
235+
if (config?.modifyCachedHtml) {
236+
const timeStart = performance.now();
237+
finalHtml = config.modifyCachedHtml(req, finalHtml as string);
238+
const totalTime = (performance.now() - timeStart).toFixed(2);
239+
finalHtml += `<!--\nℹ️ ISR: This cachedHtml has been modified with modifyCachedHtml()\n❗️
240+
This resulted into more ${totalTime}ms of processing time.\n-->`;
241+
}
242+
} else {
243+
setCompressHeader(res, this.isrConfig.htmlCompressionMethod);
244+
}
245+
227246
return res.send(finalHtml);
228247
} catch (error) {
229248
// Cache does not exist. Serve user using SSR
@@ -261,9 +280,12 @@ export class ISRHandler {
261280
config?.providers,
262281
'generate',
263282
);
264-
if (!result) {
283+
if (!result?.html) {
265284
throw new Error('Error while generating the page!');
266285
} else {
286+
if (this.isrConfig.compressHtml) {
287+
setCompressHeader(res, this.isrConfig.htmlCompressionMethod);
288+
}
267289
return res.send(result.html);
268290
}
269291
} catch (error) {
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import * as compression from 'compression';
2+
import * as express from 'express';
3+
4+
export function setCompressHeader(
5+
response: express.Response,
6+
method?: string,
7+
): void {
8+
response.setHeader('Content-Encoding', method || 'gzip');
9+
response.setHeader('Content-type', 'text/html; charset=utf-8');
10+
response.setHeader('Vary', 'Accept-Encoding');
11+
}
12+
13+
export function CompressStaticFilter(
14+
req: express.Request,
15+
res: express.Response,
16+
): boolean {
17+
const isStatic = new RegExp('.(?:js|css)$');
18+
if (!isStatic.test(req.url)) {
19+
// don't compress responses with this request header
20+
return false;
21+
}
22+
// fallback to standard filter function
23+
return compression.filter(req, res);
24+
}
25+
26+
export const bufferToString = (buffer: Buffer): string => {
27+
return buffer.toString('base64');
28+
};
29+
30+
export const stringToBuffer = (str: string): Buffer => {
31+
return Buffer.from(str, 'base64');
32+
};

yarn.lock

Lines changed: 50 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -4207,7 +4207,7 @@
42074207
"@docusaurus/theme-search-algolia" "2.0.1"
42084208
"@docusaurus/types" "2.0.1"
42094209

4210-
"@docusaurus/[email protected]", "react-loadable@npm:@docusaurus/[email protected]":
4210+
"@docusaurus/[email protected]":
42114211
version "5.5.2"
42124212
resolved "https://registry.yarnpkg.com/@docusaurus/react-loadable/-/react-loadable-5.5.2.tgz#81aae0db81ecafbdaee3651f12804580868fa6ce"
42134213
integrity sha512-A3dYjdBGuy0IGT+wyLIGIKLRE+sAk1iNk0f1HjNDysO7u8lhL4N3VEm+FAubmJbAztn94F7MxBTPmnixbiyFdQ==
@@ -7392,6 +7392,13 @@
73927392
dependencies:
73937393
"@types/node" "*"
73947394

7395+
"@types/compression@^1.7.5":
7396+
version "1.7.5"
7397+
resolved "https://registry.yarnpkg.com/@types/compression/-/compression-1.7.5.tgz#0f80efef6eb031be57b12221c4ba6bc3577808f7"
7398+
integrity sha512-AAQvK5pxMpaT+nDvhHrsBhLSYG5yQdtkaJE1WYieSNY2mVFKAgmU4ks65rkZD5oqnGCFLyQpUr1CqI4DmUMyDg==
7399+
dependencies:
7400+
"@types/express" "*"
7401+
73957402
"@types/connect-history-api-fallback@^1.3.5":
73967403
version "1.3.5"
73977404
resolved "https://registry.yarnpkg.com/@types/connect-history-api-fallback/-/connect-history-api-fallback-1.3.5.tgz#d1f7a8a09d0ed5a57aee5ae9c18ab9b803205dae"
@@ -7474,16 +7481,6 @@
74747481
"@types/qs" "*"
74757482
"@types/range-parser" "*"
74767483

7477-
"@types/express-serve-static-core@^4.17.18":
7478-
version "4.17.41"
7479-
resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.17.41.tgz#5077defa630c2e8d28aa9ffc2c01c157c305bef6"
7480-
integrity sha512-OaJ7XLaelTgrvlZD8/aa0vvvxZdUmlCn6MtWeB7TkiKW70BQLc9XEPpDLPdbo52ZhXUCrznlWdCHWxJWtdyajA==
7481-
dependencies:
7482-
"@types/node" "*"
7483-
"@types/qs" "*"
7484-
"@types/range-parser" "*"
7485-
"@types/send" "*"
7486-
74877484
"@types/express@*", "@types/express@^4.17.13":
74887485
version "4.17.17"
74897486
resolved "https://registry.yarnpkg.com/@types/express/-/express-4.17.17.tgz#01d5437f6ef9cfa8668e616e13c2f2ac9a491ae4"
@@ -7494,17 +7491,7 @@
74947491
"@types/qs" "*"
74957492
"@types/serve-static" "*"
74967493

7497-
7498-
version "4.17.14"
7499-
resolved "https://registry.yarnpkg.com/@types/express/-/express-4.17.14.tgz#143ea0557249bc1b3b54f15db4c81c3d4eb3569c"
7500-
integrity sha512-TEbt+vaPFQ+xpxFLFssxUDXj5cWCxZJjIcB7Yg0k0GMHGtgtQgpvx/MUQUeAkNbA9AAGrwkAsoeItdTgS7FMyg==
7501-
dependencies:
7502-
"@types/body-parser" "*"
7503-
"@types/express-serve-static-core" "^4.17.18"
7504-
"@types/qs" "*"
7505-
"@types/serve-static" "*"
7506-
7507-
"@types/express@^4.17.21":
7494+
"@types/[email protected]", "@types/express@^4.17.21":
75087495
version "4.17.21"
75097496
resolved "https://registry.yarnpkg.com/@types/express/-/express-4.17.21.tgz#c26d4a151e60efe0084b23dc3369ebc631ed192d"
75107497
integrity sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ==
@@ -18507,6 +18494,14 @@ react-loadable-ssr-addon-v5-slorber@^1.0.1:
1850718494
dependencies:
1850818495
"@babel/runtime" "^7.10.3"
1850918496

18497+
"react-loadable@npm:@docusaurus/[email protected]":
18498+
version "5.5.2"
18499+
resolved "https://registry.yarnpkg.com/@docusaurus/react-loadable/-/react-loadable-5.5.2.tgz#81aae0db81ecafbdaee3651f12804580868fa6ce"
18500+
integrity sha512-A3dYjdBGuy0IGT+wyLIGIKLRE+sAk1iNk0f1HjNDysO7u8lhL4N3VEm+FAubmJbAztn94F7MxBTPmnixbiyFdQ==
18501+
dependencies:
18502+
"@types/react" "*"
18503+
prop-types "^15.6.2"
18504+
1851018505
react-player@^2.12.0:
1851118506
version "2.12.0"
1851218507
resolved "https://registry.yarnpkg.com/react-player/-/react-player-2.12.0.tgz#2fc05dbfec234c829292fbca563b544064bd14f0"
@@ -19947,7 +19942,16 @@ string-length@^4.0.1:
1994719942
char-regex "^1.0.2"
1994819943
strip-ansi "^6.0.0"
1994919944

19950-
"string-width-cjs@npm:string-width@^4.2.0", string-width@^4.0.0, string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.2, string-width@^4.2.3:
19945+
"string-width-cjs@npm:string-width@^4.2.0":
19946+
version "4.2.3"
19947+
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
19948+
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
19949+
dependencies:
19950+
emoji-regex "^8.0.0"
19951+
is-fullwidth-code-point "^3.0.0"
19952+
strip-ansi "^6.0.1"
19953+
19954+
string-width@^4.0.0, string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.2, string-width@^4.2.3:
1995119955
version "4.2.3"
1995219956
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
1995319957
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
@@ -19988,7 +19992,14 @@ stringify-object@^3.3.0:
1998819992
is-obj "^1.0.1"
1998919993
is-regexp "^1.0.0"
1999019994

19991-
"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1:
19995+
"strip-ansi-cjs@npm:strip-ansi@^6.0.1":
19996+
version "6.0.1"
19997+
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
19998+
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
19999+
dependencies:
20000+
ansi-regex "^5.0.1"
20001+
20002+
strip-ansi@^6.0.0, strip-ansi@^6.0.1:
1999220003
version "6.0.1"
1999320004
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
1999420005
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
@@ -21484,8 +21495,7 @@ wordwrap@^1.0.0:
2148421495
resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb"
2148521496
integrity sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==
2148621497

21487-
"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0:
21488-
name wrap-ansi-cjs
21498+
"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0":
2148921499
version "7.0.0"
2149021500
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
2149121501
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
@@ -21503,6 +21513,15 @@ wrap-ansi@^6.0.1, wrap-ansi@^6.2.0:
2150321513
string-width "^4.1.0"
2150421514
strip-ansi "^6.0.0"
2150521515

21516+
wrap-ansi@^7.0.0:
21517+
version "7.0.0"
21518+
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
21519+
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
21520+
dependencies:
21521+
ansi-styles "^4.0.0"
21522+
string-width "^4.1.0"
21523+
strip-ansi "^6.0.0"
21524+
2150621525
wrap-ansi@^8.0.1, wrap-ansi@^8.1.0:
2150721526
version "8.1.0"
2150821527
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214"
@@ -21673,6 +21692,11 @@ yocto-queue@^1.0.0:
2167321692
resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-1.0.0.tgz#7f816433fb2cbc511ec8bf7d263c3b58a1a3c251"
2167421693
integrity sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==
2167521694

21695+
zlib@^1.0.5:
21696+
version "1.0.5"
21697+
resolved "https://registry.yarnpkg.com/zlib/-/zlib-1.0.5.tgz#6e7c972fc371c645a6afb03ab14769def114fcc0"
21698+
integrity sha512-40fpE2II+Cd3k8HWTWONfeKE2jL+P42iWJ1zzps5W51qcTsOUKM5Q5m2PFb0CLxlmFAaUuUdJGc3OfZy947v0w==
21699+
2167621700
2167721701
version "0.14.4"
2167821702
resolved "https://registry.yarnpkg.com/zone.js/-/zone.js-0.14.4.tgz#e0168fe450e3e4313c8efdb4a0ae4b557ac0fdd8"

0 commit comments

Comments
 (0)