Files
2026-05-27 23:29:53 +01:00

6.4 KiB

OpenSpec Change: enterprise-compress-svg

  • Change ID: enterprise-compress-svg
  • Package: @suaveplan/compress-svg
  • Category: universal
  • Target path: universal/compression/compress-svg
  • Status: PROPOSED
  • Date: 2026-04-24
  • Reference spec: /mnt/s/web/suaveplan/enterprise/compress-svg.md

Summary

Deliver @suaveplan/compress-svg on the genesis branch from scratch at enterprise quality. The package provides SVG optimisation and minification: whitespace removal, redundant attribute stripping, path data precision reduction, element collapsing, ID mangling, viewBox normalisation, inline style to attribute conversion, and SVGO-compatible plugin pipeline. Pure TypeScript, runs in Bun/Node/browser, operates on SVG string input/output.


Current-state audit (genesis)

The package does not exist on genesis yet — this is a fresh build. No prior source, no test results, no coverage data. The main-branch package had 99.31% coverage.


Available workspace deps on genesis

Package Role
@suaveplan/error (workspace:^) Base class for SvgCompressError; SVG_COMPRESS.* codes
@suaveplan/types (workspace:^) LoggerLike
zod (catalog:forms) Zod schemas for all public option types
@opentelemetry/api (catalog:observability) Spans + counters

Dev deps: vitest, @vitest/coverage-istanbul, fast-check, mitata (all catalog:testing).


Public API

Core types

class SvgCompressError extends SuaveplanError

interface SvgPlugin {
  name: string
  apply(node: SvgNode, options: SvgPluginOptions): SvgNode | null
}

interface SvgNode {
  type: 'element' | 'text' | 'comment' | 'cdata' | 'doctype'
  name?: string
  attributes?: Record<string, string>
  children?: SvgNode[]
  value?: string
}

interface CompressOptions {
  plugins?: SvgPlugin[]          // default: standard plugin set
  floatPrecision?: number        // decimal places for path coords; default: 3
  transformPrecision?: number    // default: 5
  multipass?: boolean            // default: true; run until stable
  removeComments?: boolean       // default: true
  removeMetadata?: boolean       // default: true
  removeDoctype?: boolean        // default: true
  collapseGroups?: boolean       // default: true
  mergeStyles?: boolean          // default: true
  mangleIds?: boolean            // default: false
}

interface CompressResult {
  data: string                   // optimised SVG string
  originalSize: number           // bytes
  compressedSize: number         // bytes
  ratio: number                  // compressedSize / originalSize
  passCount: number              // multipass iterations used
}

function compressSvg(svg: string, options?: CompressOptions): CompressResult
function parseSvg(svg: string): SvgNode
function serializeSvg(node: SvgNode): string

// Built-in plugins (each independently importable)
const removeComments: SvgPlugin
const removeMetadata: SvgPlugin
const removeDoctype: SvgPlugin
const collapseGroups: SvgPlugin
const cleanupAttrs: SvgPlugin
const convertColors: SvgPlugin
const convertPathData: SvgPlugin
const convertTransforms: SvgPlugin
const mergeStyles: SvgPlugin
const inlineStyles: SvgPlugin
const removeUnusedDefs: SvgPlugin
const mangleIds: SvgPlugin

Presets

const PRESET_DEFAULT: SvgPlugin[]    // removeComments + metadata + doctype + collapseGroups + cleanupAttrs + convertPathData
const PRESET_SAFE: SvgPlugin[]       // PRESET_DEFAULT minus id-mangling and transform conversion
const PRESET_AGGRESSIVE: SvgPlugin[] // all plugins enabled

Error codes

type SvgCompressErrorCode =
  | 'SVG_COMPRESS_PARSE_FAILED'
  | 'SVG_COMPRESS_INVALID_SVG'
  | 'SVG_COMPRESS_PLUGIN_FAILED'

const SVG_COMPRESS_ERROR_METADATA: Readonly<Record<SvgCompressErrorCode, CodeMetadata>>
function registerSvgCompressErrorCodes(): void

Zod schemas

CompressOptionsSchema
CompressResultSchema

New additions for genesis

  1. Streaming-safe parser — lightweight push-parser (no DOMParser dependency) operating on string; SAX-like for large SVGs.
  2. Plugin pipeline — each plugin receives and returns an SvgNode tree; returning null removes the node. Plugins compose cleanly via standard array.
  3. Multipass loop — runs plugins until the serialised output reaches a stable size (or multipass: false).
  4. OTel instrumentation — span svg.compress, counter svg_compress_total, histogram svg_compress_duration_ms + svg_compress_ratio.
  5. Property-based tests — compression idempotency (compress twice = same output), parseSvg ↔ serializeSvg round-trip, ratio always ≤ 1 for any valid SVG.
  6. Benchmark suite — compressSvg on 10 kB SVG icon, 100 kB complex illustration, 1 MB map SVG.

File layout

universal/compression/compress-svg/
  package.json
  tsconfig.json
  vitest.config.ts
  src/
    index.ts
    error-codes.ts
    constants/
      index.ts
    types/
      common.ts
      common.md
    core/
      compress-svg/
        compress-svg.ts
        compress-svg.md
        compress-svg.types.ts
        compress-svg.test.ts
        index.ts
    parser/
      svg-parser/
        svg-parser.ts
        svg-parser.md
        svg-parser.test.ts
        index.ts
    plugins/
      remove-comments/
        remove-comments.ts
        remove-comments.md
        remove-comments.test.ts
        index.ts
      remove-metadata/
        remove-metadata.ts
        remove-metadata.md
        remove-metadata.test.ts
        index.ts
      collapse-groups/
        collapse-groups.ts
        collapse-groups.md
        collapse-groups.test.ts
        index.ts
      cleanup-attrs/
        cleanup-attrs.ts
        cleanup-attrs.md
        cleanup-attrs.test.ts
        index.ts
      convert-path-data/
        convert-path-data.ts
        convert-path-data.md
        convert-path-data.test.ts
        index.ts
      convert-transforms/
        convert-transforms.ts
        convert-transforms.md
        convert-transforms.test.ts
        index.ts
      mangle-ids/
        mangle-ids.ts
        mangle-ids.md
        mangle-ids.test.ts
        index.ts
    presets/
      index.ts
    __benchmarks__/
      compress-svg.bench.ts
  tests/
    integration.test.ts
    property.test.ts

Impact

Downstream: build pipeline tools, @suaveplan/packer, any SVG-heavy asset pipelines. Breaking changes: none — fresh build. Prerequisites: @suaveplan/error, @suaveplan/types published to Verdaccio.