feature: Add SVG export support#20
Conversation
|
@Lojhan is attempting to deploy a commit to the Andre Silva Software's projects Team on Vercel. A member of the Team first needs to authorize it. |
andresilva-cc
left a comment
There was a problem hiding this comment.
Hey @Lojhan, thanks for the contribution — this is our first community PR and the code quality is genuinely impressive. The render.ts format handler refactor is clean and we'd like to keep that pattern regardless of how the SVG discussion goes.
That said, there are a few concerns we'd like to discuss before merging:
<foreignObject> compatibility
The approach wraps HTML inside an SVG <foreignObject>. This works well in Chrome and Firefox, but <foreignObject> is not supported by:
- Inkscape and most desktop SVG editors
- ImageMagick,
rsvg-convert, and common image converters - Social media OG scrapers (Twitter/X, LinkedIn, Slack) — which is one of Grafex's primary use cases
<img>tag embedding in HTML (blocks external resources inside<foreignObject>)
This means SVG files generated this way can't be used in many of the places people would expect SVG to work. We'd need to document this limitation very prominently, and it raises the question of whether this is the right approach for SVG in Grafex — or if we should pursue browser-based SVG generation (e.g., via page.pdf() → PDF-to-SVG conversion) which would produce more universally compatible output.
What are your thoughts? Did you evaluate other approaches?
Security: SVG is a distributable artifact
PNG output is consumed by a browser we control (WebKit) — the rendered content never leaves as parseable markup. SVG is different: it's a file users will embed in web pages, serve from CDNs, and open in viewers.
The componentHtml is injected directly into <foreignObject> without sanitization. If a composition renders user-controlled input (e.g., from props coming from an API), the resulting SVG could contain executable script content when embedded in a web page. This is a stored XSS risk that doesn't exist in the PNG path.
We'd need to either sanitize the HTML going into the SVG, or document this as a security consideration for users.
Fonts won't load in SVG context
The <link rel="stylesheet"> tags for Google Fonts are injected inside <foreignObject>, but SVG's resource-loading security model blocks external requests in most consumption contexts:
- Direct file open → CORS blocks remote fonts
<img>embed → all external requests blocked- Desktop SVG tools → ignore
<foreignObject>entirely
Users who configure config.fonts will see correct rendering in Chrome but get silent font fallback everywhere else.
Scale semantics for SVG
SVG is resolution-independent — scale doesn't have the same meaning as it does for PNG (where it maps to deviceScaleFactor and physically renders more pixels). In this implementation, scale changes the outer SVG width/height but the foreignObject stays at logical dimensions. This could produce unexpected results where the content doesn't fill the scaled canvas.
Consider either: making scale a no-op for SVG (since SVG is inherently scalable), or ensuring the foreignObject dimensions also scale.
API surface: separate renderToSVG vs unified render()
One of Grafex's design goals is a single unified API — render() handles everything, and users control output via config.format. Exporting renderToSVG as a separate public function splits that model: users now have to choose between two APIs instead of just changing a config option.
The internal format handler pattern in render.ts is great — it routes format: 'svg' to the right code path behind the scenes. But the public export of renderToSVG exposes an implementation detail that we'd have to maintain as a stable API. We'd prefer to keep it internal and let render() + format: 'svg' be the only public interface.
Smaller items
- The unit test for
--format svgvalidation (cli.test.ts) was weakened — it no longer asserts exit code, just checks stderr doesn't contain a specific string close()is called even on the SVG path where no browser was started — not a bug (it's idempotent) but worth verifying
Summary
The code is well-written and the test coverage is thorough. The concerns above are architectural and security-related, not code quality issues. We'd love to work with you on finding the right SVG approach — whether that's refining this foreignObject method with proper documentation/limitations, or exploring a browser-based approach that produces more universally compatible SVG.
Looking forward to your thoughts!
This pull request adds native SVG export support to the project, allowing compositions to be exported as either PNG or SVG files. It introduces a new
renderToSVGfunction, updates the CLI and pipeline to handle both formats, and adds comprehensive tests and an SVG dashboard example. SVG output is generated without requiring a browser, increasing performance and portability.SVG Export Support
renderToSVGfunction inruntime.tsthat wraps component HTML in an SVG<foreignObject>, supporting scaling and font embedding.export.ts) and pipeline (render.ts) to accept a--formatoption ("png" or "svg"), set default output filenames accordingly, and route rendering through format handlers. [1] [2] [3] [4]Formattype and allowsvgas a valid format inRenderOptions,RenderResult, andCompositionConfig. [1] [2]Testing and Examples
svg-dashboard.tsxfixture demonstrating a complex SVG dashboard composition.Exports and API
renderToSVGfrom the main index for public API usage.