Нативное gzip-сжатие в JS
Я недавно закрыл тикет на гитхабе, который висел с 2017 года. Его автор обращал внимание на длинные адреса картинок в моем сервисе математических формул. С 2023 года нативное сжатие произвольных данных в JS стало доступным во всех основных браузерах, и с его помощью я сделал вариант сжатых адресов.
Давние читатели вспомнят, что я уже рассказывал об адресах картинок, и даже упоминал об этом тикете. Повторю, что для использования в вебе формулы, например, $$a^2+b^2=c^2$$, её исходник на латехе a^2+b^2=c^2
кодируется через проценты (RFC 3986) и подставляется в URL:
//i.upmath.me/svg/a%5E2%2Bb%5E2%3Dc%5E2
Кодирование через символы процента очень неэкономное, поэтому и без того длинный код изображений и диаграмм становится ещё больше. Адрес $$a^2+b^2=c^2$$ в новой схеме выглядит так:
//i.upmath.me/svgb/S4wz0k6KM7JNjjMCAA
Здесь вместо кодирования через проценты используется сжатие deflate (тот же алгоритм, что и в gzip) и кодировка, аналогичная base64. Вот рабочий пример кода, который делает такое преобразование:
function deflateRaw(text, callback) {
if (typeof CompressionStream === 'undefined') {
callback(null);
return;
}
try {
var stream = new Blob([text]).stream();
var compressedStream = stream.pipeThrough(new CompressionStream('deflate-raw'));
new Response(compressedStream).blob().then(function (compressedBlob) {
return compressedBlob.arrayBuffer();
}).then(function (buffer) {
var compressedArray = new Uint8Array(buffer);
var binary = Array.from(compressedArray).map(function (b) {
return String.fromCharCode(b);
}).join('');
var base64 = btoa(binary).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
callback(base64);
}).catch(function () {
callback(null);
});
} catch (e) {
callback(null);
}
}
function getImgPath(formula, callback) {
var fallbackUrl = '//i.upmath.me/svg/' + encodeURIComponent(formula);
deflateRaw(formula, function (compressed) {
var shortUrl = compressed ? '//i.upmath.me/svgb/' + compressed : null;
callback(shortUrl && shortUrl.length < fallbackUrl.length ? shortUrl : fallbackUrl);
});
}
Важная особенность API браузеров по сжатию заключается в его асинхронности. Мы не можем получить результат сжатия в той же функции, в которой его инициируем. API возвращает promise, который «разрешится» позднее. Чтобы обеспечить обратную совместимость и откатываться к несжатым адресам в старых браузерах, я проверяю саму поддержку CompressionStream
и перехватываю возможные исключения. Также для обратной совместимости результат возвращаю через вызов пользовательского коллбэка, а не в виде промиса.
Вот пример того, как с вышеприведенным кодом создать картинку с формулой:
var node = document.createElement('img');
getImgPath('a^2+b^2=c^2', function(path) {
node.setAttribute('src', path);
});
Стоит отметить, что сам алгоритм сжатия deflate был давно портирован на JS, поэтому при необходимости можно было использовать сторонние библиотеки, например, pako. Кроме того, код библиотек работает синхронно, так что ни о каких коллбэках и промисах думать не нужно. В моём же случае я не хотел в
Оценим результат на примере диаграммы из предыдущей заметки. Длина старого несжатого URL равна 6,3 килобайт, а сжатого — 1,3 килобайт, что почти в 5 раз короче.
Комментарии
Оставьте свой комментарий