前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >一文讲透前端新秀 svelte

一文讲透前端新秀 svelte

作者头像
欧文
发布2023-02-28 16:42:11
4.3K1
发布2023-02-28 16:42:11
举报
文章被收录于专栏:MoonWebTeam

本文作者:nicolasxiao,腾讯前端高级工程师

引言

本文基于笔者在实际项目中应用svelte的调研报告整理而来,实际项目中,通过将 vue3 替换成 svelte,框架体积就从337.46kb减少到18kb,页面性能指标提升了57%。通过阅读本文,可以快速全面了解 svelte 的优缺点,社区支持,基础使用及核心原理。如果您想在实际项目中使用svelte,可以通过本文获得有力的佐证及足够信心。

1 svelte 是什么?

2、3年前就已经听说过 svelte 这个框架,但一直没有实际使用。svelte 当时还是一个相对年轻的框架,只是使用在个人兴趣的项目,尝尝鲜的话还可以,但如果运用在公司内实际的项目,需要进行充分的调研,确保框架的使用成本及风险,收益。

最近一年,以个人学习的目的,浅尝过 svelte,第一印象就是框架设计得非常的清爽,写起代码来行云流水,不再需要纠结于怎么为响应式数据编写额外的代码,因为 svelte 帮你把数据响应式都做到 JS 语法里了,只需要按照原生 JS 写代码就能获得数据响应式的能力。

近期,笔者所负责的项目重构方案中选型了 svelte,并已经上线稳定运行一段时间。该项目前期立项需要快速上线,所以对技术选型采用了团队沿用下来的方案。项目开发到一定阶段,我们开始着手优化项目的性能表现,提升业务转化率,觉得有必要进行一次技术重构,既提升服务的性能指标,也提高项目的开发效率。在重构方案的设计阶段,经过多方面的调研,最终选型了 svelte。

在方案设计阶段,笔者调研了svelte的特性,跟其他框架在性能、实现机制、适用领域等方面的对比,在网上也翻阅了不少  svelte  相关的文章。对 svelte 的实现原理进行深入探讨的文章屈指可数,比较难以作为调研方案可行性的依据。所以只能自己动手,丰衣足食,开始着手分析 svelte 的源码,挖掘这个框架的实现机制,为调研方案提供可靠的依据。

这个阶段沉淀了不少有价值的分析,加以整理,分享出去,方便后续如果有其他团队想要选择  svelte,可以更加有信心。这就是编写本文的初衷。

至于笔者团队使用  svelte  开发的体验,给大家三个词总结:效率、性能、优雅。

那究竟是什么黑魔法,让原生的  JS  语法具备了数据响应式,本文将一步一步为您揭晓。

1.1、作者

相信如果读者是一个专注前端开发的同学,这些年看到的前端头条肯定少不了 svelte 的身影。诸如《都202X年了,你还没听过 svelte》此类的文章,一直在提示你,再不学 svelte 就跟不上队伍了。虽然这种介绍类的文章不少,但实际项目运用或者原理讲解的文章,则是屈指可数。

svelte 早在2016年就已经发布,这些年来不断的迭代进化,目前已经相对成熟了,官方还配套了一个开箱即用的框架 sveltekit。

它的作者是 Rich Harris,就是下图这位帅气的哥们。

图2 Rich Harris

可能大家伙儿对这位帅哥的名字比较陌生。但如果说起 rollup,大家都知道吧。没错他就是 rollup 的作者,前端届大名鼎鼎的轮子哥。大学学的是哲学,现实工作是《纽约时报》的图片编辑,这个专业跟计算机和前端八杆子打不着的人,却发明了引领前端届的数个轮子,ractive.js, svelte,还有 rollup。每一个都有不小的影响力。

1.2、编译型框架?

 svelte 又是一个基于虚拟 dom 的框架吗?

自从 react,vue 之后,虚拟  dom  的概念盛行。基于虚拟 dom 技术的框架如雨后春笋般,不断的涌现。它们可能采用不同的设计模式,提供不同的接口,但本质都是一样的,通过虚拟  dom  来更新  dom  视图。

svelte 没有随大流,而是另辟蹊径使用了编译机制来实现了数据响应式。编译型框架的阵营里,除了 svelte 以外,目前还有另一个新秀——solid.js,号称目前性能最高的前端框架,在 js benchmark 上取得了仅次于原生 js 的分数。

