Transformer Middleware
Overview
The BlockSuite Transformer system is a powerful framework for handling document transformations, including import/export operations, content modifications, and data conversions. It provides a flexible and extensible architecture through its middleware and slots system.
Transformer Basics
Core Concepts
The transformer system revolves around these key concepts:
-
Transformer: The main class that handles document transformations
-
Middleware: Extensions that can modify the transformation process
-
Slots: Hooks into different stages of the transformation
-
Snapshots: Data structures representing document states
Basic Usage
// Creating a transformer
const transformer = new Transformer({
schema,
blobCRUD,
docCRUD,
middlewares: [] // Optional middleware array
});
// Using transformer with middleware
const transformer = doc.getTransformer([
titleMiddleware(doc.workspace.meta.docMetas),
embedSyncedDocMiddleware('content'),
defaultImageProxyMiddleware
]);
Middleware System
What is Middleware?
Middleware in BlockSuite's transformer system are functions that can intercept and modify the transformation process. They provide a way to extend the core functionality without modifying the base code.
Middleware Structure
type TransformerMiddleware = (options: TransformerMiddlewareOptions) => void;
type TransformerMiddlewareOptions = {
assetsManager: AssetsManager;
slots: TransformerSlots;
docCRUD: DocCRUD;
adapterConfigs: Map<string, unknown>;
transformerConfigs: Map<string, unknown>;
};
Creating Middleware
const myMiddleware = (): TransformerMiddleware => {
return ({ slots, adapterConfigs }) => {
// Middleware implementation
};
};
Slots System
Overview
The slots system provides hooks into different stages of the transformation process through a reactive, type-safe interface.
Slot Types
type TransformerSlots = {
beforeImport: Subject<BeforeImportPayload>;
afterImport: Subject<AfterImportPayload>;
beforeExport: Subject<BeforeExportPayload>;
afterExport: Subject<AfterExportPayload>;
};
Payload Types
BeforeImport Payloads
type BeforeImportPayload =
| BeforeImportBlockPayload
| {
snapshot: SliceSnapshot;
type: 'slice';
}
| {
snapshot: DocSnapshot;
type: 'page';
}
| {
snapshot: CollectionInfoSnapshot;
type: 'info';
};
AfterImport Payloads
type AfterImportPayload =
| AfterImportBlockPayload
| {
snapshot: DocSnapshot;
type: 'page';
page: Store;
}
| {
snapshot: SliceSnapshot;
type: 'slice';
slice: Slice;
}
| {
snapshot: CollectionInfoSnapshot;
type: 'info';
};
Using Slots
Basic Slot Usage
const basicMiddleware = (): TransformerMiddleware => {
return ({ slots }) => {
slots.beforeImport.subscribe(payload => {
if (payload.type === 'page') {
// Handle page import
}
});
};
};
Multiple Slots Example
const multiSlotMiddleware = (): TransformerMiddleware => {
return ({ slots }) => {
// Before import handling
slots.beforeImport.subscribe(payload => {
// Handle pre-import
});
// After import handling
slots.afterImport.subscribe(payload => {
// Handle post-import
});
};
};
Best Practices
Middleware Development
- Single Responsibility
- Error Handling
- Type Safety
-
Always check payload types
-
Use TypeScript's type system effectively
-
Handle all possible payload types
- Performance
-
Keep middleware lightweight
-
Avoid blocking operations
-
Use async/await when necessary
Slot Usage Guidelines
When to Use Each Slot
- beforeImport
-
Data validation
-
Content modification
-
State setup
-
Format transformation
- afterImport
-
Post-processing
-
Component updates
-
State cleanup
-
Side effects
- beforeExport
-
Data preparation
-
Metadata addition
-
Content formatting
-
Export configuration
- afterExport
-
Cleanup
-
System notifications
-
Analytics
-
UI updates
Examples
1. Title Middleware
const titleMiddleware = (metas: DocMeta[]): TransformerMiddleware =>
({ slots, adapterConfigs }) => {
slots.beforeExport.subscribe(() => {
for (const meta of metas) {
adapterConfigs.set('title:' + meta.id, meta.title);
}
});
};
2. File Name Middleware
const fileNameMiddleware = (fileName?: string): TransformerMiddleware =>
({ slots }) => {
slots.beforeImport.subscribe(payload => {
if (payload.type !== 'page') return;
if (!fileName) return;
payload.snapshot.meta.title = fileName;
payload.snapshot.blocks.props.title = {
'$blocksuite:internal:text$': true,
delta: [{ insert: fileName }]
};
});
};
3. Paste Middleware
const pasteMiddleware = (std: EditorHost['std']): TransformerMiddleware => {
return ({ slots }) => {
let tr: PasteTr | undefined;
slots.beforeImport.subscribe(payload => {
if (payload.type === 'slice') {
const text = std.selection.find(TextSelection);
if (!text) return;
tr = new PasteTr(std, text, payload.snapshot);
if (tr.canMerge()) {
tr.merge();
}
}
});
slots.afterImport.subscribe(payload => {
if (tr && payload.type === 'slice') {
tr.pasted();
tr.focusPasted();
tr.convertToLinkedDoc();
}
});
};
};
4. Complete Example
const completeMiddleware = (): TransformerMiddleware => {
return ({ slots, adapterConfigs }) => {
// Before import handling
slots.beforeImport.subscribe(payload => {
if (payload.type === 'page') {
console.log('Importing page:', payload.snapshot.meta.title);
}
});
// After import handling
slots.afterImport.subscribe(payload => {
if (payload.type === 'page') {
console.log('Page imported:', payload.page.id);
}
});
// Before export handling
slots.beforeExport.subscribe(payload => {
if (payload.type === 'page') {
console.log('Exporting page:', payload.page.id);
}
});
// After export handling
slots.afterExport.subscribe(payload => {
if (payload.type === 'page') {
console.log('Page exported:', payload.snapshot.meta.title);
}
});
};
};
Conclusion
The BlockSuite Transformer system provides a robust and flexible framework for handling document transformations. By understanding and properly utilizing the middleware and slots system, you can create powerful and maintainable extensions to the core functionality.
Key takeaways:
-
Use middleware for extending functionality
-
Leverage slots for fine-grained control
-
Follow best practices for development
-
Implement proper error handling
-
Consider performance implications
-
Maintain type safety
This system allows for great flexibility while maintaining a clean and maintainable codebase.