til
  • README
  • Software Development Roles
  • solid
  • README
    • service-worker
  • docker
    • arg
    • 更新docker版本
  • editor
    • vscode
    • Creating a VS Code Theme
  • english
    • words
  • front-end
    • ==
    • ECMAScript
    • IIFE
    • Label
    • basic
    • html.js.css渲染顺序
    • npm-vs-yarn
    • obj-delete-key-value
    • react
    • split-join-and-replace
    • video
    • 前端自检清单
    • 递归及去重
    • css
      • css换肤
      • flex
      • list
      • nth-child和nth-of-type区别
      • padding
      • position
      • 层叠上下文
      • 层叠样式(+)
      • 正方形
      • 语义化标签
    • dom
      • DOCTYPE
      • HEAD
      • 修改document
      • 自定义表单验证
    • electron
      • basic
    • es6
      • basic-type
      • basic
      • prototype-example
      • defineProperty
      • understanding-es6
        • 0.introduction
        • Appendix A: Smaller Changes
        • Appendix B: Understanding ES7
        • Block-Binding
        • Proxies&Reflection
        • class
        • 解构赋值
        • function
        • improved-array
        • iterators&generators
        • modules
        • object
        • promise
        • Map&Set
        • symbol
    • images
      • 前端角度看图片
    • interview_case
      • lexical_scope
      • redux和localstroage存储位置
    • javascript
      • fuck-the-js
      • js-engine-work
      • js原生操作dom
      • what-is-function-program
      • 执行上下文
      • articles
        • JavaScript中使用函数组合
        • JavaScript中的依赖注入
        • JavaScript作用域链中的标识符解析和闭包
        • JavaScript是何如工作的--概述
        • JavaScript深拷贝
        • JavaScript的全局变量是如何工作的
        • js继承常见的误解
        • node12&chrome中7个新的提案功能
        • 你真的懂JavaScript吗
      • date
        • index
      • engines
        • basic
        • JavaScript引擎基础:外形和内联缓存
        • v8中推测性优化的介绍
        • 优化原型
        • 更快的异步功能和promise
      • events
        • baisc
        • 事件冒泡和捕获
        • 定义事件
        • 页面生命周期
      • higher-order-function
        • curry
        • monad
      • module
        • basic
        • main&module
      • objects
        • iterator
        • spread
        • examples
          • iterator
      • performance
        • blocking-css
        • cache
      • prototype
        • Property-Descriptors
        • basic
        • prototype-shadow
      • you-dont-known-js
        • async&performance
          • Chapter 1: Asynchrony: Now & Later
          • Chapter 2: Callbacks
          • Chapter 3: Promises
          • Chapter 4: Generators
        • scope & closures
          • apA
          • apB
          • apC
          • apD
          • chapter1-what-is-scope
          • chapter2-lexical-scope
          • chapter3-function-vs-block-scope
          • chapter4-hoisting
          • chapter5-scope-closure
        • this & object prototypes
          • chapter1-this-or-that
          • chapter2-this-make-sense
          • chapter3-objects
          • chapter4-mixing(up)-class-object
          • chapter5-prototype
          • chapter6-behavior-delegation
        • types&grammer
          • Chapter1-Types
          • Chapter2-Values
          • Chapter3-Natives
          • Chapter4-coercion
          • Chapter5-grammer
        • up & going
          • chapter1-into-programming
          • chapter2-into-javascript
          • chapter3-into-YDKJS
    • mobile
      • iPhone分辨率终极指南
    • npm
      • arguments
      • build
    • react-native
      • prop-methods
    • react
      • PropTypes
      • basic
      • codebase-overview
      • component-element-instance
      • context
      • how-to-known-component-is-func-or-class
      • overview
      • react16.9
      • react18计划
      • react的设计原则
      • reconciliation
      • setState
      • useMemo
      • why-do-we-write-super-props
      • 从头实现一个react
      • concurrent
        • 引入并发模式(仅试验)
      • conf
        • conf-2019
      • events
        • 合成事件概述
      • hooks
        • custom-hook
        • effect-hook
        • hooks-api
        • intro
        • overview
        • rules
        • state-hook
        • hooks-vs-class
          • thinking-in-react-hooks
      • overreact
        • Development模式是如何工作的
        • How-Does-setState-Know-What-to-Do
        • Why-Do-React-Elements-Have-a-$$typeof-Property
        • Why-Do-React-Hooks-Rely-on-Call-Order
        • how-to-known-component-is-func-or-class
        • preparing-tach-talk-motivation
        • react作为ui运行
        • things-i-dont-known-as-2018
        • ui-element-problem-and-build-yourself
        • why-do-we-write-super-props
        • 一份完整的useEffect指南
        • 为什么X不是Hook
        • 函数组件与类有什么不同?
        • 演讲准备2-what-why-how
        • 编写弹性组件
        • 让setInterval在React-Hooks中为声明式
      • practice
        • render
      • react-dom
        • basic
      • react-redux
        • apiv7.1-hooks
        • connect
        • shallow-equal
      • redux
        • applyMiddleware
        • applyMiddleware2-细节
        • example
    • regex
      • index
    • stories
      • 数组下标
      • 阻止事件冒泡
    • svelte
      • compile-svelte-in-your-head-1
      • compiler-overview
      • parser
        • 写一个解析器-JavaScript的JSON解析器
    • turbopack
      • basic
    • typescript
      • interface和type的区别
    • webpack
      • hash
      • webpack4-for-react
      • webpack4
      • webpack4to5
      • babel
        • babel-parser和acorn的区别
        • babel.7.11
        • family
        • react16.14使用new-transform
        • update-to-7
    • pdf
      • deep-js
        • basic
      • react
        • reintroducing
  • git
    • capital
    • emoji
  • http
    • http2.0
    • response
  • rails
    • api
    • flash
    • middleware-vs-metal
    • model
    • performance
    • routes
    • environment
      • error
    • patterns
      • service
    • sidekiq
      • params
    • deploy
      • capistrano
        • ssh
  • ruby
    • self
    • net
      • http请求携带cookie
  • server
    • ss
    • ssh
    • user
    • crawler
      • puppeteer
    • nginx
      • domain-without-80
      • nginx节省带宽
  • sql
    • rails
    • search
