fix(editor): cleanup transformer middleware slot subscriptions (#12630)

<!-- This is an auto-generated comment: release notes by coderabbit.ai -->

## Summary by CodeRabbit

- **New Features**
  - Improved resource management by introducing explicit cleanup for various middleware components, ensuring that resources are properly released when no longer needed.

- **Refactor**
  - Updated middleware logic to support cleanup functions, enhancing the stability and performance of the application by preventing potential memory leaks.

- **Chores**
  - Enhanced lifecycle management in core systems to automatically dispose of resources when appropriate.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
donteatfriedrice
2025-05-29 08:33:30 +00:00
parent 9a96cfded0
commit bc67766bb9
12 changed files with 155 additions and 83 deletions

View File

@@ -37,7 +37,7 @@ const handlePoint = (
}; };
const sliceText = (slots: TransformerSlots, std: EditorHost['std']) => { const sliceText = (slots: TransformerSlots, std: EditorHost['std']) => {
slots.afterExport.subscribe(payload => { const afterExportSubscription = slots.afterExport.subscribe(payload => {
if (payload.type === 'block') { if (payload.type === 'block') {
const snapshot = payload.snapshot; const snapshot = payload.snapshot;
@@ -53,10 +53,14 @@ const sliceText = (slots: TransformerSlots, std: EditorHost['std']) => {
} }
} }
}); });
return () => {
afterExportSubscription.unsubscribe();
};
}; };
export const copyMiddleware = (std: BlockStdScope): TransformerMiddleware => { export const copyMiddleware = (std: BlockStdScope): TransformerMiddleware => {
return ({ slots }) => { return ({ slots }) => {
sliceText(slots, std); return sliceText(slots, std);
}; };
}; };

View File

@@ -3,7 +3,7 @@ import type { TransformerMiddleware } from '@blocksuite/store';
export const fileNameMiddleware = export const fileNameMiddleware =
(fileName?: string): TransformerMiddleware => (fileName?: string): TransformerMiddleware =>
({ slots }) => { ({ slots }) => {
slots.beforeImport.subscribe(payload => { const beforeImportSubscription = slots.beforeImport.subscribe(payload => {
if (payload.type !== 'page') { if (payload.type !== 'page') {
return; return;
} }
@@ -20,4 +20,8 @@ export const fileNameMiddleware =
], ],
}; };
}); });
return () => {
beforeImportSubscription.unsubscribe();
};
}; };

View File

@@ -528,7 +528,7 @@ export const pasteMiddleware = (
): TransformerMiddleware => { ): TransformerMiddleware => {
return ({ slots }) => { return ({ slots }) => {
let tr: PasteTr | undefined; let tr: PasteTr | undefined;
slots.beforeImport.subscribe(payload => { const beforeImportSubscription = slots.beforeImport.subscribe(payload => {
if (payload.type === 'slice') { if (payload.type === 'slice') {
const { snapshot } = payload; const { snapshot } = payload;
flatNote(snapshot); flatNote(snapshot);
@@ -543,13 +543,18 @@ export const pasteMiddleware = (
} }
} }
}); });
slots.afterImport.subscribe(payload => { const afterImportSubscription = slots.afterImport.subscribe(payload => {
if (tr && payload.type === 'slice') { if (tr && payload.type === 'slice') {
tr.pasted(); tr.pasted();
tr.focusPasted(); tr.focusPasted();
tr.convertToLinkedDoc(); tr.convertToLinkedDoc();
} }
}); });
return () => {
beforeImportSubscription.unsubscribe();
afterImportSubscription.unsubscribe();
};
}; };
}; };

View File

@@ -32,7 +32,7 @@ export const replaceIdMiddleware =
map(({ model }) => model) map(({ model }) => model)
); );
afterImportBlock$ const afterImportBlockSubscription = afterImportBlock$
.pipe(filter(model => matchModels(model, [DatabaseBlockModel]))) .pipe(filter(model => matchModels(model, [DatabaseBlockModel])))
.subscribe(model => { .subscribe(model => {
Object.keys(model.props.cells).forEach(cellId => { Object.keys(model.props.cells).forEach(cellId => {
@@ -44,7 +44,7 @@ export const replaceIdMiddleware =
}); });
// replace LinkedPage pageId with new id in paragraph blocks // replace LinkedPage pageId with new id in paragraph blocks
afterImportBlock$ const replaceLinkedPageIdSubscription = afterImportBlock$
.pipe( .pipe(
filter(model => filter(model =>
matchModels(model, [ParagraphBlockModel, ListBlockModel]) matchModels(model, [ParagraphBlockModel, ListBlockModel])
@@ -84,7 +84,7 @@ export const replaceIdMiddleware =
} }
}); });
afterImportBlock$ const replaceSurfaceRefIdSubscription = afterImportBlock$
.pipe(filter(model => matchModels(model, [SurfaceRefBlockModel]))) .pipe(filter(model => matchModels(model, [SurfaceRefBlockModel])))
.subscribe(model => { .subscribe(model => {
const original = model.props.reference; const original = model.props.reference;
@@ -105,7 +105,7 @@ export const replaceIdMiddleware =
}); });
// TODO(@fundon): process linked block/element // TODO(@fundon): process linked block/element
afterImportBlock$ const replaceLinkedDocIdSubscription = afterImportBlock$
.pipe( .pipe(
filter(model => filter(model =>
matchModels(model, [EmbedLinkedDocModel, EmbedSyncedDocModel]) matchModels(model, [EmbedLinkedDocModel, EmbedSyncedDocModel])
@@ -128,7 +128,7 @@ export const replaceIdMiddleware =
// Before Import // Before Import
slots.beforeImport const beforeImportPageSubscription = slots.beforeImport
.pipe(filter(payload => payload.type === 'page')) .pipe(filter(payload => payload.type === 'page'))
.subscribe(payload => { .subscribe(payload => {
if (idMap.has(payload.snapshot.meta.id)) { if (idMap.has(payload.snapshot.meta.id)) {
@@ -140,7 +140,7 @@ export const replaceIdMiddleware =
payload.snapshot.meta.id = newId; payload.snapshot.meta.id = newId;
}); });
slots.beforeImport const beforeImportBlockSubscription = slots.beforeImport
.pipe( .pipe(
filter( filter(
(payload): payload is BeforeImportBlockPayload => (payload): payload is BeforeImportBlockPayload =>
@@ -244,4 +244,13 @@ export const replaceIdMiddleware =
}); });
} }
}); });
return () => {
afterImportBlockSubscription.unsubscribe();
replaceLinkedPageIdSubscription.unsubscribe();
replaceSurfaceRefIdSubscription.unsubscribe();
replaceLinkedDocIdSubscription.unsubscribe();
beforeImportPageSubscription.unsubscribe();
beforeImportBlockSubscription.unsubscribe();
};
}; };

View File

@@ -5,12 +5,15 @@ export const surfaceRefToEmbed =
(std: BlockStdScope): TransformerMiddleware => (std: BlockStdScope): TransformerMiddleware =>
({ slots }) => { ({ slots }) => {
let pageId: string | null = null; let pageId: string | null = null;
slots.beforeImport.subscribe(payload => { const beforeImportSliceSubscription = slots.beforeImport.subscribe(
payload => {
if (payload.type === 'slice') { if (payload.type === 'slice') {
pageId = payload.snapshot.pageId; pageId = payload.snapshot.pageId;
} }
}); }
slots.beforeImport.subscribe(payload => { );
const beforeImportBlockSubscription = slots.beforeImport.subscribe(
payload => {
// only handle surface-ref block snapshot // only handle surface-ref block snapshot
if ( if (
payload.type !== 'block' || payload.type !== 'block' ||
@@ -33,5 +36,11 @@ export const surfaceRefToEmbed =
}, },
}; };
} }
}); }
);
return () => {
beforeImportSliceSubscription.unsubscribe();
beforeImportBlockSubscription.unsubscribe();
};
}; };

View File

@@ -3,9 +3,13 @@ import type { DocMeta, TransformerMiddleware } from '@blocksuite/store';
export const titleMiddleware = export const titleMiddleware =
(metas: DocMeta[]): TransformerMiddleware => (metas: DocMeta[]): TransformerMiddleware =>
({ slots, adapterConfigs }) => { ({ slots, adapterConfigs }) => {
slots.beforeExport.subscribe(() => { const beforeExportSubscription = slots.beforeExport.subscribe(() => {
for (const meta of metas) { for (const meta of metas) {
adapterConfigs.set('title:' + meta.id, meta.title); adapterConfigs.set('title:' + meta.id, meta.title);
} }
}); });
return () => {
beforeExportSubscription.unsubscribe();
};
}; };

View File

@@ -79,7 +79,7 @@ export const uploadMiddleware = (
} }
} }
blockView$ const blockViewSubscription = blockView$
.pipe( .pipe(
map(payload => { map(payload => {
if (assetsManager.uploadingAssetsMap.size === 0) return null; if (assetsManager.uploadingAssetsMap.size === 0) return null;
@@ -110,5 +110,9 @@ export const uploadMiddleware = (
) )
) )
.subscribe(); .subscribe();
return () => {
blockViewSubscription.unsubscribe();
};
}; };
}; };

View File

@@ -40,7 +40,7 @@ export const gfxBlocksFilter = (
} }
return ({ slots, transformerConfigs }) => { return ({ slots, transformerConfigs }) => {
slots.beforeExport.subscribe(payload => { const beforeExportSubscription = slots.beforeExport.subscribe(payload => {
if (payload.type !== 'block') { if (payload.type !== 'block') {
return; return;
} }
@@ -54,7 +54,7 @@ export const gfxBlocksFilter = (
} }
}); });
slots.afterExport.subscribe(payload => { const afterExportSubscription = slots.afterExport.subscribe(payload => {
if (payload.type !== 'block') { if (payload.type !== 'block') {
return; return;
} }
@@ -110,5 +110,10 @@ export const gfxBlocksFilter = (
}); });
} }
}); });
return () => {
beforeExportSubscription.unsubscribe();
afterExportSubscription.unsubscribe();
};
}; };
}; };

