JavaScript
The same converter, compiled to WebAssembly: one function, a plain object back, and nothing that touches the network. It is what the demo on this site runs.
The binding
import init, { convert } from "./pkg/accent_draw_wasm.js"; await init(); const { xml, diagnostics } = convert(svgText);
init() fetches and instantiates the module; it has to finish before convert
is called. convert takes the SVG as a string and returns a plain JavaScript
object - values are built as objects directly, not serialised to JSON and parsed
back.
The result
{ xml: "<mxfile …>", // the draw.io document diagnostics: [ // in document order { code: "picture", message: "a <filter> is drawn as a picture" }, ], }
xml is the whole .drawio file, ready to save or to hand to draw.io.
diagnostics is what did not become a native cell: each entry's code is short
and stable, for matching on, and its message is for a person to read. An empty
array means the whole drawing became part of the diagram.
Errors
convert throws rather than returning an empty diagram:
try { const { xml } = convert(text); } catch (e) { // e.message is the same sentence the command line prints }
The message is one of: the file is not well-formed XML, its root element is not
<svg>, nothing in it could be drawn, or it carries a draw.io diagram that does
not read. "Nothing could be drawn" being an error is deliberate - a silently
empty diagram is worse than a message.
Building the package
wasm-pack build --target web --release crates/accent-draw-wasm
That writes crates/accent-draw-wasm/pkg/: the .wasm, the JavaScript that
loads it, and the type declarations. There is no published npm package yet.
The glue resolves accent_draw_wasm_bg.wasm relative to its own URL at runtime,
from inside a generated string. Nothing in a build pipeline can rewrite that, so
if your bundler renames assets to content-hashed names, exclude these two or the
module will fetch a file that is no longer there. It is why this site turns
fingerprinting off.
What it costs
The .wasm is about 850 KB, and around 350 KB gzipped, which is what a server
sends. It is fetched once. The size is measured on every change against a
budget, and the budget only moves as a decision with the measurement written
down beside it - a converter that quietly triples its first load is a converter
nobody puts in a page.
Where it runs
No filesystem, no clock, no randomness, no threads: the library is held to that
by a lint, which is what lets it compile for wasm32-unknown-unknown without
shims. The consequence worth relying on is that the browser and the command line
agree exactly. The whole corpus is converted through both on every change, and
the two outputs are compared byte for byte.
Nothing in the binding opens a socket. If your page sends an SVG somewhere, that is your page's doing, not this module's.
A page around it
The demo on this site is one, and the repository has a smaller one
under web/ - the drop target, the notes, the download, and the draw.io editor
in an iframe. What that page decides without a DOM - the download's file name,
the diagram's summary, where the editor may be loaded from - is a separate
module with its own tests, and the demo on this site imports that same file
rather than a copy of it.