Powered by GitBook
On this page
  • 脑海中来编译一个svelte(1)
  • create_fragment
  • export default class App extends SvelteComponent
  • Adding data
  • Updating data
  • instance variable
  • instance($$self, $$props, $$invalidate)
  • $$invalidate
  • Adding event listeners
  • listen and dispose
  • Summary
  • 结束语
  1. front-end
  2. svelte

compile-svelte-in-your-head-1

PrevioussvelteNextcompiler-overview

Last updated 4 years ago

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

这里可以看到一个最简单的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元素

h1 = element('h1');
h1.textContent = 'Hello World';

// https://github.com/sveltejs/svelte/blob/master/src/runtime/internal/dom.ts#L21
export function element<K extends keyof HTMLElementTagNameMap>(name: K) {
    return document.createElement<K>(name);
}

m(target, anctor)

mount 短写

就是挂载到对应的target上。

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

insert(target, h1, anchor);

// http://github.com/sveltejs/svelte/tree/master/src/runtime/internal/dom.ts
export function insert(target, node, anchor) {
  target.insertBefore(node, anchor || null);
}

d(detaching)

destroy 短写

从target中删除某个元素。

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

detach(h1);

// https://github.com/sveltejs/svelte/blob/master/src/runtime/internal/dom.ts#L11
export function detach(node: Node) {
    node.parentNode.removeChild(node);
}

export default class App extends SvelteComponent

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

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

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

/* App.svelte generated by Svelte v3.19.1 */
import { SvelteComponent, init, safe_not_equal } from "svelte/internal";

class App extends SvelteComponent {
    constructor(options) {
        super();
        init(this, options, null, null, safe_not_equal, {});
    // 下面是 hello world 版本
    // init(this, options, null, create_fragment, safe_not_equal, {});
    }
}

export default App;

create_fragment被改为null去传递了。

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

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

  • component生命周期事件

  • component更新机制

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

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

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

Adding data

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

<script>
    let name = 'World';
</script>
<h1>Hello {name}</h1>

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

function create_fragment(ctx) {
  // ...
  return {
    c() {
      h1 = element('h1');
      h1.textContent = `Hello ${name}`;    },
    // ...
  };
}
let name = 'World';
class App extends SvelteComponent {
  // ...
}

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

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

Updating data

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

<script>
    let name = 'World';
    function update() {
        name = 'Svelte';
    }
</script>
<h1>Hello {name}</h1>

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

function create_fragment(ctx) {
  return {
    c() {
+      h1 = element('h1');
+         t0 = text('Hello ');
+           t1 = text(/*name*/ ctx[0]);
    },
    m(target, anchor) {
      insert(target, h1, anchor);
      append(h1, t0);
      append(h1, t1);
    },
+   p(ctx, [dirty]) {
+     if (dirty & /*name*/ 1) set_data(t1, /*name*/ ctx[0]);
+   },
    d(detaching) {
      if (detaching) detach(h1);
    },
  };
}

+ function instance($$self, $$props, $$invalidate) {
+  let name = 'World';
+  function update() {
+    $$invalidate(0, (name = 'Svelte'));
+  }
+  return [name];
+ }

