如何看待 svelte 这个前端框架?

看了下。十分精简。思路也很独特。
关注者
1,564
被浏览
939,391

37 个回答

作者是 Rich Harris,也就是 Ractive, Rollup 和 Buble 的作者,堪称前端界的轮子哥,现在又带来新轮子了!

这个框架的 API 设计是从 Ractive 那边传承过来的(自然跟 Vue 也非常像),但这不是重点。Svelte 的核心思想在于『通过静态编译减少框架运行时的代码量』。举例来说,当前的框架无论是 React Angular 还是 Vue,不管你怎么编译,使用的时候必然需要『引入』框架本身,也就是所谓的运行时 (runtime)。但是用 Svelte 就不一样,一个 Svelte 组件编译了以后,所有需要的运行时代码都包含在里面了,除了引入这个组件本身,你不需要再额外引入一个所谓的框架运行时!

当然,这不是说 Svelte 没有运行时,但是出于两个原因这个代价可以变得很小:

1. Svelte 的编译风格是将模板编译为命令式 (imperative) 的原生 DOM 操作。比如这段模板:

<a>{{ msg }}</a>


会被编译成如下代码:

function renderMainFragment ( root, component, target ) {
	var a = document.createElement( 'a' );
	
	var text = document.createTextNode( root.msg );
	a.appendChild( text );
	
	target.appendChild( a )

	return {
		update: function ( changed, root ) {
			text.data = root.msg;
		},

		teardown: function ( detach ) {
			if ( detach ) a.parentNode.removeChild( a );
		}
	};
}


可以看到,跟基于 Virtual DOM 的框架相比,这样的输出不需要 Virtual DOM 的 diff/patch 操作,自然可以省去大量代码,同时,性能上也和 vanilla JS 相差无几(仅就这个简单示例而言),内存占用更是极佳。这个思路其实并不是它首创,之前有一个性能爆表的模板引擎 Monkberry.js 也是这样实现的,ng2 的模板编译其实也跟这个很类似(但是中间加了一层渲染抽象层)。

2. 对于特定功能,Svelte 依然有对应的运行时代码,比如组件逻辑,if/else 切换逻辑,循环逻辑等等... 但它在编译时,如果一个功能没用到,对应的代码就根本不会被编译到结果里去。这就好像用 Babel 的时候没有用到的功能的 helper 是不会被引入的,又好像用 lodash 或者 RxJS 的时候只选择性地引入对应的函数。

基于这两个特点,Svelte 应用的最终代码量可以非常小。比如它的 TodoMVC min+gzip 之后只有 3kb。

但是,Svelte 也不是没有它的潜在问题:

1. 虽然在简单的 demo 里面代码量确实非常小,但同样的组件模板,这样的 imperative 操作生成的代码量会比 vdom 渲染函数要大,多个组件中会有很多重复的代码(虽然 gzip 时候可以缓解,但 parse 和 evaluate 是免不了的)。项目里的组件越多,代码量的差异就会逐渐缩小。同时,并不是真正的如宣传的那样 “没有 runtime“,而是根据你的代码按需 import 而已。使用的功能越多,Svelte 要包含的运行时代码也越多,最终在实际生产项目中能有多少尺寸优势,其实很难说。

2. Svelte 在大型应用中的性能还有待观察,尤其是在大量动态内容和嵌套组件的情况下。它的更新策略决定了它也需要类似 React 的 shouldComponentUpdate 的机制来防止过度更新。另一方面,其性能优势比起现在的主流框架并不是质的区别,现在大部分主流框架的性能都可以做到 vanilla js 的 1.2~1.5 倍慢,基于 Virtual DOM 的 Inferno 更是接近原生,证明了 Virtual DOM 这个方向理论上的可能性,所以可以预见以后 web 的性能瓶颈更多是 DOM 本身而不是框架。

3. Svelte 的编译策略决定了它跟 Virtual DOM 绝缘(渲染函数由于其动态性,无法像模板那样可以被可靠地静态分析),也就享受不到 Virtual DOM 带来的诸多好处,比如基于 render function 的组件的强大抽象能力,基于 vdom 做测试,服务端/原生渲染亲和性等等。这一点在我看来比较关键。让我在一点点性能和 Virtual DOM 之间做抉择的话,我还是会选择 Virtual DOM。Vue 3 在保留 Virtual DOM 灵活性的前提下基于模版对渲染函数做 AOT 优化,性能已经做到跟 Svelte 很接近。

