Custom Exporters
Create custom exporters to output your design tokens in any format you need.
Overview
TokiForge provides built-in exporters for CSS, SCSS, JavaScript, TypeScript, and JSON. However, you can create custom exporters for:
- Custom CSS formats (Sass, Less, Stylus)
- Framework-specific formats (Tailwind config, styled-components themes)
- Platform-specific formats (iOS, Android, Flutter)
- Custom build tools and pipelines
Built-in Exporters
Before creating custom exporters, familiarize yourself with the built-in ones:
typescript
import { TokenExporter } from '@tokiforge/core';
// CSS
const css = TokenExporter.exportCSS(tokens, {
selector: ':root',
prefix: 'hf',
});
// SCSS
const scss = TokenExporter.exportSCSS(tokens, {
prefix: 'hf',
});
// JavaScript
const js = TokenExporter.exportJS(tokens, {
variables: false,
});
// TypeScript
const ts = TokenExporter.exportTS(tokens);
// JSON
const json = TokenExporter.exportJSON(tokens);Creating Custom Exporters
Method 1: Extend TokenExporter
Create a class that extends TokenExporter:
typescript
import { TokenExporter } from '@tokiforge/core';
import type { DesignTokens, TokenExportOptions } from '@tokiforge/core';
class CustomExporter extends TokenExporter {
static exportCustomFormat(
tokens: DesignTokens,
options: TokenExportOptions = {}
): string {
// Your custom export logic here
const flattened = this.flattenTokens(tokens);
const lines: string[] = [];
for (const path in flattened) {
const token = flattened[path];
// Custom formatting logic
lines.push(`${path}: ${token.value}`);
}
return lines.join('\n');
}
}
// Usage
const output = CustomExporter.exportCustomFormat(tokens);Method 2: Standalone Function
Create a standalone function that uses TokenExporter utilities:
typescript
import { TokenExporter } from '@tokiforge/core';
import type { DesignTokens } from '@tokiforge/core';
function exportToTailwind(tokens: DesignTokens): string {
// Access private method via type casting (if needed)
// Or use public methods and flatten manually
const flattened: Record<string, any> = {};
// Flatten tokens manually
function flatten(obj: any, prefix = '') {
for (const key in obj) {
const value = obj[key];
const path = prefix ? `${prefix}.${key}` : key;
if (value && typeof value === 'object' && 'value' in value) {
flattened[path] = value.value;
} else if (value && typeof value === 'object') {
flatten(value, path);
}
}
}
flatten(tokens);
// Convert to Tailwind config format
const tailwindConfig = {
theme: {
extend: {
colors: {},
spacing: {},
borderRadius: {},
},
},
};
for (const path in flattened) {
const value = flattened[path];
const parts = path.split('.');
if (parts[0] === 'color') {
const colorName = parts.slice(1).join('-');
tailwindConfig.theme.extend.colors[colorName] = value;
} else if (parts[0] === 'spacing') {
const spacingName = parts.slice(1).join('-');
tailwindConfig.theme.extend.spacing[spacingName] = value;
} else if (parts[0] === 'radius') {
const radiusName = parts.slice(1).join('-');
tailwindConfig.theme.extend.borderRadius[radiusName] = value;
}
}
return `module.exports = ${JSON.stringify(tailwindConfig, null, 2)};`;
}
// Usage
const tailwindConfig = exportToTailwind(tokens);Method 3: Using TokenExporter.export
Create a wrapper that uses the built-in export method:
typescript
import { TokenExporter } from '@tokiforge/core';
import type { DesignTokens, TokenExportOptions } from '@tokiforge/core';
function exportToLess(tokens: DesignTokens, options: TokenExportOptions = {}): string {
// Use SCSS export as base (similar syntax)
const scss = TokenExporter.exportSCSS(tokens, options);
// Convert SCSS variables to Less variables
return scss.replace(/\$/g, '@');
}
// Usage
const less = exportToLess(tokens, { prefix: 'hf' });Examples
Example 1: Tailwind CSS Config Exporter
typescript
import type { DesignTokens } from '@tokiforge/core';
function exportToTailwind(tokens: DesignTokens): string {
const config: any = {
theme: {
extend: {},
},
};
function processTokens(obj: any, category: string, target: any) {
for (const key in obj) {
const value = obj[key];
if (value && typeof value === 'object' && 'value' in value) {
if (!target[category]) target[category] = {};
target[category][key] = value.value;
} else if (value && typeof value === 'object') {
processTokens(value, category, target);
}
}
}
// Map token categories to Tailwind config
if (tokens.color) {
processTokens(tokens.color, 'colors', config.theme.extend);
}
if (tokens.spacing) {
processTokens(tokens.spacing, 'spacing', config.theme.extend);
}
if (tokens.radius) {
processTokens(tokens.radius, 'borderRadius', config.theme.extend);
}
return `module.exports = ${JSON.stringify(config, null, 2)};`;
}Example 2: Styled Components Theme Exporter
typescript
import { TokenExporter } from '@tokiforge/core';
import type { DesignTokens } from '@tokiforge/core';
function exportToStyledComponents(tokens: DesignTokens): string {
const js = TokenExporter.exportJS(tokens, { variables: false });
// Convert to styled-components theme format
const theme = js.replace('export default', 'export const theme =');
return `${theme}\n\nexport default theme;`;
}Example 3: iOS Swift Exporter
typescript
import type { DesignTokens } from '@tokiforge/core';
function exportToSwift(tokens: DesignTokens): string {
const lines: string[] = [
'import SwiftUI',
'',
'struct DesignTokens {',
];
function processTokens(obj: any, indent: number = 1): void {
const spaces = ' '.repeat(indent);
for (const key in obj) {
const value = obj[key];
if (value && typeof value === 'object' && 'value' in value) {
const swiftValue = typeof value.value === 'string'
? `"${value.value}"`
: value.value;
lines.push(`${spaces}static let ${key} = ${swiftValue}`);
} else if (value && typeof value === 'object') {
lines.push(`${spaces}struct ${key} {`);
processTokens(value, indent + 1);
lines.push(`${spaces}}`);
}
}
}
processTokens(tokens);
lines.push('}');
return lines.join('\n');
}Example 4: Android XML Resources Exporter
typescript
import type { DesignTokens } from '@tokiforge/core';
function exportToAndroidXML(tokens: DesignTokens): string {
const lines: string[] = [
'<?xml version="1.0" encoding="utf-8"?>',
'<resources>',
];
function processTokens(obj: any, prefix: string = ''): void {
for (const key in obj) {
const value = obj[key];
const name = prefix ? `${prefix}_${key}` : key;
if (value && typeof value === 'object' && 'value' in value) {
const xmlValue = value.value.toString().replace('px', 'dp');
lines.push(` <dimen name="${name}">${xmlValue}</dimen>`);
} else if (value && typeof value === 'object') {
processTokens(value, name);
}
}
}
processTokens(tokens);
lines.push('</resources>');
return lines.join('\n');
}Integration with Build Tools
Webpack Plugin
typescript
import { TokenParser, TokenExporter } from '@tokiforge/core';
import type { Plugin } from 'webpack';
class TokiForgeWebpackPlugin implements Plugin {
constructor(private config: { input: string; output: string; format: string }) {}
apply(compiler: any) {
compiler.hooks.beforeCompile.tapAsync('TokiForgePlugin', (params: any, callback: any) => {
const tokens = TokenParser.parse(this.config.input);
const output = TokenExporter.export(tokens, {
format: this.config.format as any,
});
// Write to output file
require('fs').writeFileSync(this.config.output, output);
callback();
});
}
}
export default TokiForgeWebpackPlugin;Vite Plugin
typescript
import { TokenParser, TokenExporter } from '@tokiforge/core';
import type { Plugin } from 'vite';
import { readFileSync, writeFileSync } from 'fs';
export function tokiForgePlugin(config: {
input: string;
output: string;
format: string;
}): Plugin {
return {
name: 'tokiforge',
buildStart() {
const tokens = TokenParser.parse(config.input);
const output = TokenExporter.export(tokens, {
format: config.format as any,
});
writeFileSync(config.output, output);
},
};
}Rollup Plugin
typescript
import { TokenParser, TokenExporter } from '@tokiforge/core';
import type { Plugin } from 'rollup';
import { readFileSync, writeFileSync } from 'fs';
export function tokiForgePlugin(config: {
input: string;
output: string;
format: string;
}): Plugin {
return {
name: 'tokiforge',
buildStart() {
const tokens = TokenParser.parse(config.input);
const output = TokenExporter.export(tokens, {
format: config.format as any,
});
writeFileSync(config.output, output);
},
};
}Best Practices
- Reuse TokenExporter utilities - Use
flattenTokensand other methods when possible - Handle token references - Ensure references are expanded before exporting
- Validate tokens - Use
TokenParser.validate()before exporting - Type safety - Use TypeScript for type-safe custom exporters
- Error handling - Add proper error handling for invalid tokens
- Testing - Write tests for your custom exporters
Advanced: Custom Format Registration
Create a registry system for custom formats:
typescript
import { TokenExporter } from '@tokiforge/core';
import type { DesignTokens, TokenExportOptions } from '@tokiforge/core';
type CustomExporter = (tokens: DesignTokens, options?: TokenExportOptions) => string;
class ExporterRegistry {
private static exporters: Map<string, CustomExporter> = new Map();
static register(name: string, exporter: CustomExporter) {
this.exporters.set(name, exporter);
}
static export(name: string, tokens: DesignTokens, options?: TokenExportOptions): string {
const exporter = this.exporters.get(name);
if (!exporter) {
throw new Error(`Exporter "${name}" not found`);
}
return exporter(tokens, options);
}
static list(): string[] {
return Array.from(this.exporters.keys());
}
}
// Register custom exporter
ExporterRegistry.register('tailwind', exportToTailwind);
// Use it
const output = ExporterRegistry.export('tailwind', tokens);Next Steps
- See TokenExporter API for full API reference
- Check Design Tokens Guide for token structure
- Explore Examples for usage patterns