如何从 DOM 中检测 svelte 组件?

Min*_*ing 0 dom element google-chrome-extension svelte svelte-component

目前正在制作一个 google chrome 扩展来可视化 svelte 组件,这只能用于开发模式。目前,我正在通过const svelteComponets = document.querySelectorAll(`[class^="svelte"]`);在内容脚本上使用来获取所有 svelte 组件,但它正在获取每个 svelte 元素。只获取组件的方法有哪些?

rix*_*ixo 5

嗯,您通常无法从 DOM 元素访问 Svelte 组件。

除了 Svelte 之外,原因还在于组件和元素之间没有可靠的链接。

组件不能有任何元素:

<slot />
Run Code Online (Sandbox Code Playgroud)

或者“也许没有元素”:

{#if false}<div />{/if}
Run Code Online (Sandbox Code Playgroud)

它还可以有多个根元素:

<div> A </div>
<div> B </div>
<div> C </div>
Run Code Online (Sandbox Code Playgroud)

通过大幅调整cssHash编译器选项,您可能能够从 Svelte 生成的 CSS 作用域类中提取组件“名称”,也许是类名称。(这反过来可能会破坏 Vite 的仅 CSS HMR 更新,但那是另一个故事了。)

但是从那里,您将无法可靠地访问各个组件实例...如果我们保留上一个示例中的组件,一旦您抓住了这 6 个 div:

<div> A </div>
<div> B </div>
<div> C </div>
<div> A </div>
<div> B </div>
<div> C </div>
Run Code Online (Sandbox Code Playgroud)

...您如何知道一个组件实例在哪里结束以及另一个组件实例从哪里开始?或者甚至有两个组成部分?

我相信,实现您想要的最可靠的方法可能是使用内部 Svelte API,包括您想要模仿的实际 Svelte 开发工具使用的 API。(当私有 API 是“最可靠的”时,我一定会喜欢!)

必要的免责声明:在您的情况下这样做似乎才合理,因为它是一个研究主题,并且因为它只是开发人员。依靠这一点来做重要的事情当然是不明智的。私有/内部 API 可能会随任何版本而更改,恕不另行通知。

如果您在启用“dev”选项后进入Svelte REPL并查看生成的 JS,您将看到编译器添加了为开发工具提供的一些事件。

在此输入图像描述

通过试验和实验,您可以了解 Svelte 的工作原理以及可用的开发活动。您可能还需要挖掘编译器本身的源代码以了解某些函数发生的情况...熟悉一个好的调试器会很有帮助!

对于您的预期用途,即构建 Svelte 组件树的表示,您需要知道组件实例何时创建、其父组件是什么以及何时销毁。将其添加到树中的正确位置,并在它消失时将其删除。这样,您应该能够自己维护组件树的表示。

您可以通过“SvelteRegisterComponent”开发事件(上面屏幕截图中的红色方框)知道组件何时创建。通过 abusing 可以知道正在实例化的组件的父组件{ current_component } from 'svelte/internal'。并且您可以通过滥用组件的回调来知道组件何时被销毁this.$$.on_destroy(这似乎是我们计划中最脆弱的部分)。

更详细地了解如何继续这个问题似乎有点超出了这个问题的范围,但下面的基本示例应该会给您一些如何继续进行的想法。在此 REPL中查看它的实际效果。

下面是一些代码,用于监视 Svelte 开发事件以维护组件树,并将其公开为 Svelte 存储,以便其他人轻松使用。此代码需要在创建第一个 Svelte 组件之前运行(或者在创建您想要捕获的组件之前......)。

import { current_component } from 'svelte/internal';
import { writable } from 'svelte/store';

const nodes = new Map();

const root = { children: [] };

// root components created with `new Component(...)` won't have 
// a parent, so we'll put them in the root node's children
nodes.set(undefined, root);

const tree = writable(root);

// notify the store that its value has changed, even 
// if it's only a mutation of the same object
const notify = () => {
    tree.set(root);
};

document.addEventListener('SvelteRegisterComponent', e => {
    // current_component is the component being initialized; at the time 
    // our event is called, it has already been reverted from the component 
    // that triggered the event to its parent component
    const parentComponent = current_component;
    
    // inspect the event's detail to see what more 
    // fun you could squizze out of it
    const { component, tagName } = e.detail;

    let node = nodes.get(component);
    if (!node) {
        node = { children: [] };
        nodes.set(component, node);
    }
    Object.assign(node, e.detail);

    // children creation is completed before their parent component creation
    // is completed (necessarilly, since the parent needs to create all its
    // children to complete itself); that means that the dev event we're using
    // is fired first for children... and so we may have to add a node for the
    // parent from the (first created) child
    let parent = nodes.get(parentComponent);
    if (!parent) {
        parent = { children: [] };
        nodes.set(parentComponent, parent);
    }
    parent.children.push(node);

    // we're done mutating our tree, let the world know
    notify();

    // abusing a little bit more of Svelte private API, to know when
    // our component will be destroyed / removed from the tree...
    component.$$.on_destroy.push(() => {
        const index = parent.children.indexOf(node);
        if (index >= 0) {
            parent.children.splice(index, 1);
            notify();
        }
    });
});

// export the tree as a read only store
export default { subscribe: tree.subscribe }
Run Code Online (Sandbox Code Playgroud)