那什么是编译型框架?编译型框架的性能为何有这种优势呢?

别急,本文后面会解答这些问题,讲解编译型框架实现原理。

2  svelte 适合实际项目吗?

前面讲到笔者已经将 svelte 运用到公司中的实际项目中,并稳定的运行了有一阵子了。在运用到实际项目前,也是在网上到处搜集 svelte 能够胜任的佐证。

经过一定时间的项目实践,svelte 表现靠谱。确认可以放心使用在实际项目。

下面我们逐一看看 svelte 的发展趋势,优点,缺点,适用场景。

2.1、趋势

从 svelte 的各项指标来看,热度还在持续的上涨。

npm trends 数据

npm:svelte - npm

发展趋势,目前稳步上涨,截止本文写作的日期,日下载当前为37.58万,虽然对比 react 和 vue 来说是少了点。但还是相当可观的,并且这条曲线一直往上攀升,相信有一天会跟主流框架还来个死亡交叉的。

图3 svelte npm trend

github 指标

github: GitHub - sveltejs/svelte: Cybernetically enhanced web apps

star 数量:

图4 svelt github star

本文第一版写作时是62500个 star,现在2周过去,涨了约600个star,当前是63100个star。关注 svelte 并予以肯定的人在不断增加。

issues 解决情况:

图5 svelte issues

目前有 689 个打开的 issue, 3973 个关闭的 issue。

大概翻阅了几页 issue,官方的开发者对 issue 相当重视,对建议,bug, 使用问题都会积极的回答。对于尝试一个相对较新的框架,网上资料还比较少时,官方的态度就很重要了。从 issue的解决情况看,官方的人还是很靠谱的。

笔者抽样统计了目前issue问题,其中包含建议(新功能建议,优化建议),使用问题(对使用方法有疑问,或者使用不当导致的误报bug),bug(主要是一些边界问题,非常用问题),bug类,有替代方案(当前框架可能有bug导致无法实现对应的功能,但可以有替代方法),已修复的bug等。

bug数非常规或致命的问题,不影响正常使用。

下面补充 svelte issue 采样统计:

issue 类型

问题占比

建议类

37.5%

使用提问类

29.1%

bug类

16.7%

bug类(有替代方案)

8.3%

bug类(已修复未关闭)

8.3%

引用项目数量:

图6 svelte 引用项目

github上引用了 svelte 的项目大约有12.9万个,不管是一个 hello world也好,是一个 TODO list 也好,还是一个正儿八经的项目也好,已经有不少人开始尝试 svelte 了。

stateofjs 统计数据

这是来自全球一线开发者的统计数据,具有一定的参考价值。

图7 stateofjs 数据

根据统计,94%的前端开发者听说过 svelte。90%的开发者有了解过这个框架,并持续保持关注。对 svelte 感兴趣的开发者占 68%,位列第一。

从这份数据上看,大部分前端开发者有听过 svelte,有三分之二的开发者对其感兴趣。看起来,大家还是很看好这个框架。

2.2、优点

高性能

svelte 作为一个编译型的前端框架代表,它将需要在运行时做的事情,提前到编译阶段完成,所以它几乎没有运行时。它的运行时主要是工具函数,辅助进行dom的更新,任务的调度等。运行阶段无需处理依赖收集,虚拟 dom 比较等额外计算,所以性能自然而然会有先天的优势。

比如依赖收集,svelte 在编译阶段已经提前计算好哪个变量会在哪里引用,需要在什么时候更新 DOM,并且生成了具体的 DOM 更新指令,运行时通过对变量进行脏标记,根据脏标记更新 DOM 视图。举个反例:像某些需要运行时收集依赖的框架,需要在模板渲染时,或者是计算属性被 evaluate 时,才开始进行依赖的收集,这无疑增加了代码执行的耗时。

再比如,svelte 是不需要虚拟 dom 的,它在编译阶段直接生成创建 dom,更新 dom 的过程式代码。而基于虚拟 dom 的框架,则需要在每次数据更新时,重新生成虚拟 dom,并对新旧两个虚拟 dom 树进行比较,最后才能把改变更新到真实的 dom 上。

正因为 svelte 把框架的抽象都从运行时前移到了编译期进行处理,提前分析依赖,生成脏检查语句,生成 dom 的 patch 代码等,去除了运行时的依赖分析,虚拟 dom 等计算耗时,减少了运行时的负担,又在底层实现充分考虑了性能,例如用位运算做数据变更的标记,让整个框架变得很高效。

