SVG optimizer in pure MoonBit: svgo-compatible plugin pipeline with path data optimization, colour and number cleanup, and render-diff verification
$ svgo testdata/sketch-icon.svg --stats
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><title>icon/action/close</title><g fill="red" transform="translate(2 2)"><path stroke="red" stroke-linecap="round" stroke-width="2" d="M4 4l12 12M16 4 4 16"/><path d="M0 0h20v20H0z"/></g></svg>
testdata/sketch-icon.svg: 836 -> 276 bytes (-67%), 3 pass(es), plugins: removeXMLProcInst, removeComments, ...npm i @rivus/svgoimport { optimize } from "@rivus/svgo";
const r = await optimize(svg, { precision: 3 });
r.data; r.originalSize; r.size; r.passes; r.applied;npx @rivus/svgo input.svg -o out.svg
cat input.svg | npx @rivus/svgo > out.svg # stdin, or pass "-" as the input
npx @rivus/svgo icons -r -o dist # a directory, recursively
# installed (npm i -g @rivus/svgo) the command is `svgo-mbt`moon build --target native --release # -> $(scripts/bin-path.sh), under _build/native/release/
svgo-mbt input.svg # optimized SVG on stdout
svgo-mbt input.svg -o out.svg --stats # write a file, print size statistics
svgo-mbt input.svg -p 2 --pretty # 2 decimal places, indented output
svgo-mbt input.svg --json # {data, originalSize, size, passes, applied}
svgo-mbt input.svg --plugins convertPathData,sortAttrs
svgo-mbt input.svg --param cleanupIds.preserve=logo,icon --param cleanupIds.minify=false
svgo-mbt input.svg --disable convertShapeToPath --enable moveGroupAttrsToElems
svgo-mbt --list # available pluginsmoon add PerfectPan/svgo///|
test "optimize" {
let result = @svgo.optimize(
(
#|<svg xmlns="http://www.w3.org/2000/svg" width="10px" height="10px">
#| <!-- Generator: Sketch -->
#| <g fill="#FF0000" stroke="none">
#| <rect x="0" y="0" width="10.000" height="10.000"/>
#| </g>
#|</svg>
),
)
inspect(
result.data,
content=(
#|<svg xmlns="http://www.w3.org/2000/svg" width="10" height="10"><path fill="red" d="M0 0h10v10H0z"/></svg>
),
)
}///|
test "config" {
let config = { ..@svgo.Config::default(), precision: 1, pretty: true, }
let r = @svgo.optimize(
"<svg xmlns=\"http://www.w3.org/2000/svg\"><path d=\"M0.123 0.456 L 10 10\"/></svg>",
config~,
)
inspect(
r.data,
content=(
#|<svg xmlns="http://www.w3.org/2000/svg">
#| <path d="M.1.5 10 10"/>
#|</svg>
#|
),
)
}| plugin | what it does |
|---|---|
| removeDoctype, removeXMLProcInst, removeComments, removeMetadata | strip the prolog and editor comments (<!--! ... --> legal comments are kept) |
| removeEditorsNSData | drop Inkscape / Sketch / Illustrator / Figma namespaces, elements and attributes |
| cleanupAttrs, removeDesc, cleanupIds, removeUselessDefs | normalise whitespace, drop Created with ... descriptions, unused ids and unreferenced <defs> children |
| cleanupNumericValues | round numbers, remove px |
| convertColors | rgb() / names / #RRGGBB to the shortest form |
| mergeStyles, inlineStyles, minifyStyles | merge <style> elements, inline matching rules to style="...", minify CSS and drop unused selectors |
| removeUnknownsAndDefaults | drop attributes equal to their defaults, unless an ancestor overrides them |
| removeDeprecatedAttrs | drop attributes the SVG spec deprecated, keeping the ones that still render |
| removeNonInheritableGroupAttrs | drop presentation attributes off a <g> that children cannot inherit |
| removeUselessStrokeAndFill | drop stroke-* when nothing is stroked, fill-* when nothing is filled |
| cleanupEnableBackground | drop enable-background unless a filter uses BackgroundImage |
| removeHiddenElems, removeEmptyText | zero-size shapes, display:none, empty paths and texts |
| convertShapeToPath, convertEllipseToCircle | rect / line / polyline / polygon to path, equal-radius ellipses to circles |
| moveElemsAttrsToGroup | lift an attribute every child shares onto their group |
| collapseGroups | unwrap groups and push their attributes down |
| convertPathData | element transforms baked into the data, curve runs to arcs, relative/absolute per segment, H/V/S/T shorthands, straight curves to lines, precision with drift compensation |
| convertTransform | multiply, decompose and round transform lists, drop identity and redundant ones |
| mergePaths | join adjacent paths with identical attributes when their outlines do not intersect |
| removeEmptyAttrs, removeEmptyContainers, removeUnusedNS, sortAttrs, sortDefsChildren | final cleanup |
| file | size | svgo.mbt (wasm-gc) | svgo-js | ratio |
|---|---|---|---|---|
| sketch-icon.svg | 836 B | 0.119 | 0.319 | 2.7× |
| inkscape-drawing.svg | 2 KB | 0.188 | 0.669 | 3.6× |
| SVG_logo.svg | 4 KB | 0.579 | 1.674 | 2.9× |
| Tux.svg | 50 KB | 7.092 | 14.238 | 2.0× |
| Ghostscript_Tiger.svg | 68 KB | 12.485 | 36.016 | 2.9× |
| World map (low resolution) | 85 KB | 11.889 | 51.867 | 4.4× |
scripts/verify.sh # check, fmt, .mbti drift, fixtures, tests on js / native
scripts/verify.sh --full # + wasm build, sizes vs svgo-js, resvg pixel diff, speed (needs pnpm install)
scripts/regress.sh <ref-dir> # byte-for-byte output comparison against a reference build| cases | |
|---|---|
| pass | 289 |
| known differences | 0 |
| skipped | 0 |
svgo/ the MoonBit module: xml/ path/ plugins/ (+ fixtures) svgo.mbt wasm/ benchmark/ testdata/
packages/svgo-mbt/ npm package: JS loader + svgo.wasm + the CLI bin
packages/compare/ svgo-js comparison, resvg render diff, same-process speed, site numbers
app/website/ the website: a MoonBit (Rabbita) app that imports svgo/, Tailwind v4, Remotion hero
app/cli/ the CLI: a MoonBit executable that imports svgo/, C file I/O on native
scripts/ build-wasm, build-cli, verify, bench, regress, gen-fixtures| Plugin | Parameters and defaults |
|---|---|
| cleanupIds | remove: true, minify: true, preserve: [], preservePrefixes: [], force: false |
| convertColors | currentColor: false, names2hex: true, rgb2hex: true, shorthex: true, shortname: true |
| convertShapeToPath | convertArcs: false, floatPrecision: config precision |
| convertTransform | floatPrecision: config precision (3), transformPrecision: 5 (limited to matrix precision), degPrecision: derived from matrix digits and float precision |
| removeComments | preservePatterns: ["^!"]; false or [] removes all comments |
| removeUnknownsAndDefaults | unknownContent, unknownAttrs, defaultAttrs, uselessOverrides, keepDataAttrs, keepAriaAttrs: all true; keepRoleAttr: false |
| removeUselessStrokeAndFill | stroke: true, fill: true, removeNone: false |
| sortAttrs | xmlnsOrder: "front"; order: ["id", "width", "height", "x", "x1", "x2", "y", "y1", "y2", "cx", "cy", "r", "fill", "stroke", "marker", "d", "points"] |
impl Show for UnknownPluginfn utf8_length(s : String) -> IntInstall
Download zipSVG optimizer in pure MoonBit: svgo-compatible plugin pipeline with path data optimization, colour and number cleanup, and render-diff verification