grafex
v0.8.0
Published
Images as Code. Write JSX, export PNG.
Downloads
240
Readme
Grafex
Images as Code. Write JSX, export as images.
Grafex is a programmatic image composition tool. Write compositions in JSX/TSX with full CSS support and export as images — no browser window, no server, no configuration ceremony.
npx grafex export -f card.tsx -o card.pngRequirements
- Node.js >= 20.0.0
- WebKit browser (installed automatically on
npm install)
Quick Start
npm install grafexWrite a composition:
// card.tsx
import type { CompositionConfig } from 'grafex';
export const config: CompositionConfig = { width: 1200, height: 630 };
export default function Card() {
return (
<div
style={{
width: '100%',
height: '100%',
background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
color: 'white',
fontSize: '64px',
fontWeight: 'bold',
fontFamily: 'system-ui, sans-serif',
}}
>
Hello, Grafex!
</div>
);
}Export it:
npx grafex export -f card.tsx -o card.pngCLI Reference
grafex export
Render a composition file to an image.
| Flag | Short | Type | Default | Description |
| ----------- | ----- | ------------- | -------------- | ------------------------------------------------------------- |
| --file | -f | string | — | Path to the .tsx composition file (required) |
| --out | -o | string | ./output.png | Output file path or directory (for multi-variant output) |
| --props | | string (JSON) | {} | Props to pass to the composition as a JSON object |
| --width | | number | from config | Override composition width in pixels |
| --height | | number | from config | Override composition height in pixels |
| --format | | string | png | Output format (png or jpeg) |
| --quality | | number | 90 | JPEG quality 1–100 (only applies when format is jpeg) |
| --scale | | number | 1 | Device pixel ratio. Use 2 for retina/high-DPI output. |
| --browser | | string | webkit | Browser engine |
| --variant | | string | (all) | Render a single variant by name. Omit to render all variants. |
| --help | -h | | | Show help text |
High-DPI output: Set
scaleto control the device pixel ratio. A 1200x630 composition withscale: 2produces a 2400x1260 PNG — same layout, double the pixel density. Works in the config, CLI, and API.export const config: CompositionConfig = { width: 1200, height: 630, scale: 2, };
Examples:
# Basic export
grafex export -f card.tsx -o card.png
# Pass props to the composition
grafex export -f card.tsx -o card.png --props '{"title":"Hello World"}'
# Override dimensions
grafex export -f card.tsx -o card.png --width 800 --height 400
grafex dev
Start a live preview server that watches your composition and re-renders on every file change.
npx grafex dev -f card.tsx| Flag | Short | Type | Default | Description |
| ----------- | ----- | ------------- | ------- | ----------------------------------------------------------- |
| --file | -f | string | — | Path to the .tsx composition file (required) |
| --port | | number | 3000 | Preview server port |
| --props | | string (JSON) | {} | Props to pass to the composition as a JSON object |
| --variant | | string | (first) | Show only the named variant (when composition has variants) |
| --help | -h | | | Show help text |
The dev server watches the composition file, all its imports, CSS files from config.css, and local image assets. Changes are debounced and the preview updates within ~100ms. Open http://localhost:3000 to see the live preview.
Press Ctrl+C to stop.
Global flags
grafex --version # Print version and exit
grafex --help # Print help text and exitLibrary API
import { render, renderAll, close } from 'grafex';render(compositionPath, options?)
Render a composition to an image buffer. Pass options.variant to render a specific variant.
const result = await render('./card.tsx', {
props: { title: 'Hello' },
width: 1200,
height: 630,
browser: 'webkit',
});
// result.buffer — Buffer containing image data
// result.width — effective render width
// result.height — effective render height
// result.format — 'png' | 'jpeg'Parameters:
| Parameter | Type | Description |
| ----------------- | ------------------------- | -------------------------------------------------------- |
| compositionPath | string | Path to the .tsx composition file |
| options.props | Record<string, unknown> | Props to pass to the composition |
| options.width | number | Override composition width |
| options.height | number | Override composition height |
| options.format | 'png' \| 'jpeg' | Output format (default: 'png') |
| options.quality | number | JPEG quality 1–100 (default: 90, only applies to JPEG) |
| options.scale | number | Device pixel ratio (default: 1) |
| options.browser | 'webkit' \| 'chromium' | Browser engine (default: 'webkit') |
| options.variant | string | Named variant to render from config.variants |
Returns: Promise<RenderResult> where RenderResult is:
interface RenderResult {
buffer: Buffer;
width: number;
height: number;
format: 'png' | 'jpeg';
}renderAll(compositionPath, options?)
Render all variants defined in config.variants. Returns a Map<string, RenderResult> keyed by variant name.
import { renderAll, close } from 'grafex';
import { writeFileSync } from 'node:fs';
const all = await renderAll('./card.tsx', { props: { title: 'Hello' } });
for (const [name, result] of all) {
writeFileSync(`${name}.${result.format}`, result.buffer);
}
await close();close()
Shut down the browser process. Call this when you are done rendering to free resources.
await close();Example — render multiple compositions:
import { render, close } from 'grafex';
import { writeFileSync } from 'node:fs';
const compositions = ['hero.tsx', 'card.tsx', 'thumbnail.tsx'];
for (const file of compositions) {
const result = await render(file);
writeFileSync(file.replace('.tsx', '.png'), result.buffer);
}
await close();Advanced exports
import { h, Fragment, renderToHTML, BrowserManager } from 'grafex';CSS Files
Load external CSS files by specifying paths in config.css. Paths are resolved relative to the composition file:
export const config: CompositionConfig = {
width: 1200,
height: 630,
css: ['./styles.css'],
};This works with any CSS — plain stylesheets, Tailwind output, Sass output, anything that produces a .css file. The contents are injected as <style> tags in the HTML <head> before rendering.
Tailwind CSS example:
# 1. Generate the CSS
npx tailwindcss -i ./input.css -o ./styles.css// card.tsx
export const config: CompositionConfig = {
width: 1200,
height: 630,
css: ['./styles.css'],
};
export default function Card() {
return (
<div className="w-full h-full flex items-center justify-center bg-gradient-to-br from-indigo-500 to-purple-600 text-white">
<h1 className="text-6xl font-bold">Hello Tailwind</h1>
</div>
);
}# 2. Export the composition
npx grafex export -f card.tsx -o card.pngLocal Images
Use local image files in <img> tags or CSS background-image. Grafex reads them from disk and embeds them as base64 data URLs automatically — no server or public URL needed.
export default function Card() {
return (
<div style={{ width: '100%', height: '100%' }}>
<img src="./logo.png" alt="Logo" width="200" height="60" />
</div>
);
}Paths are resolved relative to the composition file. Supported formats: PNG, JPEG, GIF, WebP, SVG, AVIF, ICO, BMP.
CSS url() references work too — both in inline styles and in external CSS files loaded via config.css:
.hero {
background-image: url('./hero.jpg');
}Remote URLs (http://, https://) and data URLs are passed through unchanged.
Variants
Produce multiple outputs from a single composition — different sizes, formats, or props. Define a variants record in your config. Each variant inherits from the base config and can override any field:
import type { CompositionConfig } from 'grafex';
export const config: CompositionConfig = {
width: 1200,
height: 630,
variants: {
og: {},
twitter: { height: 675 },
square: { width: 1080, height: 1080, props: { layout: 'square' } },
},
};
export default function Card({ layout = 'default' }: { layout?: string }) {
return <div style={{ width: '100%', height: '100%' }}>{layout}</div>;
}Export all variants:
# Renders og.png, twitter.png, square.png (named after each variant)
grafex export -f card.tsx
# Same, but into a directory
grafex export -f card.tsx -o ./images/Export a single variant:
grafex export -f card.tsx --variant og -o card-og.pngLibrary API:
import { render, renderAll, close } from 'grafex';
// Single variant
const result = await render('./card.tsx', { variant: 'twitter' });
// All variants
const all = await renderAll('./card.tsx');
for (const [name, result] of all) {
writeFileSync(`${name}.${result.format}`, result.buffer);
}
await close();Merge rules:
- CLI/API options override variant config, which overrides base config
propsare shallow-merged: variant props apply first, then CLI/API props override individual keys- Array fields (
fonts,css) replace the base value — they do not merge
Browser Installation
WebKit is downloaded automatically when you run npm install via the postinstall script:
npm install grafex
# WebKit is installed automaticallyTo install manually:
npx playwright install webkitTo install only the browser binary without system dependencies:
npx playwright-core install webkitPLAYWRIGHT_BROWSERS_PATH
By default, Playwright installs browsers to a shared cache directory. Set PLAYWRIGHT_BROWSERS_PATH to control where the browser binary is stored:
export PLAYWRIGHT_BROWSERS_PATH=/path/to/browsers
npx playwright install webkit
grafex export -f card.tsx -o card.pngThis is useful in CI environments where the home directory may not be writable.
CI Setup
On Linux, WebKit requires system dependencies. Install them with:
npx playwright install-deps webkit
npx playwright install webkitGitHub Actions example:
- name: Install WebKit dependencies
run: npx playwright install-deps webkit
- name: Install WebKit
run: npx playwright install webkit
- name: Export image
run: npx grafex export -f card.tsx -o card.pngContributing
We welcome contributions! See CONTRIBUTING.md for development setup, architecture overview, code standards, and PR guidelines.
License
MIT