产物体积小

svelte 框架的运行时非常小,仅仅 18K,在组件数量不多的场景下,其构建产物要明显优于 vue3,react等框架。很适合轻量级的项目。针对这个优势,也有相关评测指出,随着 svelte 组件数量的增多,运行时体积的优势将会被组件拖垮,一般组件数量不超过19个, svelte 产物体积会优于 vue3 。(信息来源 vue 作者尤大大)

心智负担低

svelte 相较于其他框架,实现相同的功能需要编写的代码更简洁。

svelte 通过编译机制让原生 javascript 支持数据响应式。这种基于编译机制,对于开发者而言是完全透明的。

打个比方:

下面是 svelte 通过数据来更新视图的例子:

代码语言:javascript
复制
<div on:click={handleClick}>  {message}</div>let message = ''; // 1.声明变量
const handleClick = () => message = 'hello'; // 2.数据响应

在代码第六行处, message = 'hello',这是一句普通的 javascript 赋值语句。但在 svelte 的编译处理下,这个语句新增了数据响应式的语义。当变量发生赋值时, svelte 会帮忙处理好数据的响应式,更新视图等操作。

如果没有在编译阶段对语义进行处理,单靠运行时绝对是没法实现的。

我们可以看看其他框架的妥协做法:

比如 vue3

代码语言:javascript
复制
<template>    <div @click="handleClick">        {{message}}    </div></template><script setup>    const message = ref(''); // 1.声明变量    const handleClick = () => message.value = 'hello'; // 2.数据响应</script>

由于没有编译器的预处理,vue3只能靠运行时,给变量做封装。message 已经不是一个单纯的 javascript 字符串变量,而是一个对象。这些为数据响应式添加的机制,无疑增加了心智负担。开发者不是在写 plain javascript,尽管框架尽力往原生语法的体验靠拢,但本质上还是在对框架调用各种接口。

正因为 svelte 在编译阶段语义处理上添加框架的特性,保持了 javascript 原来的模样,开发者不需要有心智负担,不会被各种写法搞的焦头烂额。既不容易用错,也不需要浪费太多的精力去学习一个框架各种约定的规则。

丰富的特性

图8 svelte 官网特性展示

现在前端框架该有的 feature, svelte 一个都没有落下。

数据响应式,computed属性,双向绑定,事件透传,一应俱全。

甚至,svelte 把 store 也放到框架里,真正做到开箱即用。

上手简单

svelte 把框架代码编写风格设计得跟 HTML 文件规范几乎一模一样。

编写一个 svelte 组件的体验,跟开发原生 web 基本相同:写 HTML 文档结构,在 script 标签内编写 js 代码,在style 标签内编写样式。

这种方式对于初学者很友好,只需要知道如何编写网页,就可以平稳的过渡到 svelte 。学习成本很低。

额外需要关注的扩展并不多,这里我提炼了一下:

1.赋值语句能触发数据响应式

2.使用 $: 可以声明计算属性

3.使用 $ + store 的变量名可以实现 store 的订阅

只要记住上面三个规则,再加上一些基础的 HTML 网页开发技术,就能快速上手 svelte。

灵活

如果用 svelte 开发一个组件,外部调用可以把这个组件当作一个用 js 写的类来使用,直接通过 new 来创建组件,通过实例方法来调用组件的方法,非常实用。

可以看看下面的例子:

代码语言:javascript
复制
// App 是一个 svelte 编写的组件import App from './App.svelte';
// 这里把 App 当做类进行实例化就能创建出组件的实例const app = new App({  target: document.body,  props: {    answer: 42  }});

另外,svelte 还提供了 web component 的支持,可以通过修改编译选项,将 svelte 写的组件编译成 web component。有了 web component,甚至可以在原生 js ,vue ,react等其他框架中使用 svelte编写的组件。关于 svelte 开发 web component,后面笔者会单独写一篇文章介绍。

2.3、缺点

编译产物代码冗余

svelte 编译输出的组件代码相较于 vue,react 等框架还是稍微冗长了些。

比如编写一个很简单的组件如下:

代码语言:javascript
复制
<h1>Hello world!</h1>

会生成如下的 js 代码:

