compile-svelte-in-your-head-1

脑海中来编译一个svelte(1)

https://svelte.dev/repl/99aeea705b1e48fe8610b3ccee948280?version=3.23.2 这里可以看到一个最简单的svelte组件的输出结果(JS output), 如下:

/* App.svelte generated by Svelte v3.23.2 */
import {
    SvelteComponent,
    detach,
    element,
    init,
    insert,
    noop,
    safe_not_equal
} from "svelte/internal";

function create_fragment(ctx) {
    let h1;

    return {
        c() {
            h1 = element("h1");
            h1.textContent = "Hello World";
        },
        m(target, anchor) {
            insert(target, h1, anchor);
        },
        d(detaching) {
            if (detaching) detach(h1);
        }
    };
}

class App extends SvelteComponent {
    constructor(options) {
        super();
        init(this, options, null, create_fragment, safe_not_equal, {});
    }
}

export default App;

create_fragment

create_fragment就是给svelte的组件去构建DOM片段。

他的返回的对象里的方法:

c()

create 的短写

包含创建所有元素片段的指令。

这个例子里,就是创建h1元素

m(target, anctor)

mount 短写

就是挂载到对应的target上。

这个例子里就是mount h1到target上。

d(detaching)

destroy 短写

从target中删除某个元素。

这里的例子就是从DOM中移除h1。

export default class App extends SvelteComponent

svelte api

初始化的时候就使用类似create_fragment这些信息来组成。Svelte只会传递需要它的信息,并在不需要的时候删除它们。

试试空的组件,会输出什么信息。

https://svelte.dev/repl/1f29ce52adf446fc9116bb957b7200ec?version=3.19.1

打开上面这个链接看看jsoutput:

create_fragment被改为null去传递了。

Svelte在init函数中设置了大多数内部的内容:

  • component的props, ctx(后面会解释ctx)和上下文

  • component生命周期事件

  • component更新机制

最后通过create_fragment去创建和挂载元素到DOM上。

并且所有的内部状态(state)和方法都被附加到this.$$上面。

所以你获取组件的$$属性,那么就在访问组件的内部了。

Adding data

目前,大致了解了基本的组件行为,现在看下如何添加数据之后编译的输出如何改变的。

Svelte REPL

这个在编译后的输出里有了改变:

可以发现内部的内容被移动到了代码的顶层,并且h1元素的文本内容是一个模板字面量。

这有很多的东西发生在幕后。

Updating data

添加一个函数去更新name变量。

Svelte REPL

然后发现编译后改变的地方:

一些新发现:

  • h1元素的文本内容被分成了两个文本节点,通过text()函数创建。 text()地址:https://github.com/sveltejs/svelte/blob/master/src/runtime/internal/dom.ts#L48

  • create_fragment返回的对象新增了一个p(ctx, dirty)方法。

    update 短写

  • 创建了一个新的instance函数。

  • <script>中的内容被移到了instance函数中。

  • 你是否发现create_fragment中使用的变量name被替换成了ctx[0],并且也不是模板字符串了。

那么,为什么要改变?

Svelte编译器跟踪所有在<script>标签中声明的变量,他跟踪变量是否存在以下情况:

  • 可否进行改变(mutated)?,类似 count++

  • 是否可以重新赋值?类似name = 'Svelte'

  • 是否在模板中被引用?类似 Hello {name}

  • 是否可写?类似 const i = 1;let i = 1;

  • 。。。以及更多

当Svelte编译器意识到可以重新分配变量name时,(由于name = 'Svelte';update函数中),它将h1的文本内容分解为若干部分,以便动态更新部分文本。

实际上,可以看到有一个新方法p来更新文本节点。

p(ctx, dirty)

update 短写

p(ctx, dirty) 包含一些更新元素,他是基于组件中被改变的状态(dirty)和状态(ctx)。

instance variable

编译器发现变量name没有在App的不同组件实例间共享,这就是为什么会把name声明放到名为instance的函数中。

在上一个示例中,无论App组件有多少个实例,变量name的值在这些实例中都是相同的,并且没有变化:

目前的这个例子里,变量name在组件的一个实例里是可以改变的,因此变量name的声明被移到了instance函数中:

instance($$self, $$props, $$invalidate)

instance函数返回一个instance变量列表,这些变量是:

  • 在模板中被引用

  • 可以改变(mutated)或重新分配(在组件的一个实例内改变)

在Svelte中,我们将这个实例变量列表称为ctx

init函数里,Svelte调用instance函数去创建 ctx ,并使用它去创建组件片段。

现在,不是直接在组件外貌访问变量name,而是直接通过传递的ctx 来访问变量name

$$invalidate

Svelte系统的反应能力是背后秘密是$$invalidate函数。

存在的这些变量只要:

  • 重新赋值或者改变

  • 在模板中引用

会在改变或重新赋值之后通过$$invalidate函数正确的插入:

$$invalidate函数会标记变量为dirty并为组件安排更新:

Adding event listeners

现在来加一个事件监听:

Svelte REPL

发现与之前的不同点:

一些发现:

  • instance函数现在返回两个变量

  • mount 期间侦听单击事件,并在destroy 中处理它

前面也提到了,instance函数返回的变量列表是被 在模板中引用可变和重新赋值的

因为在模板中引用了update函数,所以作为在instance函数返回的 ctx 的一部分。

你如果在模板中不引用update,这个是不会被加到instance函数返回的数组里的。

由于Svelte尽可能的去返回简洁的js输出,没有必要是不会返回额外的变量。

listen and dispose

dispose(处理)

每当在Svelte中添加事件侦听器时,Svelte将注入代码来添加事件侦听器,并在从DOM中删除DOM片段时将其删除。

尝试多加几个事件监听:

Svelte REPL

并观察编译后的输出:

Svelte并没有声明和创建一个新变量来删除每个事件侦听器,而是将它们全部分配给一个数组:

这样变量名压缩可以做的更好。

同样,这是Svelte试图生成更小的JavaScript输出的另一个很好的例子。当只有一个事件侦听器时,Svelte不会创建dispose数组。

Summary

Svelte语法是HTML的超集,就像ts是js的超集。

当你写Svelte组件的时候,Svelte编译器会分析你的代码并生成优化过的Js代码输出。

输出大致可以分成三个部分:

1. create_fragment

  • 返回片段,他的内部是关于如何构建组件DOM片段的。

2. instance

  • <script>标签中写的大多数代码都会在这里。

  • 返回实例中用到的变量列表(可变和重新赋值的或模板中引用的)

  • $$invalidate 在实例变量被改变或者重新赋值的时候正确的插入

3. class App extends SvelteComponent

  • 使用create_fragmentinstance来初始化组件

  • 构建组件内部需要的变量,事件等

Svelte尽可能生成简洁的JavaScript输出:

  • 仅当部分文本可以更新时,将h1的文本内容拆分为单独的文本节点

  • create_fragmentinstance仅仅在需要的时候才会去定义

  • 根据事件侦听器的数量,以数组或函数的形式生成dispose变量

  • 。。。

结束语

这里介绍了Svelte编译输出的基本结构,而这仅仅是开始。

后面还有,待续

基本就是翻译过来的,原文地址:

https://lihautan.com/compile-svelte-in-your-head-part-1/

Last updated