Сайт Романа ПарпалакаБлог20250701

Нативное gzip-сжатие в JS

1 июля 2025 года, 12:46

Я недавно закрыл тикет на гитхабе, который висел с 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 раз короче.

Поделиться

Разбираем конечный автомат в системе личных сообщений Ctrl

Читайте также

Воспринимайте слова в переписке буквально
В общении между людьми есть проблема недопонимания. Говорящий не всегда правильно подбирает слова, а слушающий додумывает.
2020
Переносим сессии при переезде между серверами
Как-то нам нужно было перенести сессии PHP с одного сервера на другой. Сессии хранились в файлах.
2020
Латех и веб-технологии
В прошлый раз я рассказал о своем сервисе, который генерирует для веба картинки с математическими формулами на латехе.
2014

Комментарии

#1. 1 июля 2025 года, 15:43. Евгений Степанищев пишет:
Можно, кстати, посмотреть более экономное кодирование вместо base64, но надо смотреть подойдут ли они для урлов:

https://en.wikipedia.org/wiki/Binary-to-text_encoding
#2. 2 июля 2025 года, 11:33. пишет:
А, кстати, я забыл об этом написать. Насколько я понимаю, в URL безопасными считаются только 66 символов. Остальные могут иметь специальное значение (слеш, двоеточие, знак вопроса и т. д.). Но даже если удалось бы использовать, скажем, base91 вместо base64, получили бы экономию в ln(91)/ln(64) ≈ 1,08 раз. Я посчитал, что она не стоит того, чтобы программировать какое-то нестандартное кодирование.

Оставьте свой комментарий


Формулы на латехе: $$f(x) = x^2-\sqrt{x}$$ превратится в $$f(x) = x^2-\sqrt{x}$$.
Выделение текста: [i]курсивом[/i] или [b]жирным[/b].
Цитату оформляйте так: [q = имя автора]цитата[/q] или [q]еще цитата[/q].
Других команд или HTML-тегов здесь нет.

Записи