代码语言:javascript
复制
/* App.svelte generated by Svelte v3.52.0 */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);    },    p: noop,    i: noop,    o: noop,    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;

可以看看 react jsx的构建例子:

代码语言:javascript
复制
<MyButton color="blue" shadowSize={2}>  Click Me</MyButton>

通过 jsx 编译后的产物:

代码语言:javascript
复制
React.createElement(  MyButton,  {color: 'blue', shadowSize: 2},  'Click Me')

svelte 生成的是命令式的dom创建过程,虚拟 dom 的框架生成的是虚拟 dom 结构创建的过程(vdom 渲染函数)。在基于虚拟 DOM 的框架里,虚拟dom到真实dom的转换过程,被封装在运行时里,所以每个组件虚拟 dom 创建过程仅仅是数据结构的表述,更为紧凑,代码产物也就比较少。

生态不够成熟

svelte 诞生到现在有6年的时间,虽然已经有一定数量的使用者,但大公司使用的案例还是比较少。这也导致大家对这个新兴框架敬而远之。

svelte 周边的类库还不够完善,比如想找一个像 ant-design 这样成熟的组件库,目前还是没有的,只能找到一些比较轻量级的组件库。

中文相关的文章也比较匮乏。

英文社区的文档和视频会稍微好一些。

生态不够成熟确实是比较大的问题,导致我们使用 svelte 需要重复造一些轮子,对于某些需要现成组件的项目研发启动的速度会偏慢。每一个新兴的框架其实都需要经历这个过程,随着越来越多的人加入,生态会越来越好的。

2.4、适用场景

基于 svelte 高性能,产物体积小等优点, svelte 很适合开发移动端 H5 的运营营销活动。目前我们也是将 svelte 运用到一个大型的活动页面,并充分运用 svelte 的各种特性,目前项目已经上线,首屏的性能指标提升明显,且暂未遇到难以解决的坑点。

3 svelte 的基本使用

学习每个新的语言和框架,免不了一个 Hello World。下面从一个 Hello World 例子展开,以 svelte store 结尾。看完写一个增删改查的 TODO list 应该不在话下。

另,在 svelte 官网有详细的教程:

Introduction / Basics • Svelte Tutorial

3.1 svelte 脚手架

创建  svelte  项目有三种方式:手动创建,vite 脚手架,sveltekit 脚手架

这里首选推荐 vite 脚手架或者 sveltekit 脚手架,除非项目有较多定制化打包需求才选用手动创建项目的方式。

vite 脚手架

通过 vite 创建 svelte js 项目:

代码语言:javascript
复制
npm create vite@latest myapp -- --template svelte

如果使用 typescript ,可以更换 svelte-ts 模板

代码语言:javascript
复制
npm create vite@latest myapp -- --template svelte-ts

sveltekit 脚手架

sveltekit 脚手架提供交互式的选项,可以定制项目的语言,测试,eslint等配置,相对 vite 脚手架,更为全面。

代码语言:javascript
复制
npm create svelte@latest my-app

手动创建

手动配置需要配置打包工具,测试工具,lint 工具等

首选的打包工具 vite(svelte 官方对 vite 支持最好), 当然 webpack 和 rollup 也有对应的 svelte 方案。

代码语言:javascript
复制
npm install -D @sveltejs/vite-plugin-sveltenpm install -D svelte-preprocessnpm install -D eslint-plugin-svelte

vite.config.ts

代码语言:javascript
复制
import { svelte } from '@sveltejs/vite-plugin-svelte';import sveltePreprocess from 'svelte-preprocess';
export default defineConfig(({ mode }) => ({  plugins: [    svelte({      preprocess: [sveltePreprocess({ typescript: true })],    }),  ],}));

.eslintrc

代码语言:javascript
复制
{  "extends": [    "plugin:svelte/base",  ],  "parserOptions": {    "parser": "@typescript-eslint/parser",    "ecmaVersion": 2020,    "sourceType": "module",    "ecmaFeatures": {      "jsx": true    },    "extraFileExtensions": [      ".svelte"    ]  },  "plugins": [    "@typescript-eslint"  ],  "overrides": [    {      "files": [        "*.svelte"      ],      "parser": "svelte-eslint-parser",      "parserOptions": {        "parser": "@typescript-eslint/parser"      }    }  ],}

