From d136f44e94d550b642e2597720024de8878e7809 Mon Sep 17 00:00:00 2001 From: zjxxxxxxxxx <43126836+zjxxxxxxxxx@users.noreply.github.com> Date: Mon, 18 Sep 2023 23:43:00 +0800 Subject: [PATCH] feat: support integration with unplugin-vue-source (#75) --- .changeset/kind-donkeys-eat.md | 10 ++ .../client/src/resolve/framework/react.ts | 125 ------------------ .../client/src/resolve/framework/react15.ts | 65 +++++++++ .../client/src/resolve/framework/react18.ts | 59 +++++++++ packages/client/src/resolve/framework/vue.ts | 62 --------- packages/client/src/resolve/framework/vue2.ts | 104 +++++++++++++++ packages/client/src/resolve/framework/vue3.ts | 102 ++++++++++++++ packages/client/src/resolve/resolveDebug.ts | 4 +- packages/client/src/resolve/resolveSource.ts | 19 ++- pnpm-lock.yaml | 6 + 10 files changed, 358 insertions(+), 198 deletions(-) create mode 100644 .changeset/kind-donkeys-eat.md delete mode 100644 packages/client/src/resolve/framework/react.ts create mode 100644 packages/client/src/resolve/framework/react15.ts create mode 100644 packages/client/src/resolve/framework/react18.ts delete mode 100644 packages/client/src/resolve/framework/vue.ts create mode 100644 packages/client/src/resolve/framework/vue2.ts create mode 100644 packages/client/src/resolve/framework/vue3.ts diff --git a/.changeset/kind-donkeys-eat.md b/.changeset/kind-donkeys-eat.md new file mode 100644 index 00000000..8b2ba6d9 --- /dev/null +++ b/.changeset/kind-donkeys-eat.md @@ -0,0 +1,10 @@ +--- +'@open-editor/client': patch +'@open-editor/rollup': patch +'@open-editor/server': patch +'@open-editor/shared': patch +'@open-editor/vite': patch +'@open-editor/webpack': patch +--- + +support integration with unplugin-vue-source diff --git a/packages/client/src/resolve/framework/react.ts b/packages/client/src/resolve/framework/react.ts deleted file mode 100644 index 6d4cc4c3..00000000 --- a/packages/client/src/resolve/framework/react.ts +++ /dev/null @@ -1,125 +0,0 @@ -import type { Fiber } from 'react-reconciler'; -import { isFunc } from '@open-editor/shared'; - -import { ResolveDebug } from '../resolveDebug'; -import { ElementSourceMeta } from '../resolveSource'; -import { isValidFileName } from '../util'; - -export function resolveSourceFromReact( - debug: ResolveDebug, - deep?: boolean, -) { - return resolveSourceFromFiber(debug.value, deep); -} - -export function resolveSourceFromReact15( - { value: instance }: ResolveDebug, - deep?: boolean, -) { - if (instance && '_debugOwner' in instance) { - return resolveSourceFromFiber(instance, deep); - } - return resolveSourceFromInstance(instance, deep); -} - -export function resolveSourceFromFiber(fiber?: Fiber | null, deep?: boolean) { - const tree: Partial[] = []; - - while (fiber) { - let owner = fiber._debugOwner; - - const source = fiber._debugSource; - if (source && isValidFileName(source.fileName)) { - while (!isFiberComponent(owner)) { - if (!owner) { - return tree; - } - owner = owner._debugOwner; - } - - tree.push({ - name: getFiberComponentName(owner as Fiber), - file: source.fileName, - line: source.lineNumber, - column: (source).columnNumber, - }); - - if (!deep) { - return tree; - } - } - - fiber = owner; - } - - function isFiberComponent(owner?: Fiber | null) { - if (owner && owner._debugSource) { - return isFunc(owner.type) || isFunc(owner.type.render); - } - return false; - } - - function getFiberComponentName(owner: Fiber) { - const component = isFunc(owner.type) - ? owner.type - : // React.forwardRef(Component) - owner.type.render; - return component?.name || component?.displayName; - } - - return tree; -} - -export function resolveSourceFromInstance( - instance?: any | null, - deep?: boolean, -) { - const tree: Partial[] = []; - - while (instance) { - let owner = instance._currentElement._owner; - - const source = instance._currentElement._source; - if (source && isValidFileName(source.fileName)) { - while (!isInstanceComponent(owner)) { - if (!owner) { - return tree; - } - owner = owner._currentElement._owner; - } - - tree.push({ - name: getInstanceComponentName(owner), - file: source.fileName, - line: source.lineNumber, - column: (source).columnNumber, - }); - - if (!deep) { - return tree; - } - } - - instance = owner; - } - - function isInstanceComponent(owner?: any | null) { - if (owner && owner._currentElement) { - return ( - isFunc(owner._currentElement.type) || - isFunc(owner._currentElement.type.render) - ); - } - return false; - } - - function getInstanceComponentName(owner: any) { - const component = isFunc(owner._currentElement.type) - ? owner._currentElement.type - : // React.forwardRef(Component) - owner._currentElement.type.render; - return component?.name || component?.displayName; - } - - return tree; -} diff --git a/packages/client/src/resolve/framework/react15.ts b/packages/client/src/resolve/framework/react15.ts new file mode 100644 index 00000000..1ebcddd8 --- /dev/null +++ b/packages/client/src/resolve/framework/react15.ts @@ -0,0 +1,65 @@ +import { isFunc } from '@open-editor/shared'; + +import { ResolveDebug } from '../resolveDebug'; +import { ElementSourceMeta } from '../resolveSource'; +import { isValidFileName } from '../util'; +import { resolveSourceFromFiber } from './react18'; + +export function resolveReact15( + { value: instance }: ResolveDebug, + tree: Partial[], + deep?: boolean, +) { + if (instance && '_debugOwner' in instance) { + return resolveSourceFromFiber(instance, tree, deep); + } + return resolveSourceFromInstance(instance, tree, deep); +} + +export function resolveSourceFromInstance( + instance: any | null | undefined, + tree: Partial[], + deep?: boolean, +) { + while (instance) { + let owner = instance._currentElement._owner; + + const source = instance._currentElement._source; + if (source && isValidFileName(source.fileName)) { + while (!isInstanceComponent(owner)) { + if (!owner) return; + + owner = owner._currentElement._owner; + } + + tree.push({ + name: getInstanceComponentName(owner), + file: source.fileName, + line: source.lineNumber, + column: (source).columnNumber, + }); + + if (!deep) return; + } + + instance = owner; + } +} + +function isInstanceComponent(owner?: any | null) { + if (owner && owner._currentElement) { + return ( + isFunc(owner._currentElement.type) || + isFunc(owner._currentElement.type.render) + ); + } + return false; +} + +function getInstanceComponentName(owner: any) { + const component = isFunc(owner._currentElement.type) + ? owner._currentElement.type + : // React.forwardRef(Component) + owner._currentElement.type.render; + return component?.name || component?.displayName; +} diff --git a/packages/client/src/resolve/framework/react18.ts b/packages/client/src/resolve/framework/react18.ts new file mode 100644 index 00000000..8af24de8 --- /dev/null +++ b/packages/client/src/resolve/framework/react18.ts @@ -0,0 +1,59 @@ +import type { Fiber } from 'react-reconciler'; +import { isFunc } from '@open-editor/shared'; + +import { ResolveDebug } from '../resolveDebug'; +import { ElementSourceMeta } from '../resolveSource'; +import { isValidFileName } from '../util'; + +export function resolveReact18( + debug: ResolveDebug, + tree: Partial[], + deep?: boolean, +) { + return resolveSourceFromFiber(debug.value, tree, deep); +} + +export function resolveSourceFromFiber( + fiber: Fiber | null | undefined, + tree: Partial[], + deep?: boolean, +) { + while (fiber) { + let owner = fiber._debugOwner; + + const source = fiber._debugSource; + if (source && isValidFileName(source.fileName)) { + while (!isFiberComponent(owner)) { + if (!owner) return; + + owner = owner._debugOwner; + } + + tree.push({ + name: getFiberComponentName(owner as Fiber), + file: source.fileName, + line: source.lineNumber, + column: (source).columnNumber, + }); + + if (!deep) return; + } + + fiber = owner; + } +} + +function isFiberComponent(owner?: Fiber | null) { + if (owner && owner._debugSource) { + return isFunc(owner.type) || isFunc(owner.type.render); + } + return false; +} + +function getFiberComponentName(owner: Fiber) { + const component = isFunc(owner.type) + ? owner.type + : // React.forwardRef(Component) + owner.type.render; + return component?.name || component?.displayName; +} diff --git a/packages/client/src/resolve/framework/vue.ts b/packages/client/src/resolve/framework/vue.ts deleted file mode 100644 index 9ad57b95..00000000 --- a/packages/client/src/resolve/framework/vue.ts +++ /dev/null @@ -1,62 +0,0 @@ -import { ComponentInternalInstance } from '@vue/runtime-core'; - -import { getComponentNameByFile, isValidFileName } from '../util'; -import { ResolveDebug } from '../resolveDebug'; -import { ElementSourceMeta } from '../resolveSource'; - -export function resolveSourceFromVue( - { value: instance }: ResolveDebug, - deep?: boolean, -) { - const tree: Partial[] = []; - - while (instance) { - if (isValidFileName(instance.type.__file)) { - tree.push({ - name: - instance.type.name ?? - instance.type.__name ?? - getComponentNameByFile(instance.type.__file, 'vue'), - file: instance.type.__file, - }); - - if (!deep) { - return tree; - } - } - - instance = instance.parent; - } - - return tree; -} - -export function resolveSourceFromVue2( - { value: instance }: ResolveDebug, - deep?: boolean, -) { - const tree: Partial[] = []; - - if (instance._vnode.componentInstance) { - instance = instance._vnode.componentInstance; - } - - while (instance && instance.$vnode) { - const { Ctor } = instance.$vnode.componentOptions; - const __file = Ctor?.__file ?? Ctor.options?.__file; - if (isValidFileName(__file)) { - tree.push({ - name: Ctor.options?.name ?? getComponentNameByFile(__file, 'vue'), - file: __file, - }); - - if (!deep) { - return tree; - } - } - - instance = instance.$parent; - } - - return tree; -} diff --git a/packages/client/src/resolve/framework/vue2.ts b/packages/client/src/resolve/framework/vue2.ts new file mode 100644 index 00000000..c8d36755 --- /dev/null +++ b/packages/client/src/resolve/framework/vue2.ts @@ -0,0 +1,104 @@ +import { getComponentNameByFile, isValidFileName } from '../util'; +import { ResolveDebug } from '../resolveDebug'; +import { ElementSourceMeta } from '../resolveSource'; +import { parse__source } from './vue3'; + +export function resolveVue2( + debug: ResolveDebug, + tree: Partial[], + deep?: boolean, +) { + if (debug.value._vnode.componentInstance) { + debug.value = debug.value._vnode.componentInstance; + } + + const source = find__source(debug); + if (source) { + return resolveSourceFrom__source({ source, ...debug }, tree, deep); + } + return resolveSourceFromInstance(debug.value, tree, deep); +} + +function resolveSourceFrom__source( + debug: ResolveDebug & { + source: ReturnType; + }, + tree: Partial[], + deep?: boolean, +) { + let instance = debug.value; + let source = debug.source; + console.log(source); + while (instance) { + if (instance.$parent == null) { + tree.push({ + name: getComponentName(instance), + ...source, + }); + return; + } else if (get__source(instance)) { + const instanceSource = parse__source(get__source(instance)); + if ( + source.file !== instanceSource.file && + isValidFileName(instanceSource.file) + ) { + tree.push({ + name: getComponentName(instance), + ...source, + }); + if (!deep) return; + + source = instanceSource; + } + } + instance = instance.$parent; + } +} + +function find__source(debug: ResolveDebug) { + let element = debug.originalElement; + while (element && !element.getAttribute('__source')) { + element = element.parentElement!; + } + if (element?.getAttribute('__source')) { + return parse__source(element.getAttribute('__source')!); + } + + let instance = debug.value; + while (instance) { + if (get__source(instance)) { + return parse__source(get__source(instance)); + } + instance = instance.$parent; + } +} + +function resolveSourceFromInstance( + instance: any | null | undefined, + tree: Partial[], + deep?: boolean, +) { + while (instance && instance.$vnode) { + const { Ctor } = instance.$vnode.componentOptions; + const __file = Ctor?.__file ?? Ctor.options?.__file; + if (isValidFileName(__file)) { + tree.push({ + name: getComponentName(Ctor), + file: __file, + }); + + if (!deep) return; + } + + instance = instance.$parent; + } +} + +function get__source(instance: any) { + return instance.componentInstance?.$props?.__source; +} + +function getComponentName(Ctor: any) { + const __file = Ctor?.__file ?? Ctor.options?.__file; + return Ctor.options?.name ?? getComponentNameByFile(__file, 'vue'); +} diff --git a/packages/client/src/resolve/framework/vue3.ts b/packages/client/src/resolve/framework/vue3.ts new file mode 100644 index 00000000..477af355 --- /dev/null +++ b/packages/client/src/resolve/framework/vue3.ts @@ -0,0 +1,102 @@ +import { ComponentInternalInstance } from '@vue/runtime-core'; + +import { getComponentNameByFile, isValidFileName } from '../util'; +import { ResolveDebug } from '../resolveDebug'; +import { ElementSourceMeta } from '../resolveSource'; + +export function resolveVue3( + debug: ResolveDebug, + tree: Partial[], + deep?: boolean, +) { + const source = find__source(debug); + if (source) { + return resolveSourceFrom__source({ source, ...debug }, tree, deep); + } + return resolveSourceFromInstance(debug.value, tree, deep); +} + +function resolveSourceFrom__source( + debug: ResolveDebug & { + source: ReturnType; + }, + tree: Partial[], + deep?: boolean, +) { + let instance = debug.value; + let source = debug.source; + while (instance) { + if (instance.parent == null) { + tree.push({ + name: getComponentName(instance), + ...source, + }); + return; + } else if (instance.props.__source) { + const instanceSource = parse__source(instance.props.__source as string); + if ( + source.file !== instanceSource.file && + isValidFileName(instanceSource.file) + ) { + tree.push({ + name: getComponentName(instance), + ...source, + }); + if (!deep) return; + + source = instanceSource; + } + } + instance = instance.parent; + } +} + +function find__source(debug: ResolveDebug) { + let instance = debug.value; + + const __source = debug.element.getAttribute('__source'); + if (__source) { + return parse__source(__source); + } + + while (instance) { + if (instance.props.__source) { + return parse__source(instance.props.__source as string); + } + instance = instance.parent; + } +} + +export function parse__source(__source: string) { + const [file, line, column] = __source.split(':'); + return { + file, + line: Number(line), + column: Number(column), + }; +} + +function resolveSourceFromInstance( + instance: ComponentInternalInstance | null | undefined, + tree: Partial[], + deep?: boolean, +) { + while (instance) { + if (isValidFileName(instance.type.__file)) { + tree.push({ + name: getComponentName(instance), + file: instance.type.__file, + }); + if (!deep) return; + } + instance = instance.parent; + } +} + +function getComponentName(instance: ComponentInternalInstance) { + return ( + instance.type.name ?? + instance.type.__name ?? + getComponentNameByFile(instance.type.__file, 'vue') + ); +} diff --git a/packages/client/src/resolve/resolveDebug.ts b/packages/client/src/resolve/resolveDebug.ts index 8bbb1736..1bbd0389 100644 --- a/packages/client/src/resolve/resolveDebug.ts +++ b/packages/client/src/resolve/resolveDebug.ts @@ -1,6 +1,7 @@ import { isValidElement } from '../utils/element'; export type ResolveDebug = { + originalElement: HTMLElement; element: HTMLElement; key: string; value?: T | null; @@ -18,11 +19,12 @@ export const vue2Key = '__vue__'; // support parsing single project multiple frameworks export function resolveDebug(element: HTMLElement): ResolveDebug | undefined { + const originalElement = element; while (isValidElement(element)) { const key = findKey(element); if (key) { const value = (element)[key]; - if (value) return { element, key, value }; + if (value) return { originalElement, element, key, value }; } element = element.parentElement!; } diff --git a/packages/client/src/resolve/resolveSource.ts b/packages/client/src/resolve/resolveSource.ts index a9e161ff..36bae350 100644 --- a/packages/client/src/resolve/resolveSource.ts +++ b/packages/client/src/resolve/resolveSource.ts @@ -1,10 +1,9 @@ import { resolveDebug } from './resolveDebug'; import { ensureFileName } from './util'; -import { - resolveSourceFromReact, - resolveSourceFromReact15, -} from './framework/react'; -import { resolveSourceFromVue, resolveSourceFromVue2 } from './framework/vue'; +import { resolveReact18 } from './framework/react18'; +import { resolveReact15 } from './framework/react15'; +import { resolveVue3 } from './framework/vue3'; +import { resolveVue2 } from './framework/vue2'; export interface ElementSourceMeta { name: string; @@ -32,15 +31,15 @@ export function resolveSource( return source; } - let tree: Partial[] = []; + const tree: Partial[] = []; if (debug.key.startsWith('__reactFiber')) { - tree = resolveSourceFromReact(debug, deep); + resolveReact18(debug, tree, deep); } else if (debug.key.startsWith('__reactInternal')) { - tree = resolveSourceFromReact15(debug, deep); + resolveReact15(debug, tree, deep); } else if (debug.key.startsWith('__vueParent')) { - tree = resolveSourceFromVue(debug, deep); + resolveVue3(debug, tree, deep); } else if (debug.key.startsWith('__vue')) { - tree = resolveSourceFromVue2(debug, deep); + resolveVue2(debug, tree, deep); } source.tree = tree.map(normalizeMeta); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e1148379..57e0adf2 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -398,6 +398,12 @@ importers: specifier: 0.0.1-beta.0 version: 0.0.1-beta.0 + vue-element-plus-admin: + dependencies: + '@open-editor/vite': + specifier: ^0.0.14 + version: link:../packages/vite + packages: /@aashutoshrathi/word-wrap@1.2.6: resolution: