Skip to content

tinywasm/svg

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

40 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

svg

SVG icon sprite API for TinyWasm — typed, zero-typo icon definitions, split so only the icon's name ever reaches the WASM binary.

Overview

Two packages, two audiences:

  • github.com/tinywasm/svg — the icon reference. type Icon string + Render(). Safe to import from ANY file, including one compiled into the browser bundle. This package never uses //go:build — it has nothing to hide from any target.
  • github.com/tinywasm/svg/sprite — the icon definition (paths, viewBox, <symbol> markup, JSON serialization). Only backend code should import it: your own svg.go tagged //go:build !wasm, plus tinywasm/ssr (SSR extractor) and assetmin, which need it unconditionally.

Why split by package instead of a build tag inside this library: tinywasm/ssr and assetmin are backend-only programs that need sprite construction at ALL times — a tag inside this library can't express "always needed by this one backend consumer." So the library stays untagged and minimal; you, the consumer, choose what to import and tag your own files accordingly.

Each project declares its own icons — no central icon package. Icons are project-specific.

Usage Pattern

1. Declare the reference in shared code — no build tag

This is the part that reaches the WASM binary: just a name.

// mycomponent.go — no build tag, compiles for browser AND backend
package mycomponent

import "github.com/tinywasm/svg"

const iconStar = svg.Icon("mycomponent-star") // just a string; nothing else ships to WASM

2. Draw the icon in YOUR OWN svg.go//go:build !wasm

This is the part that never reaches the WASM binary. You add the tag — this library does not and cannot add it for you.

//go:build !wasm
package mycomponent

import "github.com/tinywasm/svg/sprite"

func (c *MyComponent) IconSvg() *sprite.Sprite {
	return sprite.NewSprite(
		sprite.Define(iconStar, "0 0 16 16",
			sprite.Path("M8 1l2 5h5l-4 3 1.5 5L8 11l-4.5 3L5 9 1 6h5z"),
		),
	)
}

iconStar is the constant from step 1 — same package, different file, one identifier shared by definition and reference. A typo or rename breaks the build in both places at once.

You never call IconSvg() yourself. The tinywasm framework does it automatically as part of its build pipeline: tinywasm/ssr extracts it during SSR and assetmin merges every component's sprite into one block and injects it inline at the top of <body>. There is no /assets/icons.svg URL — href="#id" always resolves without a network request.

For how that pipeline runs (dev server, hot-reload, installation), see tinywasm/app — that's the framework entry point, not this library.

3. Reference the icon in Render() — same shared file as step 1

func (c *MyComponent) Render() *dom.Element {
	return dom.Div().Child(
		iconStar.Render(string(clsIcon)), // <svg aria-hidden focusable=false class=...><use href="#mycomponent-star"/></svg>
	)
}

clsIcon is a typed Class var from tinywasm/css — converted to string because Render accepts ...string. If clsIcon is renamed in CSS, the compiler warns here too.

API

svg package

type Icon string

A typed reference to a sprite symbol. Declare as a package-level const.

Icon.Render

func (i Icon) Render(classes ...string) *dom.Element

Emits the <svg><use> reference with the correct id and classes. The only public way to build that markup — there is no Svg()/Use() builder.

svg/sprite package

Define

func Define(icon svg.Icon, viewBox string, body ...node) Symbol

Binds geometry to a shared svg.Icon reference. viewBox is mandatory (no dangerous default that clips paths). body is one or more nodes (Path or Raw).

Path

func Path(d string) node

Renders <path fill="currentColor" d="<d>"/>. The fill is hardcoded — infallible, no one can forget it.

Raw

func Raw(s string) node

Escape hatch for markup that Path doesn't express: groups, circles, masks, gradients, etc. You decide where to use fill="currentColor" if you want theming.

NewSprite

func NewSprite(symbols ...Symbol) *Sprite

Constructs a *Sprite from typed definitions. The only way to build a sprite.

Color: currentColor

currentColor is not a color — it's a reference to the CSS color property of the consuming element.

The design requires the same icon to be colored differently in different contexts (e.g., secondary color in the nav, white when active). This works only with currentColor hardcoded in the symbol + color set by CSS at the use-site:

// In the component's CSS:
Rule(clsNavIcon, Color(tokenColorSecondary))                     // normal
Rule(Selector(".pd-nav-active svg"), Color(tokenColorPrimary))   // active → white

The icon definition is color-agnostic (always currentColor); theming centralizes in CSS tokens at the use-site. No icon can be "non-themeable."

The one thing this library cannot enforce — read this

svg/sprite is a plain Go package with no build tag of its own — it compiles fine for GOOS=js GOARCH=wasm too. Nothing stops you from importing it into a file reachable by the browser build by mistake; that mistake compiles successfully and silently ships path data + encoding/json into the WASM binary. There is no compiler error to catch it.

This manual check is now automated during development via svg/watch.LeakGuard, registered in tinywasm/app's hot-reload (see app/docs/PLAN_SVG_LEAK_GUARD.md) — the manual go list -deps command remains documented as the CI/pre-publish fallback for environments without the watcher running.

GOOS=js GOARCH=wasm go list -deps ./... | grep tinywasm/svg/sprite

Empty output = safe. Any output = some file without //go:build !wasm is importing svg/sprite — find it and tag it (or move the import into your tagged svg.go).

Internal

  • Sprite keeps its internal representation (iconEntry{id, content, viewBox}) and methods String(), MarshalJSON/UnmarshalJSON, Merge() unchanged — assetmin continues to inject Sprite.String() inline in <body> and serialize by JSON during SSR extraction. Define and NewSprite just construct that same data with compile-time safety.
  • Path(d) produces exactly <path fill="currentColor" d="..."> — the sprite result is byte-equivalent to manually-written markup.

About

Svg managment api for TinyWasm App

Resources

License

Stars

0 stars

Watchers

0 watching

Forks

Packages

 
 
 

Contributors

Languages