From 95a1d4b7de01ab8560e0f115395bcadf232d24ee Mon Sep 17 00:00:00 2001 From: Rirax Date: Wed, 12 Jun 2024 07:37:12 +0200 Subject: [PATCH] fix(vue-3): faster component rendering (#5206) --- packages/vue-3/src/Editor.ts | 8 ++-- packages/vue-3/src/EditorContent.ts | 38 +++++----------- packages/vue-3/src/VueNodeViewRenderer.ts | 10 +++-- packages/vue-3/src/VueRenderer.ts | 55 ++++++++++++++--------- 4 files changed, 57 insertions(+), 54 deletions(-) diff --git a/packages/vue-3/src/Editor.ts b/packages/vue-3/src/Editor.ts index 0933a7992..691eec2ff 100644 --- a/packages/vue-3/src/Editor.ts +++ b/packages/vue-3/src/Editor.ts @@ -1,16 +1,14 @@ import { Editor as CoreEditor, EditorOptions } from '@tiptap/core' import { EditorState, Plugin, PluginKey } from '@tiptap/pm/state' import { + AppContext, ComponentInternalInstance, ComponentPublicInstance, customRef, markRaw, - reactive, Ref, } from 'vue' -import { VueRenderer } from './VueRenderer.js' - function useDebouncedRef(value: T) { return customRef((track, trigger) => { return { @@ -42,10 +40,10 @@ export class Editor extends CoreEditor { private reactiveExtensionStorage: Ref> - public vueRenderers = reactive>(new Map()) - public contentComponent: ContentComponent | null = null + public appContext: AppContext | null = null + constructor(options: Partial = {}) { super(options) diff --git a/packages/vue-3/src/EditorContent.ts b/packages/vue-3/src/EditorContent.ts index 09f1b51c4..7c215c3c9 100644 --- a/packages/vue-3/src/EditorContent.ts +++ b/packages/vue-3/src/EditorContent.ts @@ -1,5 +1,4 @@ import { - DefineComponent, defineComponent, getCurrentInstance, h, @@ -8,7 +7,6 @@ import { PropType, Ref, ref, - Teleport, unref, watchEffect, } from 'vue' @@ -45,6 +43,17 @@ export const EditorContent = defineComponent({ // @ts-ignore editor.contentComponent = instance.ctx._ + if (instance) { + editor.appContext = { + ...instance.appContext, + provides: { + // @ts-ignore + ...instance.provides, + ...instance.appContext.provides, + }, + } + } + editor.setOptions({ element, }) @@ -69,6 +78,7 @@ export const EditorContent = defineComponent({ } editor.contentComponent = null + editor.appContext = null if (!editor.options.element.firstChild) { return @@ -87,35 +97,11 @@ export const EditorContent = defineComponent({ }, render() { - const vueRenderers: any[] = [] - - if (this.editor) { - this.editor.vueRenderers.forEach(vueRenderer => { - const node = h( - Teleport, - { - to: vueRenderer.teleportElement, - key: vueRenderer.id, - }, - h( - vueRenderer.component as DefineComponent, - { - ref: vueRenderer.id, - ...vueRenderer.props, - }, - ), - ) - - vueRenderers.push(node) - }) - } - return h( 'div', { ref: (el: any) => { this.rootEl = el }, }, - ...vueRenderers, ) }, }) diff --git a/packages/vue-3/src/VueNodeViewRenderer.ts b/packages/vue-3/src/VueNodeViewRenderer.ts index a9f9c448d..201e051e0 100644 --- a/packages/vue-3/src/VueNodeViewRenderer.ts +++ b/packages/vue-3/src/VueNodeViewRenderer.ts @@ -124,7 +124,7 @@ class VueNodeView extends NodeView, } +type ExtendedVNode = ReturnType | null + +interface RenderedComponent { + vNode: ExtendedVNode + destroy: () => void + el: Element | null +} + /** * This class is used to render Vue components inside the editor. */ export class VueRenderer { id: string + renderedComponent!: RenderedComponent + editor: ExtendedEditor component: Component - teleportElement: Element - - element: Element + el: Element | null props: Record @@ -28,35 +38,40 @@ export class VueRenderer { this.id = Math.floor(Math.random() * 0xFFFFFFFF).toString() this.editor = editor as ExtendedEditor this.component = markRaw(component) - this.teleportElement = document.createElement('div') - this.element = this.teleportElement + this.el = document.createElement('div') this.props = reactive(props) - this.editor.vueRenderers.set(this.id, this) - - if (this.editor.contentComponent) { - this.editor.contentComponent.update() - - if (this.teleportElement.children.length !== 1) { - throw Error('VueRenderer doesn’t support multiple child elements.') - } - - this.element = this.teleportElement.firstElementChild as Element - } + this.renderedComponent = this.renderComponent() } - get ref(): any { - return this.editor.contentComponent?.refs[this.id] + get element(): Element | null { + return this.renderedComponent.el + } + + renderComponent() { + let vNode: ExtendedVNode = h(this.component as DefineComponent, this.props) + + if (typeof document !== 'undefined' && this.el) { render(vNode, this.el) } + + const destroy = () => { + if (this.el) { render(null, this.el) } + this.el = null + vNode = null + } + + return { vNode, destroy, el: this.el ? this.el.firstElementChild : null } } updateProps(props: Record = {}): void { + Object .entries(props) .forEach(([key, value]) => { this.props[key] = value }) + this.renderComponent() } destroy(): void { - this.editor.vueRenderers.delete(this.id) + this.renderedComponent.destroy() } }