export default class App extends SvelteComponent {
  constructor(options) {
    super();
+   init(this, options, instance, create_fragment, safe_not_equal, {});
  }
}

一些新发现:

  • 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的值在这些实例中都是相同的,并且没有变化:

<App />
<App />
<App />

<!-- gives you -->
<h1>Hello world</h1>
<h1>Hello world</h1>
<h1>Hello world</h1>

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

<App />
<App />
<App />

<!-- could possibly be -->
<h1>Hello world</h1>
<h1>Hello Svelte</h1>
<h1>Hello world</h1>
<!-- depending on the inner state of the component -->

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

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

  • 在模板中被引用

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

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

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

// 就概念而言,
const ctx = instance(/*...*/);
const fragment = create_fragment(ctx);
// create the fragment
fragment.c();
// mount the fragment onto the DOM
fragment.m(target);

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

t1 = text(/*name*/ ctx[0]);

$$invalidate

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

存在的这些变量只要:

  • 重新赋值或者改变

  • 在模板中引用

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

name = 'Svelte';
count++;
foo.a = 1;

// 编译成类似于这样
name = 'Svelte';
$$invalidate(/* name */, name);
count++;
$$invalidate(/* count */, count);
foo.a = 1;
$$invalidate(/* foo */, foo);

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

// 概念上来说...
const ctx = instance(/*...*/);
const fragment = create_fragment(ctx);
// 跟踪哪些变量被改变
const dirty = new Set();
const $$invalidate = (variable, newValue) => {
  // 更新 ctx (variable对应的数组的下标)
  ctx[variable] = newValue;
  // 标记变量为dirty
  dirty.add(variable);
  // 为组件安排更新
  scheduleUpdate(component);
};

// 被安排更新时调用
function flushUpdate() {
  // update片段
  fragment.p(ctx, dirty);
  // 清楚dirty标记
  dirty.clear();
}

Adding event listeners

现在来加一个事件监听:

<script>
    let name = 'world';
    function update() {
        name = 'Svelte';
    }
</script>
<h1 on:click={update}>Hello {name}</h1>

发现与之前的不同点:

function create_fragment(ctx) {
  // ...
  return {
    c() {
      h1 = element('h1');
      t0 = text('Hello ');
      t1 = text(/*name*/ ctx[0]);
    },
    m(target, anchor) {
      insert(target, h1, anchor);
      append(h1, t0);
      append(h1, t1);
+     dispose = listen(h1, 'click', /*update*/ ctx[1]);    },
    p(ctx, [dirty]) {
      if (dirty & /*name*/ 1) set_data(t1, /*name*/ ctx[0]);
    },
    d(detaching) {
      if (detaching) detach(h1);
+     dispose();    },
  };
}

function instance($$self, $$props, $$invalidate) {
  let name = 'world';

  function update() {
    $$invalidate(0, (name = 'Svelte'));
  }
+ return [name, update];}
// ...

一些发现:

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

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

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

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

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

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

listen and dispose

dispose(处理)

尝试多加几个事件监听:

<h1
    on:click={update}
    on:mousedown={update}
    on:touchstart={update}>
  Hello {name}!
</h1>

并观察编译后的输出:

// ...
+ dispose = [
+  listen(h1, 'click', /*update*/ ctx[1]),
+  listen(h1, 'mousedown', /*update*/ ctx[1]),
+  listen(h1, 'touchstart', /*update*/ ctx[1], { passive: true }),
+ ];
// ...
+ run_all(dispose);

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

// instead of
dispose1 = listen(h1, 'click', /*update*/ ctx[1]);
dispose2 = listen(h1, 'mousedown', /*update*/ ctx[1]);
dispose2 = listen(h1, 'touchstart', /*update*/ ctx[1], { passive: true });
// ...
dispose1();
dispose2();
dispose3();

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

同样,这是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_fragment和instance来初始化组件

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

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

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

  • create_fragment和instance仅仅在需要的时候才会去定义

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

  • 。。。

结束语

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

后面还有,待续

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

h1元素的文本内容被分成了两个文本节点,通过text()函数创建。 text()地址:

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

提供

https://svelte.dev/repl/99aeea705b1e48fe8610b3ccee948280?version=3.23.2
svelte api
https://svelte.dev/repl/1f29ce52adf446fc9116bb957b7200ec?version=3.19.1
Svelte REPL
Svelte REPL
https://github.com/sveltejs/svelte/blob/master/src/runtime/internal/dom.ts#L48
Svelte REPL
事件侦听器
事件侦听器
Svelte REPL
组件的API
https://lihautan.com/compile-svelte-in-your-head-part-1/