pdf-builder-core
v0.2.0
Published
Config-driven visual template builder for PDF and email
Readme
Template Builder
A config-driven, registry-driven, command-driven, and schema-driven visual template builder for PDF and email generation.
Built on GrapesJS as the internal visual engine, exposed through a clean, typed public API.
Quick Start
With Docker (recommended)
# Copy env file
cp .env.example .env
# Start everything
docker compose upThe playground will be available at http://localhost:3000.
Without Docker
Requirements: Node.js 20+ and pnpm 9+
# Install dependencies
pnpm install
# Build the builder package
pnpm build
# Start playground + builder in watch mode
pnpm dev:allProject Structure
├── packages/
│ └── builder/ # @template-builder/core — the main package
│ ├── src/
│ │ ├── core/ # Types, models, config, registries
│ │ ├── adapters/ # Storage, exporters (PDF, email)
│ │ ├── presets/ # Default blocks, commands, templates
│ │ └── ui/ # React components (shell, panels, canvas)
│ ├── tsup.config.ts # Build config (ESM + CJS + d.ts)
│ └── vitest.config.ts # Test config
├── apps/
│ └── playground/ # Vite + React playground app
│ └── src/routes/ # Lab routes (editor, blocks, export)
├── docker-compose.yml
├── Dockerfile
└── docs/ # Architecture & design docsArchitecture
The package follows a layered architecture:
| Layer | Purpose | |------------|----------------------------------------------| | Core | Types, contracts, config normalization, registries | | Adapters | GrapesJS bridge, storage, exporters | | Presets | Default blocks, commands, templates | | UI | React components for the editor interface |
Key Design Principles
- Config-driven: Everything customizable via
BuilderConfig - Registry-based: Blocks, commands, and templates via typed registries
- Schema-driven properties: Block panels generated from
propsSchema - Engine-agnostic API: GrapesJS is internal; the public API is the package
- Replaceable services: Storage, assets, exporters — all swappable
Usage
Basic usage
import { TemplateBuilder } from '@template-builder/core';
import '@template-builder/core/styles.css';
function App() {
return <TemplateBuilder />;
}With configuration
import {
TemplateBuilder,
defineBuilderConfig,
defaultPdfExporter,
defaultEmailExporter,
} from '@template-builder/core';
import '@template-builder/core/styles.css';
const config = defineBuilderConfig({
theme: {
tokens: {
colors: { primary: '#e11d48' },
},
},
capabilities: {
userLevel: 'advanced',
allowExport: { pdf: true, email: true },
},
exporters: {
pdf: defaultPdfExporter,
email: defaultEmailExporter,
},
onChange: (data) => console.log('Changed:', data),
});
function App() {
return <TemplateBuilder config={config} />;
}Custom blocks
import { defineBlock, TemplateBuilder } from '@template-builder/core';
const myBlock = defineBlock({
type: 'my-custom-block',
label: 'My Block',
category: 'Custom',
defaults: { text: 'Hello!' },
propsSchema: [
{ key: 'text', label: 'Text', type: 'text', tab: 'content' },
],
renderers: {
canvas: (props) => `<div>${props.text}</div>`,
},
});
function App() {
return <TemplateBuilder config={{ blocks: [myBlock] }} />;
}Scripts
| Command | Description |
|---------|-------------|
| pnpm dev | Start playground |
| pnpm dev:all | Start playground + builder watch |
| pnpm build | Build the builder package |
| pnpm test | Run tests |
| pnpm lint | Lint all packages |
| pnpm typecheck | Run TypeScript checks |
| pnpm format | Format with Prettier |
Default Blocks
| Block | Type | Category | Semantic Tag |
|-------|------|----------|-------------|
| Section | section | Layout | <section> |
| Container | container | Layout | <div> |
| Hero Banner | hero-banner | Layout | <header> |
| Section Title | section-title | Typography | <h2> |
| Rich Text | rich-text | Typography | <div> |
| Image | image-block | Media | <figure> |
| Callout | callout | Content | <blockquote> |
| Bullet List | bullet-list | Content | <ul> |
| Numbered List | numbered-list | Content | <ol> |
| CTA Button | cta-button | Actions | <a> |
| Footer | footer | Layout | <footer> |
| Divider | divider | Layout | <hr> |
| Page Break | page-break | Layout | - |
| KPI Card | kpi-card | Data | <div> |
| Data Table | data-table | Data | <table> |
| Timeline | timeline | Data | <div> |
| Signature | signature | Content | <div> |
| Chart Placeholder | chart-placeholder | Data | <figure> |
All blocks include semantic descriptors (semantics field) defining HTML tag, editable strategy, and content model.
PDF Export Engines
The builder supports multiple PDF engines via a pluggable PdfEngine contract:
| Engine | ID | Status | Selectable Text | Links | Method |
|--------|----|--------|:-:|:-:|--------|
| pdfmake | pdfmake | Official | ✅ | ✅ | Config-driven DDO |
| Browser Print | browser-print | Complementary | ✅ | ✅ | window.print() |
| Legacy (jsPDF) | legacy-jspdf | Fallback | ❌ | ❌ | html-to-image raster |
Using the Semantic PDF Engine
import { TemplateBuilder, pdfmakeEngine } from '@template-builder/core';
// pdfmake is registered by default.
// Export via the ExportMenu "PDF Semântico" option,
// or programmatically:
const builderRef = useRef(null);
// ...
const result = await builderRef.current.exportPdfViaEngine({ engineId: 'pdfmake' });Custom PDF Engine
import { PdfEngine } from '@template-builder/core';
const myEngine: PdfEngine = {
id: 'my-engine',
label: 'My Custom Engine',
status: 'experimental',
capabilities: { selectableText: true, externalLinks: true, /* ... */ },
async render(input) {
// Convert input.document (TemplateDocument) to PDF blob
return { blob: myBlob };
},
};Plugin API
import { BuilderPlugin, PluginManager } from '@template-builder/core';
const myPlugin: BuilderPlugin = {
id: 'my-plugin',
name: 'My Plugin',
setup(ctx) {
ctx.registerBlock(myCustomBlock);
ctx.registerCommand(myCommand);
ctx.registerPdfEngine(myEngine);
},
};Data Binding
The builder supports data-driven content via BindingRegistry:
import { BindingRegistryService } from '@template-builder/core';
const bindings = new BindingRegistryService();
// Variable binding: replace a block prop with data
bindings.addVariable('block-1', {
sourcePath: 'user.name',
targetProp: 'content',
});
// Loop binding: repeat a block for each item in an array
bindings.addLoop('row-template', {
sourcePath: 'items',
itemAlias: 'item',
limit: 10,
emptyState: 'No items found',
});
// Set data sources
bindings.setDataSource('user', { name: 'Alice' });
bindings.setDataSource('items', [{ title: 'A' }, { title: 'B' }]);Export
- PDF Semântico: pdfmake engine — text selecionável, links clicáveis
- PDF Legacy: html2pdf.js — imagem rasterizada (fallback)
- PDF Alta Fidelidade: Backend Chromium (se configurado)
- Browser Print: Diálogo nativo do navegador
- Email HTML: HTML table-based seguro para clientes de email
All exporters are replaceable via config.exporters or the PdfEngineRegistry.
License
Private — All rights reserved.