需要特别注意,官方开发的 eslint 插件对 typescript 支持有问题,推荐使用下面这个 eslint 插件,支持非常完美,作者也是 svelte, vue3 的贡献者 Yosuke Ota⬇️

GitHub - ota-meshi/eslint-plugin-svelte: ESLint plugin for Svelte using AST

3.2 svelte REPL

如果只是想学习 svelte,可以不急着在本地搭建 svelte 的开发环境。官方为我们提供了 REPL。可以在 REPL 编写 svelte 代码并实时查看结果。REPL 很适合学习入门,或者需要编写 DEMO 验证功能时使用。

点击下方链接直达 svelte REPL ⬇️

Hello world • REPL • Svelte

3.3 Hello, Svelte

svelte 的程序结构分为三部分:模版(template),脚本(script),样式(style)

与 HTML 语法结构高度一致

与 HTML是,在 script 里声明的所有变量,都可以在模版中引用。同时样式是局部的(scoped),只会应用在当前的模板

代码语言:javascript
复制
<div>    Hello, {name}!</div><script>    const name = 'Svelte';</script><style>    div {        color: orange;        }</style>

放入 REPL 可以看到 svelte 输出了一个橙色的"Hello, Svelte! "

图9 Hello, Svelte

3.4 事件绑定

svelte 的事件绑定使用 on:事件名 的格式,如下代码所示

代码语言:javascript
复制
<button on:click={handleClick}>click me</button>

下面的例子演示当用户点击按钮,浏览器将弹出 Clicked!的信息

代码语言:javascript
复制
<button on:click={showMessage}>click me</button><script>const showMessage = () => alert('Clicked!');</script>

放到 svelte REPL 运行得到如下结果:

图10 事件绑定

3.5 赋值

每个前端框架在数据驱动视图的方式上都各显神通,比如 vue2 利用 getter setter的数据响应式,又或者是 vue3 使用 proxy 实现的,再比如 react 的 hooks。

svelte 采用的是编译方式:对变量赋值语句生成额外的数据响应式逻辑。

只要在 javascript 里有对变量赋值,就会自动触发数据的响应式。不需要多余的 api 调用。

可以用下面的例子对比下 vue3 和 svelte

两个例子都是实现了“点击按钮,修改按钮文本”的逻辑

vue3 版本:

代码语言:javascript
复制
<template>    <button @click="handleClick">{{title}}</button></template><script setup>    const title = ref("click me!");    const handleClick = () => {        title.value = "you clicked me!";    } </script>

 vue3 里响应式数据需要用 ref 来封装。赋值需要通过.value才能实现响应式。而模板里,可以省略 .value。

svelte 版本:

代码语言:javascript
复制
<button on:click={handleClick}>{title}</button><script>    let title = 'click me!';    const handleClick = () => {        title = 'you clicked me!';    }</script>

先放到 REPL 里看看效果

图12 数据响应式

按钮确实更新了。

但代码里只有对变量的赋值,不需要 ref,.value 或者类似 setState 之类的数据更新机制。

可通过上面例子看到 svelte 里变量赋值自带了响应式。

但是翻遍 JS 的语法特性,肯定找不到这样的特性的。

别急,本文第四节会深入 svelte 的底层机制,解密 svelte 数据响应式的原理。

当进行数组操作,如push,splice, unshift等,因为不满足响应的数据放在等号的左侧的原则,我们需要多写一点代码,来触发svelte的响应式:

代码语言:javascript
复制
let todos = []function addTodo() {    todos = [...todos, 'new todo'] // 有等号,会触发svelte的响应式}

3.6 神奇的 $ 符号

svelte使用一个特定的语法来表达,在赋值表达式前加上$:定义计算属性

代码语言:javascript
复制
$: numOfTodos = todos.length

等价于 vue 的 computed:

代码语言:javascript
复制
const numOfTodos = computed(() => todos.length)

$: 还可以实现 vue watchEffect 的效果

代码语言:javascript
复制
$: {    if (!numOfTodos) {        console.log('todos is empty')    }}

3.7 麻雀虽小,五脏俱全 - svelte store

svelte 居然还包含了一个 store 的实现。

svelte store 的设计很简洁,下面以一个 svelte 官方的 custom store 的例子展示 svelte store 的用法。(这也是我们实际项目用得最多的形式)

stores.js:

代码语言:javascript
复制
function createCount() {  const { subscribe, set, update } = writable(0);
  return {    subscribe,    increment: () => update(n => n + 1),    decrement: () => update(n => n - 1),    reset: () => set(0)  };}

app.svelte:

代码语言:javascript
复制
<script>  import { count } from './stores.js';</script>
<h1>The count is {$count}</h1>
<button on:click={count.increment}>+</button><button on:click={count.decrement}>-</button><button on:click={count.reset}>reset</button>

lwriteable 用于创建一个 svelte store 实例。

lstore 实例方法 subscribe 用于 store 改动的订阅,实际使用常常被 $store 这种简写代替

lset 用于修改 store 的值

lupdate 用于更新 store 的值

4 svelte 的核心实现

前面一章介绍了 svelte 的用法,通过 js 的赋值语法,能触发数据的响应式逻辑,进而更新视图。想必读者首次看到这种黑科技,估计脑海里会把 defineProperty,getter,setter,proxy都遍历一般,这是 javascript 的新特性吗?怎么把数据响应式都做到语言特性里了?

为了更好的发挥 svelte 的优势,更快的定位解决实际使用问题,有必要对 svelte 的原理进行深入的探究。下文将对 svelte 的核心机制进行剖析。

4.1 编译型前端框架

我们来看看 vue 作者尤大大前些年对 svelte 的评价:

"That would make it technically SvelteScript, right?"

图13 Rich 的演讲

这句话是想表达:svelte 是造了个编译器吗?

确实可以理解成为 svelte 给 javascript 的编译器做了魔改。

在 svelte 源码里,使用了 acorn 将 javascript 编译成 ast 树,然后对 javascript 的语义解释过程做了额外的工作:

  • 编译赋值语句时,除了生成对应的赋值逻辑,额外生成数据更新逻辑代码
  • 编译变量声明时,变量被编译成上下文数组
  • 编译模板时,标记依赖,并对每个变量引用生成更新逻辑

这就是编译型框架,与传统前端框架的区别:把运行时的逻辑提前在编译期就完成。所以自然而然的,运行时逻辑很轻量级,很显然是有利于页面的首屏和渲染性能的。

4.2 实现原理

本节将会从 svelte 的组件底层实现,各种模板语句的编译,svelte 的脚本编译等原理分别展开讲解。

4.2.1 组件的底层实现

每一个 .svelte 文件代表一个 svelte 的组件。

通过 svelte 的编译,最终会转换为下图所示的组件的结构

图14 Svelte 组件底层结构

每一个 svelte 的组件类,都继承了SvelteComponent。

svelte 组件使用create, mount, patch, destroy 这四个方法实现对 DOM 视图的操作。

  • create 负责组件dom的创建
  • mount 负责将 dom 挂载到对应的父节点上
  • patch 负责根据数据的变化更新 dom
  • destroy 负责销毁对应的 dom

svelte 的组件实例化,是通过 instance 方法和组件上下文构成的。

  • instance 方法:可以理解为 instance方法是 svelte 组件的构造器。写在 script 里的代码,会被生成在 instance 方法里。每个组件实例都会调用一次形成自己的闭包,从而隔离各自的数据,通过 instance 方法返回的数组就是上下文。代码中的赋值语句,会被生成为数据更新逻辑。变量定义会被收集生成上下文数组。
  • 上下文:每个 svelte 组件都会有自己的上下文,上下文存储的就是 script 标签内定义的变量的值。svelte 会为每个组件实例内定义的数据生成上下文,按照变量的声明顺序保存在一个名为 ctx 数组内。

图15 上下文结构

4.2.2  模板编译

4.2.2.1 视图的创建

前端框架创建视图的方式有几种,比如虚拟 dom,字符串模板,过程式创建。

svelte 采用的是过程式创建。

举个例子,假设我想要通过纯 js 的方式创建一个如下的 web ui:

我们可能会写下这样的代码:

代码语言:javascript
复制
const todoListNode = document.createElement('ul');const todos = [1,2,3];for (const todo of todos) {    const itemNode = document.createElement('li');    itemNode.textContent = `item ${todo}`;    todoListNode.appendChild(itemNode);}

而 svelte 生成的视图代码就很类似我们手动编写的 js 代码。

这部分创建 dom 的代码,会生成为组件内部的 create 函数, mount 函数,patch 函数。

下面我们来看一下模板编译过程。

1)、首先解析 svelte 模板并生成模板 AST

