Code Export

Export generated UI as standalone code for your framework.

Overview

While json-render is designed for dynamic rendering, you can export generated UI as static code. The code generation is intentionally project-specific so you have full control over:

  • Component templates (standalone, no json-render dependencies)
  • Package.json and project structure
  • Framework-specific patterns (Next.js, Remix, etc.)
  • How data is passed to components

Architecture

Code export is split into two parts:

1. @json-render/codegen (utilities)

Framework-agnostic utilities for building code generators:

import {
  traverseSpec,          // Walk the UI spec
  collectUsedComponents, // Get all component types used
  collectStatePaths,      // Get all data binding paths
  collectActions,        // Get all action names
  serializeProps,        // Convert props to JSX string
} from '@json-render/codegen';

2. Your Project (generator)

Custom code generator specific to your project and framework:

// lib/codegen/generator.ts
import { collectUsedComponents, serializeProps } from '@json-render/codegen';

export function generateNextJSProject(spec: Spec): GeneratedFile[] {
  const components = collectUsedComponents(spec);
  
  return [
    { path: 'package.json', content: '...' },
    { path: 'app/page.tsx', content: '...' },
    // ... component files
  ];
}

Example: Next.js Export

See the dashboard example for a complete implementation that exports:

  • package.json - Dependencies and scripts
  • tsconfig.json - TypeScript config
  • next.config.js - Next.js config
  • app/layout.tsx - Root layout
  • app/globals.css - Global styles
  • app/page.tsx - Generated page with data
  • components/ui/*.tsx - Standalone components

Standalone Components

The exported components are standalone with no json-render dependencies. They receive data as props instead of using hooks:

// Generated component (standalone)
interface MetricProps {
  label: string;
  statePath: string;
  data?: Record<string, unknown>;
}

export function Metric({ label, statePath, data }: MetricProps) {
  const value = data ? getByPath(data, statePath) : undefined;
  return (
    <div>
      <span>{label}</span>
      <span>{formatValue(value)}</span>
    </div>
  );
}

Using the Utilities

traverseSpec

import { traverseSpec } from '@json-render/codegen';

traverseSpec(spec, (element, key, depth, parent) => {
  console.log(' '.repeat(depth * 2) + `${key}: ${element.type}`);
});

collectUsedComponents

import { collectUsedComponents } from '@json-render/codegen';

const components = collectUsedComponents(spec);
// Set { 'Card', 'Metric', 'Chart', 'Table' }

// Generate only the needed component files
for (const component of components) {
  files.push({
    path: `components/ui/${component.toLowerCase()}.tsx`,
    content: componentTemplates[component],
  });
}

serializeProps

import { serializeProps } from '@json-render/codegen';

const propsStr = serializeProps({
  title: 'Dashboard',
  columns: 3,
  disabled: true,
});
// 'title="Dashboard" columns={3} disabled'

Try It

Run the dashboard example and click "Export Project" to see code generation in action:

cd examples/dashboard
pnpm dev
# Open http://localhost:3001
# Generate a widget, then click "Export Project"