@svgsketch/core
v0.4.0
Published
Core types, document format, and rendering engine for SVGSketch
Downloads
192
Maintainers
Readme
@svgsketch/core
Core SDK, renderer, and document engine for SVGSketch. This package
defines the .svgs file format and provides everything needed to build,
serialize, load, validate, render, and migrate SVGSketch documents
programmatically — no browser or editor required.
Install
npm install @svgsketch/coreQuick start
import { Document, Circle, Rectangle } from '@svgsketch/core';
const doc = new Document({ width: 400, height: 300 })
.title('Hello')
.add(new Circle(200, 150, 80).fill('#2563eb'))
.add(new Rectangle(10, 10, 100, 60).fill('#ef4444').cornerRadius(8));
const svg = doc.toSVG(); // rendered SVG string
const svgs = doc.toJSON(); // canonical `.svgs` documentWhat it includes
| Module | Exports |
| ----------- | --------------------------------------------------------------- |
| types | HistorySnapshot, SerializedShape, SerializedSymbolDef, … |
| format | parseDocument, stringifyDocument, migrateSnapshot, validateSnapshot, substituteVariables |
| renderer | renderToSvg |
| sdk | Document, Circle, Rectangle, …, Timeline, Track |
| codegen | generateCode — SVG / React / Vue / D3 / CSS output |
The .svgs format
A .svgs file is a deterministic JSON serialization of a
HistorySnapshot — the complete state of an SVGSketch document. It is
designed for:
- Git-friendly diffs: canonical key ordering, one property per line.
- Round-trip fidelity: every editor-persisted field survives save → load → render without loss.
- External tooling: the schema is fully typed in
@svgsketch/coreand can be produced or consumed without running the editor.
Top-level structure
{
"schemaVersion": 1,
"documentMetadata": { /* title, author, license, … */ },
"templateVariables": [ /* { name, type, defaultValue, … } */ ],
"viewboxes": [ /* { id, x, y, width, height } */ ],
"guides": [ /* { id, orientation, position, … } */ ],
"measurements": [ /* dimensioning annotations */ ],
"groups": [ /* { id, parentId, siblingIndex, … } */ ],
"clipMaskGroups": [ /* <clipPath> / <mask> definitions */ ],
"customPatterns": [ /* user-defined fill patterns */ ],
"symbols": [ /* reusable component definitions */ ],
"animationTimeline": { /* tracks + keyframes */ },
"shapes": [ /* array — order IS z-order */ ]
}See src/types/serialized.ts for the
exhaustive schema.
Ordering rules
shapespreserves array order — the position of a shape in the array is its painter's-model z-order. Sorting would silently reorder rendering and destroy user intent.- Id-keyed collections (
groups,viewboxes,guides,clipMaskGroups,customPatterns,symbols) are sorted byid. Their order has no semantic meaning — groups useparentId+siblingIndexto record position. - Object keys within each shape are sorted alphabetically.
- Indentation is 2 spaces, one property per line, with a trailing newline by default.
These rules combine to give identical documents a byte-identical
serialization, so diffing .svgs files in git produces meaningful
line-level changes rather than noisy reorderings.
Schema versioning
schemaVersion is written on save and checked on load. The constant
CURRENT_SCHEMA_VERSION in @svgsketch/core is the latest version. A
document missing schemaVersion is treated as version 1.
On load, parseDocument calls migrateSnapshot which walks the
MIGRATIONS registry — each entry at key N transforms a version-N
snapshot into version-(N+1). Unknown older versions (no migration
registered) throw. Newer versions (from-the-future documents) pass
through unchanged; loaders should refuse them at a higher layer.
Compatibility rules for schema changes:
- Never rename an existing serialized property. Add a new one alongside and read both with a fallback.
- Never change a property's type. Add a new property instead.
- Never make a previously optional field required without a migration.
- Never remove a shape type — old documents referencing it would silently lose shapes.
- When adding new optional properties, always handle their absence on
the read side with
??orif-guards.
Template variables & bindings
Two complementary mechanisms for parameterizing documents:
{{variable}}references — string placeholders that are substituted at render time. Valid in any string property, and in numeric fields (e.g.radius: "{{r}}") where they resolve to numbers. Defaults live intemplateVariables; overrides can be passed tosubstituteVariables(snapshot, { r: '50' })or todoc.toSVG({ variables: { r: '50' } }).state.bindings— a map from a geometry property name to a CSS custom-property name (without the leading--). When a property is bound,state[property]holds the resolved value (what the renderer uses if no substitution runs), andbindingsrecords the variable the editor should re-bind to on the next edit. Example:{ "id": "c1", "type": "circle", "state": { "radius": 50, "bindings": { "radius": "card-radius" } } }
Round-trip fidelity
The format guarantees that for any valid snapshot s:
stringifyDocument(parseDocument(stringifyDocument(s))) === stringifyDocument(s);
renderToSvg(parseDocument(stringifyDocument(s))) === renderToSvg(s);The tests/format/roundtrip.test.ts integration suite enforces this
across a document that touches shapes, groups, symbols, template
variables, bindings, and animation tracks. When the format grows a new
field, that test must still pass — if it regresses, a field is being
dropped somewhere in the pipeline.
Validation
validateSnapshot(snapshot) returns { valid, errors, warnings } with
dot-paths (e.g. shapes[2].state.width) for each problem. Validation
runs automatically on parseDocument unless { validate: false } is
passed. Rules include:
- Shape IDs are unique within a document.
typeis a known shape type (circle,rectangle,spline, …) or a plugin-registered custom type.- Numeric geometry fields (
x,y,width,height,radius, …) are finite numbers — or{{variable}}references, which resolve to numbers at render time. - Viewbox dimensions are positive.
- Group references point to existing shapes.
Errors block loading; warnings are surfaced but do not.
Minimal example
import {
Document,
Circle,
parseDocument,
substituteVariables,
renderToSvg,
} from '@svgsketch/core';
// Build
const doc = new Document({ width: 200, height: 200 })
.add(new Circle(100, 100, 75).fill('{{accent}}').id('c1'))
.defineVariable('accent', 'color', '#3498db');
// Serialize
const svgs = doc.toJSON();
// Deserialize, substitute, render
const snapshot = parseDocument(svgs);
const resolved = substituteVariables(snapshot, { accent: '#e74c3c' });
const svg = renderToSvg(resolved);CLI
The companion @svgsketch/cli package exposes svgsketch validate,
svgsketch render, svgsketch info, svgsketch vars, and
svgsketch codegen commands that operate on .svgs files headlessly —
suitable for CI/CD pipelines.
