From 7d3a227401f6b7c5b5164c10e04514829bd747e3 Mon Sep 17 00:00:00 2001 From: Kiko Beats Date: Tue, 8 Sep 2026 14:35:57 +0000 Subject: [PATCH 1/2] refactor(function): compress via CompressionStream Prefer native brotli and fall back to gzip so Chrome works without shipping lz-ts. Co-authored-by: Cursor --- packages/function/README.md | 4 +-- packages/function/package.json | 3 +- packages/function/src/index.js | 28 +++++++++------- packages/function/test/compress.mjs | 51 ++++++++++++++++------------- 4 files changed, 49 insertions(+), 37 deletions(-) diff --git a/packages/function/README.md b/packages/function/README.md index 986f7e6..ebb99eb 100644 --- a/packages/function/README.md +++ b/packages/function/README.md @@ -284,10 +284,10 @@ If you still need the rendered markup, call `page.content()` inside the function ```js const compressed = await microlink.compress(({ page }) => page.title()) -// 'br#...' on Node.js (brotli), 'lz#...' as fallback +// 'br#...' when brotli is available, 'gz#...' in Chrome ``` -Supported prefixes: `lz#` (lz-string), `br#` (brotli), `gz#` (gzip). +Supported prefixes: `br#` (brotli), `gz#` (gzip). ### Optimization checklist diff --git a/packages/function/package.json b/packages/function/package.json index 8d6efa4..0882652 100644 --- a/packages/function/package.json +++ b/packages/function/package.json @@ -34,8 +34,7 @@ "serverless" ], "dependencies": { - "@microlink/mql": "workspace:*", - "lz-ts": "~1.1.2" + "@microlink/mql": "workspace:*" }, "devDependencies": { "@rollup/plugin-commonjs": "latest", diff --git a/packages/function/src/index.js b/packages/function/src/index.js index 6aaf02b..2a05a2f 100644 --- a/packages/function/src/index.js +++ b/packages/function/src/index.js @@ -2,17 +2,23 @@ const mql = require('@microlink/mql') -let toCompress - -try { - const { promisify } = require('util') - const { brotliCompress } = require('zlib') - const compress = promisify(brotliCompress) - toCompress = async code => - `br#${(await compress(code.toString())).toString('base64url')}` -} catch { - const { compressToURI } = require('lz-ts') - toCompress = code => Promise.resolve(`lz#${compressToURI(code.toString())}`) +const format = (() => { + try { + // eslint-disable-next-line no-new + new CompressionStream('brotli') + return 'brotli' + } catch { + return 'gzip' + } +})() + +const toCompress = async code => { + const stream = new Blob([code.toString()]) + .stream() + .pipeThrough(new CompressionStream(format)) + const bytes = new Uint8Array(await new Response(stream).arrayBuffer()) + const alias = format === 'brotli' ? 'br' : 'gz' + return `${alias}#${bytes.toBase64({ alphabet: 'base64url' })}` } const fn = (code, mqlOpts, gotOpts) => { diff --git a/packages/function/test/compress.mjs b/packages/function/test/compress.mjs index 19e389a..68ca73b 100644 --- a/packages/function/test/compress.mjs +++ b/packages/function/test/compress.mjs @@ -1,20 +1,20 @@ -import { brotliDecompress } from 'zlib' +import { brotliDecompress, gunzip } from 'zlib' import { createRequire } from 'module' import { promisify } from 'util' import test from 'ava' const require = createRequire(import.meta.url) -const { decompressFromURI, compressToURI } = require('lz-ts') const fn = require('@microlink/function') const decompress = promisify(brotliDecompress) +const unzip = promisify(gunzip) const code = '({ page }) => page.title()' test('compress is exported', t => { t.is(typeof fn.compress, 'function') }) -test('node.js selects brotli compression', async t => { +test('selects brotli when CompressionStream supports it', async t => { const compressed = await fn.compress(code) t.true(compressed.startsWith('br#')) }) @@ -22,28 +22,35 @@ test('node.js selects brotli compression', async t => { test('brotli roundtrip produces original code', async t => { const compressed = await fn.compress(code) const payload = compressed.slice(3) - const decompressed = (await decompress(Buffer.from(payload, 'base64url'))).toString() + const decompressed = ( + await decompress(Buffer.from(payload, 'base64url')) + ).toString() t.is(decompressed, code) }) -test('falls back to lz-string when zlib is unavailable', async t => { - const { execFileSync } = await import('child_process') - const result = execFileSync(process.execPath, ['-e', ` - const Module = require('module') - const originalLoad = Module._load - Module._load = function (id, parent, isMain) { - if (id === 'zlib') throw new Error('not available') - return originalLoad(id, parent, isMain) +test('falls back to gzip when brotli is unavailable', async t => { + const { execFileSync } = require('child_process') + const result = execFileSync( + process.execPath, + [ + '-e', + ` + const OriginalCS = globalThis.CompressionStream + globalThis.CompressionStream = class { + constructor (format) { + if (format === 'brotli') throw new TypeError('unsupported') + return new OriginalCS(format) + } } const fn = require(${JSON.stringify(require.resolve('@microlink/function'))}) - fn.compress('({ page }) => page.title()').then(r => process.stdout.write(r)) - `], { encoding: 'utf8' }) - t.true(result.startsWith('lz#')) -}) - -test('lz roundtrip produces original code', t => { - const compressed = `lz#${compressToURI(code)}` - t.true(compressed.startsWith('lz#')) - const payload = compressed.slice(3) - t.is(decompressFromURI(payload), code) + fn.compress(${JSON.stringify(code)}).then(r => process.stdout.write(r)) + ` + ], + { encoding: 'utf8' } + ) + t.true(result.startsWith('gz#')) + const decompressed = ( + await unzip(Buffer.from(result.slice(3), 'base64url')) + ).toString() + t.is(decompressed, code) }) From 1ac210a5d88339f7281d1e73e74f93226aa7dd72 Mon Sep 17 00:00:00 2001 From: Kiko Beats Date: Tue, 8 Sep 2026 14:42:23 +0000 Subject: [PATCH 2/2] fix(function): encode base64url without toBase64 Uint8Array#toBase64 is not on Node 24, which CI and engines allow. Co-authored-by: Cursor --- packages/function/src/index.js | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/function/src/index.js b/packages/function/src/index.js index 2a05a2f..cccc7cc 100644 --- a/packages/function/src/index.js +++ b/packages/function/src/index.js @@ -12,13 +12,20 @@ const format = (() => { } })() +const toBase64url = bytes => { + if (typeof Buffer === 'function') { return Buffer.from(bytes).toString('base64url') } + let binary = '' + for (const byte of bytes) binary += String.fromCharCode(byte) + return btoa(binary).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '') +} + const toCompress = async code => { const stream = new Blob([code.toString()]) .stream() .pipeThrough(new CompressionStream(format)) const bytes = new Uint8Array(await new Response(stream).arrayBuffer()) const alias = format === 'brotli' ? 'br' : 'gz' - return `${alias}#${bytes.toBase64({ alphabet: 'base64url' })}` + return `${alias}#${toBase64url(bytes)}` } const fn = (code, mqlOpts, gotOpts) => {