diff --git a/examples/esbuild.config.mjs b/examples/esbuild.config.mjs index 33903b39b7ba..206ffc951348 100644 --- a/examples/esbuild.config.mjs +++ b/examples/esbuild.config.mjs @@ -44,7 +44,7 @@ const ctx = await esbuild[args.watch ? 'context' : 'build']({ color: true, loader: { '.svg': 'file', '.ttf': 'file' }, sourcemap: args.watch, - minify: !args.watch, + minify: false, target: 'chrome70', plugins: [ copyPlugin({ diff --git a/examples/src/sheets/lazy.ts b/examples/src/sheets/lazy.ts index f4f17dd6e5e9..a58835af8c3f 100644 --- a/examples/src/sheets/lazy.ts +++ b/examples/src/sheets/lazy.ts @@ -17,22 +17,22 @@ import type { Plugin, PluginCtor } from '@univerjs/core'; import { UniverSheetsFilterUIPlugin } from '@univerjs/sheets-filter-ui'; import { UniverSheetsFindReplacePlugin } from '@univerjs/sheets-find-replace'; -import { UniverUniscriptPlugin } from '@univerjs/uniscript'; +// import { UniverUniscriptPlugin } from '@univerjs/uniscript'; export default function getLazyPlugins(): Array<[PluginCtor] | [PluginCtor, unknown]> { return [ - [ - UniverUniscriptPlugin, - { - getWorkerUrl(moduleID: string, label: string) { - if (label === 'typescript' || label === 'javascript') { - return './vs/language/typescript/ts.worker.js'; - } + // [ + // UniverUniscriptPlugin, + // { + // getWorkerUrl(moduleID: string, label: string) { + // if (label === 'typescript' || label === 'javascript') { + // return './vs/language/typescript/ts.worker.js'; + // } - return './vs/editor/editor.worker.js'; - }, - }, - ], + // return './vs/editor/editor.worker.js'; + // }, + // }, + // ], [UniverSheetsFilterUIPlugin], [UniverSheetsFindReplacePlugin], ]; diff --git a/packages/core/src/common/request-immediate-macro-task.ts b/packages/core/src/common/request-immediate-macro-task.ts index 68b45028fab8..0744d48c106d 100644 --- a/packages/core/src/common/request-immediate-macro-task.ts +++ b/packages/core/src/common/request-immediate-macro-task.ts @@ -18,18 +18,18 @@ export function requestImmediateMacroTask(callback: (value?: unknown) => void): const channel = new MessageChannel(); let cancelled = false; - channel.port1.onmessage = () => { + const hanlder = () => { if (!cancelled) { callback(); } }; + // This would cause memory leak. But we cannot use addEventListener because it won't work in web worker. + channel.port1.onmessage = hanlder; channel.port2.postMessage(null); return () => { cancelled = true; - // dispose - channel.port1.onmessage = null; channel.port1.close(); channel.port2.close(); }; diff --git a/packages/core/src/services/command/command.service.ts b/packages/core/src/services/command/command.service.ts index 4b3a0d0701a2..8e6d512f6b46 100644 --- a/packages/core/src/services/command/command.service.ts +++ b/packages/core/src/services/command/command.service.ts @@ -19,7 +19,7 @@ import { createIdentifier, Inject, Injector } from '@wendellhu/redi'; import { findLast, remove } from '../../common/array'; import { sequence, sequenceAsync } from '../../common/sequence'; -import { DisposableCollection, toDisposable } from '../../shared/lifecycle'; +import { Disposable, DisposableCollection, toDisposable } from '../../shared/lifecycle'; import type { IKeyValue } from '../../shared/types'; import { IContextService } from '../context/context.service'; import { ILogService } from '../log/log.service'; @@ -215,7 +215,7 @@ export const NilCommand: ICommand = { handler: () => true, }; -export class CommandService implements ICommandService { +export class CommandService extends Disposable implements ICommandService { protected readonly _commandRegistry: CommandRegistry; private readonly _beforeCommandExecutionListeners: CommandListener[] = []; @@ -231,10 +231,19 @@ export class CommandService implements ICommandService { @Inject(Injector) private readonly _injector: Injector, @ILogService private readonly _logService: ILogService ) { + super(); + this._commandRegistry = new CommandRegistry(); this._registerCommand(NilCommand); } + override dispose(): void { + super.dispose(); + + this._commandExecutedListeners.length = 0; + this._beforeCommandExecutionListeners.length = 0; + } + hasCommand(commandId: string): boolean { return this._commandRegistry.hasCommand(commandId); } diff --git a/packages/debugger/package.json b/packages/debugger/package.json index fa8209f329ab..6d08260864a4 100644 --- a/packages/debugger/package.json +++ b/packages/debugger/package.json @@ -58,6 +58,7 @@ "@univerjs/core": "workspace:*", "@univerjs/design": "workspace:*", "@univerjs/engine-render": "workspace:*", + "@univerjs/facade": "workspace:*", "@univerjs/sheets": "workspace:*", "@univerjs/sheets-drawing-ui": "workspace:*", "@univerjs/ui": "workspace:*", @@ -71,6 +72,7 @@ "devDependencies": { "@univerjs/core": "workspace:*", "@univerjs/engine-render": "workspace:*", + "@univerjs/facade": "workspace:*", "@univerjs/shared": "workspace:*", "@univerjs/sheets": "workspace:*", "@univerjs/sheets-drawing-ui": "workspace:*", diff --git a/packages/debugger/src/commands/commands/unit.command.ts b/packages/debugger/src/commands/commands/unit.command.ts index 03f19462dfba..ee3d0416d420 100644 --- a/packages/debugger/src/commands/commands/unit.command.ts +++ b/packages/debugger/src/commands/commands/unit.command.ts @@ -15,11 +15,13 @@ */ import { CommandType, type ICommand, IUniverInstanceService, type Univer, UniverInstanceType } from '@univerjs/core'; +import type { FUniver } from '@univerjs/facade'; declare global { // eslint-disable-next-line ts/naming-convention interface Window { univer?: Univer; + univerAPI?: ReturnType; } } @@ -28,6 +30,8 @@ export const DisposeUniverCommand: ICommand = { type: CommandType.COMMAND, handler: () => { window.univer?.dispose(); + window.univer = undefined; + window.univerAPI = undefined; return true; }, diff --git a/packages/debugger/src/controllers/e2e/e2e-memory.controller.ts b/packages/debugger/src/controllers/e2e/e2e-memory.controller.ts index 3e9b786062cc..3a5b78166017 100644 --- a/packages/debugger/src/controllers/e2e/e2e-memory.controller.ts +++ b/packages/debugger/src/controllers/e2e/e2e-memory.controller.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { ICommandService, IUniverInstanceService, LifecycleStages, LocaleType, OnLifecycle, UniverInstanceType } from '@univerjs/core'; +import { Disposable, ICommandService, IUniverInstanceService, LifecycleStages, LocaleType, OnLifecycle, UniverInstanceType } from '@univerjs/core'; import { DisposeUniverCommand } from '../../commands/commands/unit.command'; const DEFAULT_WORKBOOK_DATA_DEMO = { @@ -80,14 +80,21 @@ declare global { * This controller expose a API on `Window` for the E2E memory test. */ @OnLifecycle(LifecycleStages.Starting, E2EMemoryController) -export class E2EMemoryController { +export class E2EMemoryController extends Disposable { constructor( @IUniverInstanceService private readonly _univerInstanceService: IUniverInstanceService, @ICommandService private readonly _commandService: ICommandService ) { + super(); + this._initPlugin(); } + override dispose(): void { + // @ts-ignore + window.E2EControllerAPI = undefined; + } + private _initPlugin(): void { window.E2EControllerAPI = { loadAndRelease: (id) => this._releaseAndLoad(id), diff --git a/packages/engine-render/src/components/docs/text-selection/text-selection-render-manager.ts b/packages/engine-render/src/components/docs/text-selection/text-selection-render-manager.ts index 1674bf6a39f0..6a0475ea2116 100644 --- a/packages/engine-render/src/components/docs/text-selection/text-selection-render-manager.ts +++ b/packages/engine-render/src/components/docs/text-selection/text-selection-render-manager.ts @@ -651,7 +651,7 @@ export class TextSelectionRenderManager extends RxDisposable implements ITextSel super.dispose(); this._detachEvent(); - + this._removeAllTextRanges(); this._container.remove(); } diff --git a/packages/engine-render/src/engine.ts b/packages/engine-render/src/engine.ts index b33d4f950c42..2c5fb869c729 100644 --- a/packages/engine-render/src/engine.ts +++ b/packages/engine-render/src/engine.ts @@ -278,6 +278,9 @@ export class Engine extends ThinEngine { this._beginFrame$.complete(); this._endFrame$.complete(); + + this._resizeObserver?.disconnect(); + this._container = null; } /** diff --git a/packages/engine-render/src/layer.ts b/packages/engine-render/src/layer.ts index 7feab78cdfdf..5721360618c7 100644 --- a/packages/engine-render/src/layer.ts +++ b/packages/engine-render/src/layer.ts @@ -301,6 +301,7 @@ export class Layer extends Disposable { }); this.clear(); + this._debounceDirtyFunc?.(); this._debounceDirtyFunc = null; this._cacheCanvas?.dispose(); diff --git a/packages/rpc/src/services/rpc/channel.service.ts b/packages/rpc/src/services/rpc/channel.service.ts index adac4d8eae52..5d23eaa8b61c 100644 --- a/packages/rpc/src/services/rpc/channel.service.ts +++ b/packages/rpc/src/services/rpc/channel.service.ts @@ -14,6 +14,7 @@ * limitations under the License. */ +import type { IDisposable } from '@wendellhu/redi'; import { createIdentifier } from '@wendellhu/redi'; import type { IChannel, IMessageProtocol } from './rpc.service'; @@ -29,7 +30,7 @@ export const IRPCChannelService = createIdentifier('IRPCChan /** * This service is responsible for managing the RPC channels. */ -export class ChannelService { +export class ChannelService implements IDisposable { private readonly _client: ChannelClient; private readonly _server: ChannelServer; @@ -38,6 +39,11 @@ export class ChannelService { this._server = new ChannelServer(_messageProtocol); } + dispose(): void { + this._client.dispose(); + this._server.dispose(); + } + requestChannel(name: string): IChannel { return this._client.getChannel(name); } diff --git a/packages/rpc/src/services/rpc/rpc.service.ts b/packages/rpc/src/services/rpc/rpc.service.ts index a06ecbd02e20..914cf41dcf3b 100644 --- a/packages/rpc/src/services/rpc/rpc.service.ts +++ b/packages/rpc/src/services/rpc/rpc.service.ts @@ -15,7 +15,7 @@ */ import { RxDisposable } from '@univerjs/core'; -import type { Subscriber, Subscription } from 'rxjs'; +import type { Subscription } from 'rxjs'; import { BehaviorSubject, firstValueFrom, isObservable, Observable, of } from 'rxjs'; import { filter, take, takeUntil } from 'rxjs/operators'; @@ -175,7 +175,6 @@ export class ChannelClient extends RxDisposable implements IChannelClient { private _initialized = new BehaviorSubject(false); private _lastRequestCounter = 0; private _pendingRequests = new Map(); - private _pendingSubscriptions = new Map>(); constructor(private readonly _protocol: IMessageProtocol) { super(); @@ -185,6 +184,10 @@ export class ChannelClient extends RxDisposable implements IChannelClient { this._protocol.onMessage.pipe(takeUntil(this.dispose$)).subscribe((message) => this._onMessage(message)); } + override dispose(): void { + this._pendingRequests.clear(); + } + getChannel(channelName: string): T { const self = this; @@ -326,6 +329,13 @@ export class ChannelServer extends RxDisposable implements IChannelServer { this._sendResponse({ seq: -1, type: ResponseType.INITIALIZE }); } + override dispose(): void { + super.dispose(); + + this._subscriptions.clear(); + this._channels.clear(); + } + registerChannel(channelName: string, channel: IChannel): void { this._channels.set(channelName, channel); } diff --git a/packages/sheets-conditional-formatting/src/services/conditional-formatting-formula.service.ts b/packages/sheets-conditional-formatting/src/services/conditional-formatting-formula.service.ts index 69eb45ea3328..b78665cf447d 100644 --- a/packages/sheets-conditional-formatting/src/services/conditional-formatting-formula.service.ts +++ b/packages/sheets-conditional-formatting/src/services/conditional-formatting-formula.service.ts @@ -79,6 +79,10 @@ export class ConditionalFormattingFormulaService extends Disposable { })); } + override dispose(): void { + this._formulaChange$.complete(); + } + private _initCache() { const _conditionalFormattingService = this._injector.get(ConditionalFormattingService); this.disposeWithMe(_conditionalFormattingService.ruleComputeStatus$.subscribe((option) => { diff --git a/packages/sheets-data-validation/src/controllers/dv.controller.ts b/packages/sheets-data-validation/src/controllers/dv.controller.ts index 951f0722c0e3..ec906e21a43c 100644 --- a/packages/sheets-data-validation/src/controllers/dv.controller.ts +++ b/packages/sheets-data-validation/src/controllers/dv.controller.ts @@ -84,7 +84,7 @@ export class DataValidationController extends RxDisposable { private _initInstanceChange() { const disposableCollection = new DisposableCollection(); - this._univerInstanceService.getCurrentTypeOfUnit$(UniverInstanceType.UNIVER_SHEET).subscribe((workbook) => { + this.disposeWithMe(this._univerInstanceService.getCurrentTypeOfUnit$(UniverInstanceType.UNIVER_SHEET).subscribe((workbook) => { disposableCollection.dispose(); if (!workbook) { return; @@ -104,7 +104,7 @@ export class DataValidationController extends RxDisposable { } }) )); - }); + })); this.disposeWithMe(disposableCollection); } diff --git a/packages/sheets-formula/src/services/register-other-formula.service.ts b/packages/sheets-formula/src/services/register-other-formula.service.ts index 259d4b3a9357..a00e3b626a9a 100644 --- a/packages/sheets-formula/src/services/register-other-formula.service.ts +++ b/packages/sheets-formula/src/services/register-other-formula.service.ts @@ -41,6 +41,13 @@ export class RegisterOtherFormulaService extends Disposable { this._initFormulaCalculationResultChange(); } + override dispose(): void { + super.dispose(); + + this._formulaChange$.complete(); + this._formulaResult$.complete(); + } + private _ensureCacheMap(unitId: string, subUnitId: string) { let unitMap = this._formulaCacheMap.get(unitId); diff --git a/packages/sheets-ui/src/services/selection/base-selection-render.service.ts b/packages/sheets-ui/src/services/selection/base-selection-render.service.ts index 43d5f21b7d61..72f0f935b03c 100644 --- a/packages/sheets-ui/src/services/selection/base-selection-render.service.ts +++ b/packages/sheets-ui/src/services/selection/base-selection-render.service.ts @@ -157,9 +157,8 @@ export class BaseSelectionRenderService extends Disposable implements ISheetSele protected readonly _renderManagerService: IRenderManagerService ) { super(); + this._resetStyle(); - // @ts-expect-error - if (!window.srs) window.srs = this; } protected _setStyle(style: ISelectionStyle) { diff --git a/packages/ui/src/common/component-manager.ts b/packages/ui/src/common/component-manager.ts index 79b607ef87ee..9ea8008af549 100644 --- a/packages/ui/src/common/component-manager.ts +++ b/packages/ui/src/common/component-manager.ts @@ -292,13 +292,10 @@ export class ComponentManager { async function renderVue3Component(VueComponent: ReturnType, element: HTMLElement, args: Record) { try { const { h, render } = await import('vue'); - const vnode = h(VueComponent, args); - const container = document.createElement('div'); document.body.appendChild(container); - render(vnode, element); } catch (error) { } diff --git a/packages/ui/src/controllers/ui/ui-desktop.controller.tsx b/packages/ui/src/controllers/ui/ui-desktop.controller.tsx index 246687f3eb83..165c3f63b272 100644 --- a/packages/ui/src/controllers/ui/ui-desktop.controller.tsx +++ b/packages/ui/src/controllers/ui/ui-desktop.controller.tsx @@ -130,13 +130,15 @@ function bootstrap( render(); return toDisposable(() => { - unmount(mountContainer); + // https://github.com/facebook/react/issues/26031 + createRoot(
, mountContainer); + setTimeout(() => createRoot(
, mountContainer), 200); + setTimeout(() => unmount(mountContainer), 500); }); } function createContainer(id: string): HTMLElement { const element = document.createElement('div'); element.id = id; - // FIXME: the element is not append to the DOM tree. So it won't be rendered. return element; } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8bff532ef01e..b4c90fd65320 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -473,6 +473,9 @@ importers: '@univerjs/engine-render': specifier: workspace:* version: link:../engine-render + '@univerjs/facade': + specifier: workspace:* + version: link:../facade '@univerjs/shared': specifier: workspace:* version: link:../../common/shared