9.9 KiB
9.9 KiB
Build System Reference
Complete guide to the Pencil build system for component linking and monolithic builds.
Overview
The build system enables:
- Component Linking: Reference components across files
- Monolithic Builds: Merge all pages + components into one file
- Standard Builds: Inject components into individual pages
- Validation: Ensure output structure is valid
Configuration
pencil.config.json
Create in project root:
{
"version": "1.0",
"sourceDir": "src",
"buildDir": "build",
"componentLibrary": "src/components/ui-kit.pen",
"buildOptions": {
"minify": false,
"validateAfterBuild": true,
"preserveComments": true
}
}
| Field | Description |
|---|---|
sourceDir |
Source files directory |
buildDir |
Build output directory |
componentLibrary |
Path to component library .pen file |
minify |
Minify JSON output |
validateAfterBuild |
Validate JSON after build |
preserveComments |
Keep JSON comments |
Build Modes
Monolithic Build ⭐ (Recommended)
Merges all pages + component library into one file.
Commands:
python3 scripts/build.py --monolithic
python3 scripts/build.py -m
python3 scripts/build.py --mode monolithic
# Custom output name
python3 scripts/build.py -m --output myDesign.pen
Output:
- File:
tPOS.pen(project root) - Contains: All pages + component library as separate frames
- Size: ~326KB (example)
- Use case: Opening complete design, sharing, archiving
Example structure:
{
"version": "2.6",
"children": [
{
"type": "frame",
"name": "Component Library",
"children": [ /* All reusable components */ ]
},
{
"type": "frame",
"name": "Desktop Home Page",
"children": [ /* Page content */ ]
},
{
"type": "frame",
"name": "Mobile Home Page",
"children": [ /* Page content */ ]
}
],
"variables": { /* Design tokens */ }
}
Component Library Build (Atomic Design)
Merges distributed atomic components into a single library file.
Command:
python3 scripts/build.py --library
python3 scripts/build.py -l
Output:
- File:
src/components/ui-kit.pen(or configured path) - Contains: All atoms, molecules, organisms merged into one file
- Use case: Generating the library to use during page design
Standard Build
Builds individual pages with component injection.
Command:
python3 scripts/build.py
python3 scripts/build.py --mode standard
Output:
- Directory:
build/ - Files: Separate
.penfiles with injected components - Use case: Development workflow, testing component refs
Example:
build/
├── desktop-home.pen # With injected components
├── mobile-home.pen # With injected components
└── tablet-home.pen # With injected components
Build Process
What the Build Script Does
- Load Component Library: Read
ui-kit.pen - Extract Components: Find all
reusable: truecomponents - Extract Variables: Get design tokens
- Find Component Refs: Search for
component_reftypes in pages - Resolve Paths: Match
ui-kit#Button/Primaryto actual component - Inject Components: Add component definitions to pages
- Convert Refs: Transform
component_ref→refwith proper IDs - Validate: Check output structure
Transformation Example
Before build (source file):
{
"type": "component_ref",
"component": "ui-kit#Button/Primary",
"name": "submitButton",
"overrides": {
"label_id": {
"content": "Submit"
}
}
}
After build (output file):
Step 1: Component injected at top of children array
{
"type": "frame",
"id": "abc123",
"name": "Button/Primary",
"reusable": true,
"children": [
{
"type": "text",
"id": "label_id",
"content": "Button"
}
]
}
Step 2: Reference converted
{
"type": "ref",
"ref": "abc123",
"name": "submitButton",
"descendants": {
"label_id": {
"content": "Submit"
}
}
}
Project Structure
Recommended Structure
project-name/
├── src/
│ ├── components/
│ │ └── ui-kit.pen # Component library + design tokens
│ └── pages/
│ ├── desktop-home.pen # Desktop (1440px)
│ ├── mobile-home.pen # Mobile (390px)
│ └── tablet-home.pen # Tablet (768px)
├── build/ # Standard build output (gitignore)
│ ├── desktop-home.pen # With injected components
│ └── ...
├── scripts/
│ └── build.py # Build script
├── pencil.config.json # Build configuration
├── tPOS.pen # Monolithic build output
└── USAGE_GUIDE.md # Documentation
Atomic Design Structure
For enterprise-scale UI Kits:
project-name/
├── src/
│ ├── foundations/
│ │ └── design-tokens.pen # Design system variables
│ ├── atoms/ # Smallest building blocks
│ │ ├── buttons.pen
│ │ ├── inputs.pen
│ │ └── badges.pen
│ ├── molecules/ # Combinations of atoms
│ │ ├── form-fields.pen
│ │ └── search-bar.pen
│ ├── organisms/ # Complex UI sections
│ │ ├── cards.pen
│ │ └── navigation.pen
│ └── pages/ # Complete pages
│ ├── desktop-home.pen
│ └── mobile-home.pen
├── build/
├── scripts/
│ └── build.py
├── pencil.config.json
└── tPOS.pen
Build Commands Reference
Basic Commands
# Help
python3 scripts/build.py --help
# Monolithic build
python3 scripts/build.py -m
# Standard build
python3 scripts/build.py
# Custom output
python3 scripts/build.py -m --output custom.pen
Validation Commands
# Validate JSON
jq empty tPOS.pen && echo "✅ Valid JSON"
# Check structure
jq '{version, childrenCount: (.children | length)}' tPOS.pen
# List frame names
jq '[.children[].name]' tPOS.pen
# Extract design tokens
jq '.variables' tPOS.pen
# Count components
jq '[.children[0].children[] | select(.reusable == true)] | length' tPOS.pen
Workflows
Atomic Design Workflow (Recommended)
1. Layout & Components:
- Update atoms/molecules in `src/atoms/` etc.
- Run `python3 scripts/build.py --library` to update component library
2. Page Design:
- Open `src/pages/desktop-home.pen`
- Use components from the generated `src/components/ui-kit.pen`
3. Production Build:
- Run `python3 scripts/build.py -m`
- Output `tPOS.pen` contains everything
Legacy/Mockup Workflow
1. Design in modular files:
- Edit components in `src/components/ui-kit.pen`
- Edit pages in `src/pages/*.pen`
- Use `component_ref` to reference components
2. Build for testing:
python3 scripts/build.py # Standard build
# Open files in build/ to test
3. Build for production:
python3 scripts/build.py -m # Monolithic
# Share tPOS.pen with team/clients
4. Convert to code:
# Use agent to convert tPOS.pen to HTML/CSS/React/Blazor
Component Library Updates
1. Update component in ui-kit.pen
2. Save file
3. Rebuild all pages:
python3 scripts/build.py -m
4. Test in Pencil app
5. If OK, commit changes
Adding New Components
1. Open ui-kit.pen in Pencil
2. Create component with:
- Unique ID
- Descriptive name (e.g., "Button/Secondary")
- `reusable: true`
3. Save file
4. Use in pages with component_ref:
{
"type": "component_ref",
"component": "ui-kit#Button/Secondary"
}
5. Build and test
Best Practices
1. Component References
✅ DO: Use component_ref in source files
❌ DON'T: Hardcode components into pages
✅ DO: Keep components in ui-kit.pen
❌ DON'T: Duplicate components across files
2. Build Before Testing
✅ DO: Build before opening in Pencil
❌ DON'T: Open source files directly
# Correct workflow:
python3 scripts/build.py -m
open tPOS.pen
# Component refs will be resolved ✅
3. Version Control
✅ DO: Commit source files (src/)
✅ DO: Gitignore build output (build/, tPOS.pen)
# .gitignore
build/
tPOS.pen
*.pen.backup
4. Validation
✅ DO: Validate after build
❌ DON'T: Skip validation
# Always validate:
jq empty tPOS.pen && echo "✅ Valid"
5. Configuration Consistency
✅ DO: Keep pencil.config.json in sync
❌ DON'T: Hardcode paths in scripts
# Update config when structure changes
Troubleshooting
Build Script Errors
Error: Component library not found
# Check config
cat pencil.config.json
# Verify path exists
ls -la src/components/ui-kit.pen
Error: Invalid JSON in source file
# Validate source files
jq empty src/components/ui-kit.pen
jq empty src/pages/desktop-home.pen
Error: Component ref not resolved
# Check component exists in library
jq '[.children[0].children[] | select(.name == "Button/Primary")]' src/components/ui-kit.pen
# Check component path syntax
# Correct: "ui-kit#Button/Primary"
# Wrong: "Button/Primary" (missing library prefix)
Output Validation
# Check file was created
ls -lh tPOS.pen
# Validate JSON structure
jq . tPOS.pen > /dev/null && echo "✅ Valid"
# Check version
jq '.version' tPOS.pen
# Count frames
jq '.children | length' tPOS.pen
# List frame names
jq '[.children[].name]' tPOS.pen