最后,我个人觉得 Svelte 比较有优势的地方,就是用来编译可独立分发的 Web Components。传统框架和 Web Components 结合最大的问题就在于运行时:单独分发的 WC 里面直接打包框架运行时,等于每个组件都要复制一份框架;不打包的话,又做不到开箱即用。但 Svelte 受这个问题的限制最小(依然存在重复代码问题,但取决于你用了多少功能),可以说是最适合这个用例的框架。

Svelte 算是让 Web 页面的工作方式回归到最原始的方法了,当然我说的不是开发方式,这里我指的是浏览器真实在执行的工作。传统的 Web 开发基本都是 jQuery 的天下,$('xxx') 出来一个元素然后直接对它进行操作,但随着应用规模的扩大,这种没有集中状态管理,只靠粗暴操作 DOM 的方式就行不太通了,架构不好的代码维护起来也会很费劲。

然后以 Angular.js 1.0 为代表的 MVVM 框架诞生了,紧接着 React.js、Vue.js 等流行框架就如雨后春笋般地出现了。这些框架各有各的特点,以 React.js 举例,它是一个纯粹的 V 层类库,通过数据到视图元素的映射来构建界面(即界面是数据的实时体现),想要实现这一目标,最简单的方法就是直接渲染出 HTML,然后设置到 innerHTML 中。但是这会带来很严重的性能问题,React.js 的解决方法就是使用 VirtualDOM + Diff(现在使用 Fiber Reconciler),这样通过对比两个 V-Dom 的差异,然后只将少数差异的部分 patch 到真实 DOM 上,就能极大地提高性能,因此 React.js 一大半的工作都是在如何做 Reconciling 上。当然,使用了 React.js 开发的应用打包出来的代码,浏览器实际执行的代码,这部分也占了绝大多数。

当我们需要开发的应用很简单时,用 React.js 之类的类库就会有点『杀鸡用牛刀』了,这就有点像开发一个记事本桌面应用用 Qt,类库代码占了整个应用的 90%。如果类库的各个部分不能 cherry-pick 出来的话这利用率就太低了。像 lodash、Ramda、Rx.js 这样功能模块相对独立的类库 cherry-pick 容易,而 React.js、Angular 这类完整的框架各个内部组件的耦合度很大,基本就要全部打包进去,即使 gzip 也是很大。

再看 Svelte,它的思路很棒,把 HTML 直接编译成 JavaScript,所有的代码都是应用会出现的操作,打个比方:

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

这段代码包含的唯一操作,也就是有动态性的地方就是 name 这个插值,如果用 React.js 来实现的话,先渲染成 element 然后与 V-Dom 做 diff 操作,然后 patch,bla bla bla,中间还要经过几个 event loop,是不是很多余?

Svelte 将这个模板片段 AOT 编译成很精简的一段 JavaScript 模块,这个模块主要的操作有四个部分:create、mount、update、unmount。create 顾名思义就是创建 HTML 片段,把模板中的各个用 HTML 元素用 Vanilla API 创建出来,然后 mount 就可以将这个片段添加到页面的 DOM 中。比较重要的就是 update,它是将新数据绑定到视图的操作,我们看上面这个例子的 update:

function update(changed, state) {
    if (changed.name) {
        text_1.data = state.name;
    }
}

简洁到无以复加是吧?我们只是想修改一个插值而已,何必大炮打蚊子用 V-Dom 呢?

所以这就是 Svelte 的精髓,用最少的操作(代码、CPU Cycle)去实现我们的目标,所以它生成的代码又小运行起来又快。

大家可以自己看看 Svelte compile 后的代码,很简洁易懂。当然它现在刚起步,还有很多不足的地方,比如生成的代码并不是最简,现有 API 比较少,内建特性也比较少。当然生态也很初步,最大的问题就是缺少合格的路由和状态管理这样的类库,所以现在应用到生产环境的话适用面可能也比较小。不过好在它的亲和度比较高,编译出来的小组件与 React.js 等其他框架结合很轻松,所以还是很值得学习和研究的。