npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

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.png

Requirements

  • Node.js >= 20.0.0
  • WebKit browser (installed automatically on npm install)

Quick Start

npm install grafex

Write 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.png

CLI 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 scale to control the device pixel ratio. A 1200x630 composition with scale: 2 produces 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 exit

Library 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.png

Local 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.png

Library 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
  • props are 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 automatically

To install manually:

npx playwright install webkit

To install only the browser binary without system dependencies:

npx playwright-core install webkit

PLAYWRIGHT_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.png

This 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 webkit

GitHub 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.png

Contributing

We welcome contributions! See CONTRIBUTING.md for development setup, architecture overview, code standards, and PR guidelines.


License

MIT