From f90ce0031ed735fdee2224f64a18e092516013f0 Mon Sep 17 00:00:00 2001 From: Oleksandr Date: Sat, 10 Jan 2026 11:33:54 +0100 Subject: [PATCH 1/5] feat: added support compression for static files, Ref #2002 --- lib/static.js | 34 ++++++++++++++++-- test/static.js | 86 ++++++++++++++++++++++++++++++++++++++++++++++ types/core.d.ts | 1 + types/impress.d.ts | 3 ++ 4 files changed, 121 insertions(+), 3 deletions(-) diff --git a/lib/static.js b/lib/static.js index e074afdd6..5323b842b 100644 --- a/lib/static.js +++ b/lib/static.js @@ -21,12 +21,26 @@ const status = (code) => { return file; }; +const COMPRESSORS = { + gzip: node.zlib.createGzip, + deflate: node.zlib.createDeflate, + br: node.zlib.createBrotliCompress, + zstd: node.zlib.createZstdCompress, +}; + class Static extends Place { constructor(name, application, options = {}) { super(name, application); this.files = new Map(); this.ext = options.ext; this.maxFileSize = -1; + const { compressType } = options; + const compressor = COMPRESSORS[compressType]?.(); + if (compressType && !compressor) { + throw new Error(`Unsupported compression type ${compressType}`); + } + this.compressType = compressType; + this.compressor = compressor; } get(key) { @@ -44,6 +58,12 @@ class Static extends Place { this.files.delete(key); } + compress(filePath) { + const fileStream = node.fs.createReadStream(filePath); + fileStream.pipe(this.compressor); + return node.streamConsumers.buffer(this.compressor); + } + async change(filePath) { if (this.maxFileSize === -1) { const maxFileSize = this.application.config?.cache?.maxFileSize; @@ -58,7 +78,13 @@ class Static extends Place { if (stat.size > this.maxFileSize) { this.files.set(key, { data: null, stat }); } else { - const data = await node.fsp.readFile(filePath); + let data = null; + if (this.compressor) { + data = await this.compress(filePath); + stat.size = data.length; + } else { + data = await node.fsp.readFile(filePath); + } this.files.set(key, { data, stat }); } } catch { @@ -98,7 +124,9 @@ class Static extends Place { let file = this.find(filePath); if (file.data && file.stat) { if (file.code === -1) return void transport.write(file.data, 200, 'html'); - return void transport.write(file.data, file.code, fileExt); + return void transport.write(file.data, file.code, fileExt, { + contentEncoding: this.compressType, + }); } const absPath = join(this.path, url); if (absPath.startsWith(this.path)) { @@ -106,7 +134,7 @@ class Static extends Place { if (!stat) stat = await node.fsp.stat(absPath).catch(() => null); if (stat && stat.isFile()) { const { size } = stat; - const options = { size }; + const options = { size, contentEncoding: this.compressType }; let code = 200; const { headers } = transport.req; if (headers.range) { diff --git a/test/static.js b/test/static.js index 7d3373f8c..224783a5b 100644 --- a/test/static.js +++ b/test/static.js @@ -32,3 +32,89 @@ test('lib/static load - should load static files correctly', async () => { assert.strictEqual(cache.ext, undefined); assert.strictEqual(cache.maxFileSize, 10000000); }); + +test('lib/static load - should compress correctly by gzip', async () => { + const cache = new Static('lib', application, { compressType: 'gzip' }); + assert.strictEqual(cache.files instanceof Map, true); + assert.strictEqual(cache.files.size, 0); + assert.strictEqual(cache.ext, undefined); + assert.strictEqual(cache.maxFileSize, -1); + assert.strictEqual(cache.get('/example/add.js'), undefined); + + await cache.load(); + assert.strictEqual(cache.files.size, 13); + const file = cache.get('/example/add.js'); + assert.strictEqual(file.data instanceof Buffer, true); + assert.strictEqual(file.data.length, 116); + assert.strictEqual(cache.get('/example/unknown.js'), undefined); + assert.strictEqual(cache.ext, undefined); + assert.strictEqual(cache.maxFileSize, 10000000); +}); + +test('lib/static load - should compress correctly by deflate', async () => { + const cache = new Static('lib', application, { + compressType: 'deflate', + }); + assert.strictEqual(cache.files instanceof Map, true); + assert.strictEqual(cache.files.size, 0); + assert.strictEqual(cache.ext, undefined); + assert.strictEqual(cache.maxFileSize, -1); + assert.strictEqual(cache.get('/example/add.js'), undefined); + + await cache.load(); + assert.strictEqual(cache.files.size, 13); + const file = cache.get('/example/add.js'); + assert.strictEqual(file.data instanceof Buffer, true); + assert.strictEqual(file.data.length, 104); + assert.strictEqual(cache.get('/example/unknown.js'), undefined); + assert.strictEqual(cache.ext, undefined); + assert.strictEqual(cache.maxFileSize, 10000000); +}); + +test('lib/static load - should compress correctly by brotli', async () => { + const cache = new Static('lib', application, { + compressType: 'br', + }); + assert.strictEqual(cache.files instanceof Map, true); + assert.strictEqual(cache.files.size, 0); + assert.strictEqual(cache.ext, undefined); + assert.strictEqual(cache.maxFileSize, -1); + assert.strictEqual(cache.get('/example/add.js'), undefined); + + await cache.load(); + assert.strictEqual(cache.files.size, 13); + const file = cache.get('/example/add.js'); + assert.strictEqual(file.data instanceof Buffer, true); + assert.strictEqual(file.data.length, 100); + assert.strictEqual(cache.get('/example/unknown.js'), undefined); + assert.strictEqual(cache.ext, undefined); + assert.strictEqual(cache.maxFileSize, 10000000); +}); + +test('lib/static load - should compress correctly by zstd', async () => { + const cache = new Static('lib', application, { + compressType: 'zstd', + }); + assert.strictEqual(cache.files instanceof Map, true); + assert.strictEqual(cache.files.size, 0); + assert.strictEqual(cache.ext, undefined); + assert.strictEqual(cache.maxFileSize, -1); + assert.strictEqual(cache.get('/example/add.js'), undefined); + + await cache.load(); + assert.strictEqual(cache.files.size, 13); + const file = cache.get('/example/add.js'); + assert.strictEqual(file.data instanceof Buffer, true); + assert.strictEqual(file.data.length, 109); + assert.strictEqual(cache.get('/example/unknown.js'), undefined); + assert.strictEqual(cache.ext, undefined); + assert.strictEqual(cache.maxFileSize, 10000000); +}); + +test('lib/static - should throw error on unsupported compression', async () => { + assert.throws(() => { + new Static('lib', application, { + compressType: 'unsupported', + }); + }, new Error('Unsupported compression type unsupported')); +}); diff --git a/types/core.d.ts b/types/core.d.ts index 6a8af0339..f05bd71b8 100644 --- a/types/core.d.ts +++ b/types/core.d.ts @@ -20,6 +20,7 @@ export interface InvokeTarget { export interface Static { get(name: string): unknown; + compress(filePath: string): Promise; } export interface Schemas { diff --git a/types/impress.d.ts b/types/impress.d.ts index 20bd8173e..cbdc37402 100644 --- a/types/impress.d.ts +++ b/types/impress.d.ts @@ -14,6 +14,7 @@ import * as _qs from 'node:querystring'; import * as _querystring from 'node:querystring'; import * as _assert from 'node:assert'; import * as _stream from 'node:stream'; +import * as _streamConsumers from 'node:stream/consumers'; import * as _fs from 'node:fs'; import * as _crypto from 'node:crypto'; import * as _zlib from 'node:zlib'; @@ -77,6 +78,8 @@ declare global { const querystring: typeof _qs; const assert: typeof _assert; const stream: typeof _stream; + const stream_consumers: typeof _streamConsumers; + const streamConsumers: typeof _streamConsumers; const fs: typeof _fs; const fsp: typeof _fs.promises; const crypto: typeof _crypto; From 980f4b2697b7dadc155c65805b78beec9a73b848 Mon Sep 17 00:00:00 2001 From: Timur Shemsedinov Date: Thu, 26 Feb 2026 20:25:28 +0200 Subject: [PATCH 2/5] Apply suggestions from code review --- lib/static.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/lib/static.js b/lib/static.js index 5323b842b..bfc7a4549 100644 --- a/lib/static.js +++ b/lib/static.js @@ -124,9 +124,8 @@ class Static extends Place { let file = this.find(filePath); if (file.data && file.stat) { if (file.code === -1) return void transport.write(file.data, 200, 'html'); - return void transport.write(file.data, file.code, fileExt, { - contentEncoding: this.compressType, - }); + const opt = { contentEncoding: this.compressType }; + return void transport.write(file.data, file.code, fileExt, opt); } const absPath = join(this.path, url); if (absPath.startsWith(this.path)) { From 85db0a5ba3ae6b5c8d1ea27f914bf6efbe036d7b Mon Sep 17 00:00:00 2001 From: Oleksandr Date: Wed, 25 Mar 2026 18:21:10 +0100 Subject: [PATCH 3/5] fix tests, if zstd not support for older nodejs versions --- test/static.js | 39 +++++++++++++++++++++------------------ 1 file changed, 21 insertions(+), 18 deletions(-) diff --git a/test/static.js b/test/static.js index 224783a5b..0d9458bfa 100644 --- a/test/static.js +++ b/test/static.js @@ -2,6 +2,7 @@ const { test } = require('node:test'); const assert = require('node:assert'); +const zlib = require('node:zlib'); const path = require('node:path'); const { Static } = require('../lib/static.js'); @@ -91,25 +92,27 @@ test('lib/static load - should compress correctly by brotli', async () => { assert.strictEqual(cache.maxFileSize, 10000000); }); -test('lib/static load - should compress correctly by zstd', async () => { - const cache = new Static('lib', application, { - compressType: 'zstd', - }); - assert.strictEqual(cache.files instanceof Map, true); - assert.strictEqual(cache.files.size, 0); - assert.strictEqual(cache.ext, undefined); - assert.strictEqual(cache.maxFileSize, -1); - assert.strictEqual(cache.get('/example/add.js'), undefined); +if (zlib.zstdCompress) { + test('lib/static load - should compress correctly by zstd', async () => { + const cache = new Static('lib', application, { + compressType: 'zstd', + }); + assert.strictEqual(cache.files instanceof Map, true); + assert.strictEqual(cache.files.size, 0); + assert.strictEqual(cache.ext, undefined); + assert.strictEqual(cache.maxFileSize, -1); + assert.strictEqual(cache.get('/example/add.js'), undefined); - await cache.load(); - assert.strictEqual(cache.files.size, 13); - const file = cache.get('/example/add.js'); - assert.strictEqual(file.data instanceof Buffer, true); - assert.strictEqual(file.data.length, 109); - assert.strictEqual(cache.get('/example/unknown.js'), undefined); - assert.strictEqual(cache.ext, undefined); - assert.strictEqual(cache.maxFileSize, 10000000); -}); + await cache.load(); + assert.strictEqual(cache.files.size, 13); + const file = cache.get('/example/add.js'); + assert.strictEqual(file.data instanceof Buffer, true); + assert.strictEqual(file.data.length, 109); + assert.strictEqual(cache.get('/example/unknown.js'), undefined); + assert.strictEqual(cache.ext, undefined); + assert.strictEqual(cache.maxFileSize, 10000000); + }); +} test('lib/static - should throw error on unsupported compression', async () => { assert.throws(() => { From 134a2ad06acf3c9838803d2f1e7f79ab5d8c7e03 Mon Sep 17 00:00:00 2001 From: Timur Shemsedinov Date: Sat, 4 Apr 2026 23:29:41 +0300 Subject: [PATCH 4/5] Apply suggestions from code review Co-authored-by: Timur Shemsedinov --- lib/static.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/static.js b/lib/static.js index cdc5f60a4..e8c47060f 100644 --- a/lib/static.js +++ b/lib/static.js @@ -181,7 +181,9 @@ class Static extends Place { const fileExt = metautil.fileExt(filePath); let file = this.find(filePath); if (file.data && file.stat) { - if (file.code === -1) return void this.write(req, res, file.data, 200, 'html'); + if (file.code === -1) { + return void this.write(req, res, file.data, 200, 'html'); + } const opt = { contentEncoding: this.compressType }; return void this.write(req, res, file.data, file.code, fileExt, opt); } From 959ea27bda96271dbe0f4face7dc0c17b5b02905 Mon Sep 17 00:00:00 2001 From: Timur Shemsedinov Date: Sat, 4 Apr 2026 23:50:00 +0300 Subject: [PATCH 5/5] Apply suggestions from code review Co-authored-by: Timur Shemsedinov --- lib/static.js | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/lib/static.js b/lib/static.js index e8c47060f..bc24825d3 100644 --- a/lib/static.js +++ b/lib/static.js @@ -86,18 +86,23 @@ const COMPRESSORS = { }; class Static extends Place { + files = new Map(); + compressor = null; + compressType = 'none'; + maxFileSize = -1; + constructor(name, application, options = {}) { super(name, application); - this.files = new Map(); - this.ext = options.ext; - this.maxFileSize = -1; - const { compressType } = options; - const compressor = COMPRESSORS[compressType]?.(); - if (compressType && !compressor) { - throw new Error(`Unsupported compression type ${compressType}`); + const { compressType, ext } = options; + this.ext = ext; + if (compressType) { + const compressor = COMPRESSORS[compressType]; + if (!compressor) { + throw new Error(`Unsupported compression type ${compressType}`); + } + this.compressType = compressType; + this.compressor = compressor(); } - this.compressType = compressType; - this.compressor = compressor; } get(key) {