Минимизация сборки для уменьшения размера бандла

Stanford JS Crypto Library изначально проектировалась как универсальная криптографическая библиотека с акцентом на полноту алгоритмов и совместимость. Это приводит к тому, что при стандартном подключении в проект попадает значительно больше кода, чем требуется конкретному приложению.

Основные факторы разрастания:

  • подключение всех криптографических примитивов по умолчанию
  • отсутствие агрессивного tree-shaking в исходной архитектуре
  • дублирование вспомогательных функций между модулями
  • наличие кодовых путей для редких алгоритмов (PBKDF2, CCM, OCB, ecc, ecc)

В результате минимальный импорт часто тянет за собой десятки килобайт лишнего JavaScript-кода, который никогда не выполняется в рантайме.


Структура SJCL с точки зрения сборки

Библиотека организована как набор модулей внутри глобального объекта sjcl, где каждый компонент регистрируется через расширение пространства имён.

Типичная структура:

  • sjcl.core — базовые утилиты и математические операции
  • sjcl.cipher — AES и режимы шифрования
  • sjcl.hash — SHA-256, SHA-512 и др.
  • sjcl.misc — вспомогательные функции (bitArray, codec)
  • sjcl.keyexchange — Diffie-Hellman и ECDH
  • sjcl.prng — генератор случайных чисел

Особенность архитектуры заключается в том, что модули не являются ESM или CommonJS в современном смысле. Это влияет на работу tree-shaking в Webpack/Rollup.


Проблема отсутствия tree-shaking

Современные сборщики ориентируются на статический анализ импортов. SJCL же:

  • модифицирует глобальный объект
  • использует побочные эффекты при подключении файлов
  • не экспортирует функции как независимые сущности

Из-за этого:

  • Webpack не может безопасно удалить неиспользуемые части
  • Rollup теряет гранулярность модулей
  • Terser работает только на уровне минификации, но не удаления кода

Даже при импорте только AES в сборку часто попадает hash, prng и codec.


Минимизация через точечные импорты

Наиболее эффективный подход — использование исходных файлов SJCL напрямую, без подключения всей библиотеки.

Пример структуры:

import './sjcl/core.js';
import './sjcl/cipher/aes.js';
import './sjcl/misc/bitArray.js';

Ключевой принцип — импортировать только те файлы, которые реально используются.

Ограничения подхода:

  • необходимо вручную отслеживать зависимости
  • порядок подключения имеет значение
  • возможны скрытые зависимости внутри модулей

Удаление неиспользуемых алгоритмов

На практике наибольший выигрыш даёт исключение следующих компонентов:

  • ECC (эллиптическая криптография)
  • PBKDF2 (если не используется derivation)
  • CCM/OCB режимы (если достаточно CBC/CTR)
  • альтернативные кодеки base64/hex, если используется только один формат

Ручное удаление достигается через кастомную сборку или патчинг исходников:

// удаление лишних модулей перед сборкой
delete sjcl.ecc;
delete sjcl.keyexchange;

Однако такой подход нестабилен и применяется только в специфических сборках.


Использование Webpack и ограничение tree-shaking

Webpack в случае SJCL работает преимущественно как транспилятор и бандлер, а не как оптимизатор структуры кода.

Типичная проблема:

import sjcl from 'sjcl';

Такой импорт гарантирует попадание всей библиотеки в бандл.

Более эффективный вариант:

import sjcl from './vendor/sjcl/core.js';
import './vendor/sjcl/cipher/aes.js';

Даже в этом случае Webpack сохраняет побочные эффекты, если не указать sideEffects: false в package.json, что для SJCL обычно невозможно без риска поломки логики.


Настройка sideEffects и ограничения

Попытка включить агрессивное удаление кода:

{
  "sideEffects": false
}

Для SJCL часто приводит к:

  • удалению инициализации PRNG
  • некорректной работе cipher модулей
  • ошибкам при генерации ключей

Причина — модули рассчитывают на выполнение кода при загрузке, а не на чистые экспорты.


Кастомная сборка SJCL

Наиболее эффективный подход — сборка собственной версии библиотеки.

Структура:

sjcl-custom/
  core.js
  aes.js
  bitArray.js
  sha256.js

Скрипт сборки (пример с Rollup):

export default {
  input: 'sjcl-custom/index.js',
  output: {
    file: 'dist/sjcl.min.js',
    format: 'iife',
    name: 'sjcl'
  },
  plugins: []
};

index.js:

import './core.js';
import './aes.js';
import './bitArray.js';
import './sha256.js';

Такой подход даёт полный контроль над итоговым размером бандла.


Минификация через Terser и Closure Compiler

Terser

Terser эффективно удаляет:

  • мёртвый код внутри функций
  • неиспользуемые переменные
  • дублирующиеся выражения

Ограничение — он не понимает архитектуру SJCL, поэтому не удаляет целые модули.


Closure Compiler (advanced mode)

Closure Compiler даёт более глубокую оптимизацию:

  • агрессивное удаление unreachable-кода
  • инлайнинг функций
  • переименование namespace

Однако SJCL требует адаптации:

  • запрет на ADVANCED_OPTIMIZATIONS без проверки
  • необходимость аннотаций JSDoc
  • контроль побочных эффектов

Оптимизация криптографических примитивов

Наиболее значительное уменьшение размера достигается за счёт выбора алгоритмов.

Пример минимального набора:

  • AES-128
  • SHA-256
  • HMAC

Исключение:

  • SHA-512
  • PBKDF2 с большим числом итераций
  • ECC

Такой набор покрывает большинство прикладных задач (шифрование токенов, локальное хранение секретов).


Разделение сборок (build splitting)

Практика разделения криптографии на отдельный chunk:

crypto.chunk.js
app.chunk.js
vendor.chunk.js

Используется динамический импорт:

const sjcl = await import('./crypto.chunk.js');

Преимущества:

  • криптографический код не попадает в initial bundle
  • возможна ленивую загрузка
  • упрощается кэширование

Сжатие gzip и brotli как обязательный этап

После минимизации критично использовать:

  • gzip (уровень 6–9)
  • brotli (уровень 11)

SJCL хорошо сжимается из-за:

  • повторяющихся структур битовых операций
  • однотипных функций в cipher модулях
  • длинных повторяющихся идентификаторов

Типичный эффект:

  • 200–300 KB JS → 60–90 KB gzip
  • 60–90 KB gzip → 45–70 KB brotli

Оптимизация через alias и path resolution

Webpack alias позволяет исключить лишние части:

resolve: {
  alias: {
    sjcl: path.resolve('./sjcl-custom/minimal.js')
  }
}

Такой подход фиксирует использование строго контролируемой версии библиотеки и предотвращает случайное расширение зависимостей.


Практическая стратегия минимизации

Наиболее стабильная схема уменьшения размера:

  • переход на кастомную сборку SJCL
  • исключение ECC и PBKDF2
  • точечные импорты вместо глобального sjcl
  • отключение ненужных кодеков
  • использование Terser после tree-shaking
  • применение Brotli на финальном этапе

Результат выражается не только в уменьшении размера бандла, но и в снижении времени инициализации криптографических операций в браузере.