遇见Svelte

Sat 05 October 2019 / In categories Framework

JavaScript, Svelte

Hi Svelte, what took you so long!

Svelte不是一个妹子,而是一个JavaScript框架。遇见她,有令我心动的感觉。背景是,最近正在试验一个小项目,需要一款趁手的JS框架。寻寻觅觅,却一直没有碰到合适的,直到遇见了Svelte。

Svelte的标语:Cybernetically enhanced web apps

现在流行的JS框架大都是基于Virtual DOM。所谓Virtual DOM,就是不直接操作DOM,而是根据需求,通过一层中间的数据结构来构造出对实际DOM的映射,然后将所有对DOM的操作都通过修改中间的数据结构,也就是所谓的Virtual DOM来达成。当然,框架要提供Virtual DOM与实际的DOM之间的同步机制。采用Virtual DOM的好处有不少。首先是性能,直接操作DOM比较耗时,而操作纯的JS数据结构则要快得多,因为操作JS不像操作DOM那样需要渲染。Virtual DOM可以允许序列化对DOM的操作,并且可以等到不得不更新DOM的时候才对DOM进行操作,以提高性能。所以Virtual DOM也可以看成是DOM的缓存。

基于Virtual DOM的JS框架大大小小,流行的不流行的有很多,比如React、Vue、Mithrill、Preact、HyperApp、SnabbDOM等等。

如果想学习一下Virtual DOM,可以参考一下SnabbDOM或者HyperApp这些轻量级实现。

但是问题来了,VirtualDOM这一层缓存,真的是必要的吗?Svelte给出了答案,不是!Svelte所采取的思路是将模板编译成纯正的JS,执行这些JS的时候,只需辅以部分运行库,而不用嵌入整个框架。所以,Svelte和传统VirtualDOM的框架的区别,从编程语言的角度,一个是编译型语言,一个是解释型语言。

根据谷歌的翻译,Svelte应该是北欧语系中苗条的意思。

解释还是编译?

解释和编译最大的区别,在于运行的时候是否要嵌入一个解释器。像Python、JS这种解释型语言,在运行的时候要有相应的解释器来求值(然后可能需要在虚拟机中执行);而像Java或者C#这种编译型语言,则运行的时候不需要解释器(但可能需要在虚拟机中执行)。

说了这么多,解释和编译最大的区别在于:是否可以在运行的时候对一段代码进行求值(evaluate)。Python和JS可以在运行的时候通过eval函数来将一段字符串当成一段代码,然后解释这段代码并执行。但是这个操作在编译型语言Java和C#中就不可以。这意味着,编译型语言需要在编译前见到代码的全貌,因为运行的时候不能要加载代码。但也是因为于此,编译器比解释器拥有更多确定信息,能够生成更多代码。但是呢,编译需要有编译单元的概念(例如C语言中的一个.c文件)。自然地,Svelte把Component作为一个编译单元,每个.svelte文件记述一个component,同时也成为编译单元。

但是上面的讨论有一个前提条件,我们的目标代码是Javascript,本身就是一种高级的、动态的语言。仅凭借JavaScript本身所带有的类型反射以及动态求值功能已经足够实现UI组件所需要的反应性(reactiveness)。真的像React那样需要中间搞一层VirtualDOM,这种中间状态保留机制来帮助记忆哪些DOM需要更新呢?解释型的JS不需要上面再套一层像解释器这种保存程序中间执行状态的Overhead。

为什么Svelte出现这么晚,大概是因为写一个编译型的框架总比解释型的框架要复杂,需要更全面细致。

说到底还是解释

解释器通常有一个eval函数,接受一个字符串作为参数。然后执行字符串中所指示的代码。Web引擎本身其实说到底,还是一个大解释器,可以解释三大语言:HTML、CSS、JS。只不过Web引擎的eval函数是以多种形式存在,比如:

  • 把HTML字符串设置给一个HTML元素的innerHTML属性可以修改此HTML元素的子内容
  • 把CSS字符串设置给一个HTML元素的style属性可以修改此HTML元素的样式
  • 把JS字符串交给JS的eval函数,可以将字符串当成代码执行

所以说Web引擎是一个大的解释器,它有很多地方可以接受各种内容和形式的字符串,并解释之。所有隐藏在这背后的机关只有一个,那就是将字符串这种数据转化为可执行的代码。在运行的时候,将数据转为代码,这就是解释器的本质。

更关键的是,这些表征代码的数据都是一种形式,就是字符串形式。这也意味着Svelte或者其他JS框架所真正做的事情,就是生成合适的字符串,喂给Web引擎。当然,生成这些字符串的过程以及逻辑跟具体的业务相关。所以就看谁的方式更适合用来描述业务,用更少的上层语言,生成更多的HTML、CSS、JS代码,同时兼顾性能。

所以,Svelte也好,其他JS框架也好,本质上都是和DOM同型的调度系统,对数据流和事件流进行调度。DOM是从根到叶子的倒树形结构,而这些调度系统是正树形结构,就像一颗枣树一样。数据流就像水分传输,从主干流向叶子;而数据流则像光合作用,从叶子流向主干。这两个是调度系统的主要流向,当然有时候也会有逆流。如果你的代码中出现了逆流,说明设计可能出现问题,就像右撇子用左手写字一样。为了提高调度效率,树上的节点会被分隔成若干区域,然后组件化。每个组件有自己的状态(state),也有自己的入参(props)。 每个区域都是一个可以求值(eval)的对象,这个调度系统决定了区域的求值顺序。事件处理也看成是一种求值,也是由这个调度系统决定的。

Svelte快速体验

快速设置Svelte

https://svelte.dev/repl/hello-world?version=3.12.1点击下载按键,会下载一个svelte-app.zip,解压以后到目录中执行npm install执行安装,然后执行npm run dev运行之,然后在浏览器中输入地址localhost:5000,就可以看到运行的内容。

默认情况下,下载的svelte-app中程序的主内容是:

<script>
	let name = 'world';
</script>

<h1>Hello {name}!</h1>

在浏览器中看到的内容是Hello world!

svelte-app这个项目模板默认使用rollup作为打包工具。如果要为生产环境提供打包结果,可以使用npm run build。项目模板中还使用了sirv-cli这个命令行工具。

你也可以使用yarn,而不是npm来执行nodejs包相关的操作。

sveltejs/template创建项目

你也可以从sveltejs/template创建项目,只需使用命令行:

npx degit sveltejs/template svelte-app

后面的操作与上一章节相同。

degit是Svelte的作者Rich-Harris所创建的,从git仓库创建项目的一个工具。

参考

Svelte文档

其他

类似框架

ryansolid/solid也是采用类似理念,不过目前看来还不够成熟,有待观察。

(完)

Load Disqus Comments