Migration Guide
Upgrading to newer versions of Sone and migration tips
Migration Guide
This guide helps you migrate between different versions of Sone and provides tips for common migration scenarios.
Version 1.0.x
New Features in 1.0.x
- Text Auto-fit: Automatic text scaling to fit available space
- Improved Line Breaking: Better support for Khmer and other complex scripts
- Enhanced Gradients: More robust gradient parsing and rendering
- Squircle Support: iOS-style corner smoothing
- QR Code Generation: Built-in QR code support
Breaking Changes
None - Version 1.0.x is fully backward compatible.
Recommended Updates
While not required, consider updating your code to use new features:
Text Auto-fit
// Old approach - manual sizing
Text("Long text content").size(calculateOptimalSize(text, container))
// New approach - automatic fitting
Text("Long text content").autofit(true).maxWidth(300)
Corner Smoothing
// Old approach - basic rounded corners
Column(content).rounded(15)
// New approach - smooth iOS-style corners
Column(content).rounded(15).cornerSmoothing(0.6)
Migrating from Similar Libraries
From React/JSX
If you're coming from React, the component syntax will feel familiar:
// React JSX
function MyComponent() {
return (
<div style={{ display: 'flex', flexDirection: 'column', padding: 20 }}>
<h1 style={{ fontSize: 24, fontWeight: 'bold' }}>Hello</h1>
<p style={{ fontSize: 16 }}>World</p>
</div>
);
}
// Sone equivalent
function MyComponent() {
return Column(
Text("Hello").size(24).weight("bold"),
Text("World").size(16)
).padding(20);
}
From Canvas APIs
If you're migrating from direct Canvas API usage:
// Canvas API
const canvas = createCanvas(400, 300);
const ctx = canvas.getContext('2d');
ctx.fillStyle = 'white';
ctx.fillRect(0, 0, 400, 300);
ctx.fillStyle = 'black';
ctx.font = '24px Arial';
ctx.fillText('Hello World', 50, 100);
// Sone equivalent
const document = Column(
Text("Hello World").size(24).font("Arial")
).size(400, 300).bg("white").padding(50);
const buffer = await sone(document).jpg();
From CSS
Sone's layout system maps closely to CSS Flexbox:
/* CSS */
.container {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
padding: 20px;
background: linear-gradient(45deg, blue, red);
border-radius: 10px;
}
.text {
font-size: 18px;
font-weight: bold;
color: white;
text-shadow: 1px 1px 2px rgba(0,0,0,0.5);
}
// Sone equivalent
Column(
Text("Content")
.size(18)
.weight("bold")
.color("white")
.dropShadow("1px 1px 2px rgba(0,0,0,0.5)")
)
.justifyContent("center")
.alignItems("center")
.padding(20)
.bg("linear-gradient(45deg, blue, red)")
.rounded(10)
Common Migration Patterns
Responsive Design
// Old: Fixed sizes
function Card(content) {
return Column(content).width(300).height(200);
}
// New: Flexible sizing
function Card(content) {
return Column(content)
.minWidth(250)
.maxWidth(400)
.padding(20)
.flex(1); // Grows with container
}
Typography Hierarchy
// Old: Scattered font definitions
Text("Title").size(24).weight("bold").font("Arial");
Text("Body").size(16).font("Arial");
Text("Caption").size(12).color("gray").font("Arial");
// New: Using TextDefault for consistency
function Typography(title, body, caption) {
return TextDefault(
Text(title).size(24).weight("bold"),
Text(body).size(16),
Text(caption).size(12).color("gray")
).font("Arial", "sans-serif");
}
Image Handling
// Old: Basic image placement
Photo("image.jpg").size(200, 150);
// New: Responsive image with proper scaling
Photo("image.jpg")
.maxWidth(400)
.preserveAspectRatio(true)
.scaleType("cover")
.rounded(8);
Color Management
// Old: Hardcoded colors
Text("Error").color("#ff0000");
Text("Success").color("#00ff00");
// New: Color system
const colors = {
error: "#dc2626",
success: "#059669",
primary: "#2563eb"
};
Text("Error").color(colors.error);
Text("Success").color(colors.success);
Platform Migration
Node.js to Browser
When moving from Node.js to browser environment:
// Node.js
import { sone } from "sone";
const buffer = await sone(document).jpg();
// Browser (requires custom renderer)
import { render, browserRenderer } from "./browser-renderer";
const canvas = await render(document, browserRenderer);
Server-Side to Client-Side Rendering
// Server-side generation
app.get('/certificate/:id', async (req, res) => {
const cert = await generateCertificate(req.params.id);
const buffer = await sone(cert).pdf();
res.contentType('application/pdf');
res.send(buffer);
});
// Client-side preview (using canvas)
async function previewCertificate(data) {
const cert = generateCertificate(data);
const canvas = await render(cert, browserRenderer);
document.body.appendChild(canvas);
}
Performance Migration
Optimizing Large Documents
// Old: Single large component
function LargeReport(data) {
return Column(
...data.sections.map(section =>
Column(
Text(section.title).size(20),
...section.items.map(item => Text(item))
)
)
);
}
// New: Paginated approach
function OptimizedReport(data, page = 0, itemsPerPage = 50) {
const startIndex = page * itemsPerPage;
const pageItems = data.sections.slice(startIndex, startIndex + itemsPerPage);
return Column(
...pageItems.map(section =>
Column(
Text(section.title).size(20),
...section.items.map(item => Text(item))
)
)
);
}
Font Loading Optimization
// Old: Loading fonts on demand
async function generateDocument(data) {
await Font.load("CustomFont", "./fonts/custom.ttf");
return Text("Content").font("CustomFont");
}
// New: Pre-loading fonts
const fontPromises = [
Font.load("CustomFont", "./fonts/custom.ttf"),
Font.load("BoldFont", "./fonts/bold.ttf")
];
await Promise.all(fontPromises);
function generateDocument(data) {
// Fonts already loaded
return Text("Content").font("CustomFont");
}
Configuration Migration
Environment-Specific Configs
// development.config.js
export const config = {
fonts: {
basePath: "./assets/fonts/"
},
debug: {
layout: true,
text: true
}
};
// production.config.js
export const config = {
fonts: {
basePath: "/static/fonts/"
},
debug: {
layout: false,
text: false
}
};
// Usage
const document = generateDocument(data);
const result = await sone(document, config).jpg();
Build System Integration
// webpack.config.js
module.exports = {
externals: {
'skia-canvas': 'commonjs skia-canvas'
},
resolve: {
alias: {
'sone': process.env.NODE_ENV === 'production'
? 'sone/dist/node.js'
: 'sone/src/node.ts'
}
}
};
Testing Migration
Unit Test Updates
// Old: Testing with mock canvas
describe('Document Generation', () => {
it('should render text', () => {
const mockCanvas = createMockCanvas();
const result = render(Text("Hello"), mockRenderer);
expect(result).toMatchSnapshot();
});
});
// New: Using Sone's built-in testing utilities
describe('Document Generation', () => {
it('should render text', async () => {
const result = await sone(Text("Hello")).png();
expect(result).toMatchImageSnapshot();
});
});
Visual Regression Testing
// Set up visual testing
async function visualTest(component, name) {
const buffer = await sone(component).png();
await expect(buffer).toMatchVisualSnapshot(name);
}
// Test usage
test('Certificate layout', async () => {
const cert = generateCertificate(testData);
await visualTest(cert, 'certificate-basic');
});
Troubleshooting Common Issues
Font Loading Issues
// Problem: Font not found error
Text("Content").font("NonExistentFont");
// Solution: Check font availability and provide fallbacks
if (!Font.has("CustomFont")) {
await Font.load("CustomFont", "./fonts/custom.ttf");
}
Text("Content").font("CustomFont", "Arial", "sans-serif");
Memory Issues with Large Documents
// Problem: Memory leaks with repeated rendering
for (let i = 0; i < 1000; i++) {
const doc = generateDocument(data[i]);
await sone(doc).jpg(); // Memory builds up
}
// Solution: Process in batches
async function processBatch(items, batchSize = 10) {
for (let i = 0; i < items.length; i += batchSize) {
const batch = items.slice(i, i + batchSize);
await Promise.all(
batch.map(async (item) => {
const doc = generateDocument(item);
const result = await sone(doc).jpg();
return saveResult(result, item.id);
})
);
// Allow garbage collection between batches
global.gc && global.gc();
}
}
Layout Calculation Issues
// Problem: Unexpected layout behavior
Column(
Text("Long text that might wrap"),
Photo("image.jpg")
).width(200); // Layout might not be as expected
// Solution: Be explicit about sizing and constraints
Column(
Text("Long text that might wrap")
.maxWidth(200)
.wrap(true),
Photo("image.jpg")
.maxWidth(200)
.preserveAspectRatio(true)
.scaleType("contain")
).width(200);
Best Practices for Migration
- Start Small: Migrate one component at a time
- Test Thoroughly: Use visual regression testing
- Performance Monitor: Watch memory usage and render times
- Font Management: Centralize font loading
- Error Handling: Implement robust error handling for font and image loading
- Documentation: Keep examples up to date with your migration
Getting Help
If you encounter issues during migration:
- Check the GitHub Issues
- Review the test files for examples
- Join the community discussions
- Create a minimal reproduction case for bugs