View File

@@ -9,7 +9,8 @@ export const newIdCrossDoc =
let samePage = false; let samePage = false;
const oldToNewIdMap = new Map<string, string>(); const oldToNewIdMap = new Map<string, string>();
slots.beforeImport.subscribe(payload => { const beforeImportSliceSubscription = slots.beforeImport.subscribe(
payload => {
if (payload.type === 'slice') { if (payload.type === 'slice') {
samePage = payload.snapshot.pageId === std.store.id; samePage = payload.snapshot.pageId === std.store.id;
} }
@@ -19,9 +20,11 @@ export const newIdCrossDoc =
oldToNewIdMap.set(payload.snapshot.id, newId); oldToNewIdMap.set(payload.snapshot.id, newId);
payload.snapshot.id = newId; payload.snapshot.id = newId;
} }
}); }
);
slots.afterImport.subscribe(payload => { const afterImportBlockSubscription = slots.afterImport.subscribe(
payload => {
if ( if (
!samePage && !samePage &&
payload.type === 'block' && payload.type === 'block' &&
@@ -40,5 +43,11 @@ export const newIdCrossDoc =
payload.model.props.cells$.value = newCells; payload.model.props.cells$.value = newCells;
} }
}); }
);
return () => {
beforeImportSliceSubscription.unsubscribe();
afterImportBlockSubscription.unsubscribe();
};
}; };

View File

@@ -7,7 +7,8 @@ import type { TransformerMiddleware } from '@blocksuite/store';
export const reorderList = export const reorderList =
(std: BlockStdScope): TransformerMiddleware => (std: BlockStdScope): TransformerMiddleware =>
({ slots }) => { ({ slots }) => {
slots.afterImport.subscribe(payload => { const afterImportBlockSubscription = slots.afterImport.subscribe(
payload => {
if (payload.type === 'block') { if (payload.type === 'block') {
const model = payload.model; const model = payload.model;
if ( if (
@@ -21,5 +22,10 @@ export const reorderList =
} }
} }
} }
}); }
);
return () => {
afterImportBlockSubscription.unsubscribe();
};
}; };

View File

@@ -113,6 +113,8 @@ type TransformerMiddlewareOptions = {
transformerConfigs: Map<string, unknown>; transformerConfigs: Map<string, unknown>;
}; };
type TransformerMiddlewareCleanup = () => void;
export type TransformerMiddleware = ( export type TransformerMiddleware = (
options: TransformerMiddlewareOptions options: TransformerMiddlewareOptions
) => void; ) => void | TransformerMiddlewareCleanup;

View File

@@ -1,3 +1,4 @@
import { DisposableGroup } from '@blocksuite/global/disposable';
import { BlockSuiteError, ErrorCode } from '@blocksuite/global/exceptions'; import { BlockSuiteError, ErrorCode } from '@blocksuite/global/exceptions';
import { nextTick } from '@blocksuite/global/utils'; import { nextTick } from '@blocksuite/global/utils';
import { Subject } from 'rxjs'; import { Subject } from 'rxjs';
@@ -67,6 +68,8 @@ export class Transformer {
private readonly _docCRUD: DocCRUD; private readonly _docCRUD: DocCRUD;
private readonly _disposables: DisposableGroup = new DisposableGroup();
private readonly _slots: TransformerSlots = { private readonly _slots: TransformerSlots = {
beforeImport: new Subject<BeforeImportPayload>(), beforeImport: new Subject<BeforeImportPayload>(),
afterImport: new Subject<AfterImportPayload>(), afterImport: new Subject<AfterImportPayload>(),
@@ -366,13 +369,16 @@ export class Transformer {
this._docCRUD = docCRUD; this._docCRUD = docCRUD;
middlewares.forEach(middleware => { middlewares.forEach(middleware => {
middleware({ const cleanup = middleware({
slots: this._slots, slots: this._slots,
docCRUD: this._docCRUD, docCRUD: this._docCRUD,
assetsManager: this._assetsManager, assetsManager: this._assetsManager,
adapterConfigs: this._adapterConfigs, adapterConfigs: this._adapterConfigs,
transformerConfigs: this._transformerConfigs, transformerConfigs: this._transformerConfigs,
}); });
if (cleanup) {
this._disposables.add(cleanup);
}
}); });
} }
@@ -646,4 +652,9 @@ export class Transformer {
reset() { reset() {
this._assetsManager.cleanup(); this._assetsManager.cleanup();
} }
[Symbol.dispose]() {
this._disposables.dispose();
this._assetsManager.cleanup();
}
} }