2)、然后遍历模板 AST

  • 如果碰到普通的 html tag 或者文本,输出 dom 创建语句(dom.createElement)
  • 如果碰到变量
  • 转换为上下文引用方式并输出取值语句(如:name 被生成为 ctx[/** name */0])
  • 在 patch 函数中生成对应的更新语句
  • 如果碰到 if 模板
  • 获取 condition 语句,输出选择函数 select_block (子模板选择器)
  • 获取 condition 为 true 的模板片段,输出 if_block 子模板构建函数
  • 获取 condition 为 false 的模板片段,输出 else_block 子模板构建函数
  • 如果碰到 each 模板
  • 获取循环模板片段,生成块构建函数 create_each_block
  • 根据循环内变量引用,生成循环实例上下文获取 get_each_block_context
  • 生成 key获取函数 get_key
  • 生成基于key更新列表的patch逻辑函数 update_keyed_each

图17 模板AST

子模板构建函数

svelte 会把 if 模板, each 模板中的逻辑分支,抽取成子模板,并为其生成独立的模板实例(包含创建,挂载,更新,销毁等生命周期)

4.2.2.2 视图更新

视图更新时通过patch函数来完成的。

下图是模板解析过程中patch函数的逻辑:

代码语言:javascript
复制
function patch(ctx, [dirty]) {  if (dirty & /*name*/ 1) set_data(t1, /*name*/ ctx[0]);  if (dirty & /*age*/ 2) set_data(t4, /*age*/ ctx[1]);  if (dirty & /*school*/ 4) set_data(t6, /*school*/ ctx[2]);}

通过 dirty 位检查变量是否发生更新,如果发生更新调用 dom 操作函数对 dom 进行局部更新。上面例子的 set_data 函数作用是给 dom 设置 innerText。根据数据更新的视图位置的不同,还会有 set_props之类的更新 dom 属性的函数等。

4.2.2.3 条件分支的处理

条件分支例子:

代码语言:javascript
复制
<script>   let isLogin = false;  const login = () => {    isLogin = true;  }  const logout = () => {    isLogin = false;  }</script>
{#if !isLogin}<button on:click={login}>  login</button>{:else}<div>  hello, xxx  <button on:click={logout}>logout</button></div>{/if}

图18 if 模板产物

1)、条件分支的判断语句会生成select block函数,用于判断条件,并根据条件返回条件判断为真的子模板(if_block)或者条件判断为假的子模板(else_block)

代码语言:javascript
复制
// 根据条件返回对应的block构造函数function select_block(ctx, dirty) {  if (!/*isLogin*/ ctx[0]) return if_block;  return else_block;}// 选择block构造函数let current_block = select_block(ctx, -1);// 返回子模板实例,跟组件类似,提供create,mount,patch等生命周期let block = current_block(ctx);

2)、条件逻辑分支会生成独立的子模板构造函数

if block示例

代码语言:javascript
复制
// 子模板构造函数function if_block(ctx) {  let button;  let mounted;  let dispose;
  return {    // 创建block    create() {      button = element("button");      button.textContent = "login";    },    // 挂载block    mount(target, anchor) {      insert(target, button, anchor);      if (!mounted) {        mounted = true;      }    },    // 销毁block    destroy(detaching) {      if (detaching) detach(button);      mounted = false;      dispose();    }  };}

3)、if分支如何挂载及更新

if 分支的创建:

图19 if 分支创建逻辑

if 分支的更新:

图20 if 分支更新逻辑

4.2.2.4 循环模板的处理

svelte的循环模板跟条件分支模板一样,也会生成迭代逻辑的子模板,每一个循环迭代都是子模板的实例,并且拥有独立的上下文。

主要由4部分组成:

1)、循环迭代构建函数 create_each_block

2)、循环迭代实例上下文获取函数 get_each_block_context

3)、循环迭代 key 获取函数 get_key

4)、基于 key 更新列表的 patch 逻辑函数 update_keyed_each

4.2.3 脚本编译

4.2.3.1 编译过程
  • svelte 调用 acorn 生成 JS AST 树
  • 遍历 AST 找到赋值语句
  • 为赋值语句生成数据响应式

图21 赋值语句编译流程

svelte 组件源码:

代码语言:javascript
复制
<script>    let name = 'world';    const changeName = () => {        name = 'yyb';    }</script>

编译结果:

代码语言:javascript
复制
function instance($$self, $$props, $$invalidate) {  let name = 'world';
  const changeName = () => {    $$invalidate(0, name = 'yyb');  };
  return [name];}
4.2.3.2 $$invalidate

每个数据的赋值语句,svelte都会生成对$$invalidate的调用,$$invalidate的调用主要做的是对某个改动的变量进行标记,然后在微任务中调用patch函数,根据变量改动的脏标记进行局部更新

数据赋值触发视图更新:

图22 赋值触发视图更新逻辑

4.2.3.3 dirty 脏标记

svelte 通过位运算(bitmask)对变量的改变进行脏标记

每个变量都被分配一个位值,可以用于在 ctx 上下文数据里取得变量对应的值,也可以通过位运算对变量改动进行标记和检查。

比如 name 的位值是 1,那 name 的值可以通过 ctx[1]取得。

通过 dirty |= 1 设置 name 已经改动的状态,再通过 dirty & 1 判断 name 是否改动。

按 javascript 的位运算可以有 32 位。svelte 支持每个组件里对 32 个变量标记改动。

一般一个组件不应该定义过多的变量。当然如果定义变量多于 32 个,无非就是拿两个位标记变量,凑成 64 位,以此类推。

图23 脏标记结构

设置位:

代码语言:javascript
复制
bitmask |= 1 << (n-1)

检测位:

代码语言:javascript
复制
if (bitmask & (1 << (n-1)))

变量

位置

位值

name

1

1<<(1-1)=1

age

2

1<<(2-1)=2

school

3

1<<(3-1)=4

5 总结

本文汇总了笔者调研 svelte 实际项目运用的可行性信息,收益,坑点,同时整理了部分笔者分析 svelte 运行机制的分析案例。相信还有不少遗漏的地方,后续有时间会继续深入 svelte 源码,给大家分享更多细节。

这段时间通过公司内部几个前端项目对 svelte 的实践,既有完全开荒的新项目,也有存在历史包袱的老项目。过程中感受的是现阶段的 svelte 已经相当成熟,开发过程中遇到的问题,基本可以通过官方文档,社区找到解决方案。整体的体验是很顺滑的。svelte 基于编译技术实现响应式的设计理念也给笔者不小的惊艳。

最终的期望大家多了解 svelte 这个框架,别再 《都202X年了,还没听过 svelte》了,感兴趣就加入 svelte 阵营。相信 svelte 会在各方面不断带给你惊喜。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2022-12-04,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 MoonWebTeam 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 本文作者:nicolasxiao,腾讯前端高级工程师
  • 1 svelte 是什么?
    • 1.1、作者
      • 1.2、编译型框架?
      • 2  svelte 适合实际项目吗?
        • 2.1、趋势
          • npm trends 数据
          • github 指标
          • stateofjs 统计数据
        • 2.2、优点
          • 高性能
          • 产物体积小
          • 心智负担低
          • 丰富的特性
          • 上手简单
          • 灵活
        • 2.3、缺点
          • 编译产物代码冗余
          • 生态不够成熟
        • 2.4、适用场景
        • 3 svelte 的基本使用
          • 3.1 svelte 脚手架
            • vite 脚手架
            • sveltekit 脚手架
            • 手动创建
          • 3.2 svelte REPL
            • 3.3 Hello, Svelte
              • 3.4 事件绑定
                • 3.5 赋值
                  • 3.6 神奇的 $ 符号
                    • 3.7 麻雀虽小,五脏俱全 - svelte store
                    • 4 svelte 的核心实现
                      • 4.1 编译型前端框架
                        • 4.2 实现原理
                          • 4.2.1 组件的底层实现
                          • 4.2.2  模板编译
                          • 4.2.3 脚本编译
                      • 5 总结
                      相关产品与服务
                      图片处理
                      图片处理(Image Processing,IP)是由腾讯云数据万象提供的丰富的图片处理服务,广泛应用于腾讯内部各产品。支持对腾讯云对象存储 COS 或第三方源的图片进行处理,提供基础处理能力(图片裁剪、转格式、缩放、打水印等)、图片瘦身能力(Guetzli 压缩、AVIF 转码压缩)、盲水印版权保护能力,同时支持先进的图像 AI 功能(图像增强、图像标签、图像评分、图像修复、商品抠图等),满足多种业务场景下的图片处理需求。
                      领券
                      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档