Svelte 是一个构建 web 应用程序的工具,与 React 和 Vue 等 JavaScript 框架类似,都怀揣着一颗让构建交互式用户界面变得更容易的心。
但是有一个关键的区别:Svelte 在 构建/编译阶段 将你的应用程序转换为理想的 JavaScript 应用,而不是在 运行阶段 解释应用程序的代码。这意味着你不需要为框架所消耗的性能付出成本,并且在应用程序首次加载时没有额外损失。
你可以使用 Svelte 构建整个应用程序,也可以逐步将其融合到现有的代码中。你还可以将组件作为独立的包(package)交付到任何地方,并且不会有传统框架所带来的额外开销。
我们以一个简单例子来说明,在输入框中输入内容,然后在弹窗中显示相关内容。然后将svelte的代码与react、vue作一下对比,可以很明显的发现,svelte要写的代码量远少于react和vue。
<script>
let animal = 'dog';
const showModal = () => {
alert(`My favorite animal is ${animal}`);
};
</script>
<input type="text" bind:value={animal} />
<button on:click={showModal}>弹出</button>
import React, { useState } from 'react';
export default function App() {
const [animal, setAnimal] = useState('dog');
const showModal = () => {
alert(`My favorite animal is ${animal}`);
};
return (
<>
<input
type="text"
value={animal}
onChange={() => {
setAnimal(animal);
}}
/>
<button onClick={showModal}>弹出</button>
</>
);
}
<template>
<div>
<input type="text" v-model="animal" />
<button @click="showModal">弹出</button>
</div>
</template>
<script>
import { defineComponent, ref } from 'vue';
export default defineComponent({
setup() {
const animal = ref('dog');
const showModal = () => {
alert(`My favorite animal is ${animal.value}`);
};
return {
animal,
showModal,
};
},
});
</script>
Svelte 能够将代码编译成体积小、不依赖框架的普通js代码,让应用程序无论是启动还是运行都很迅速。
许多同学在学习 react 或者 vue 时可能听说过诸如“虚拟dom很快”之类的言论,所以看到这里就会疑惑,svelte 没有虚拟dom,为什么反而更快呢?
这其实是一个误区,react 和 vue 等框架实现虚拟 dom 的最主要的目的不是性能,而是为了掩盖底层 dom 操作,让用户通过声明式的、基于状态驱动UI的方式去构建我们的应用程序,提高代码的可维护性。
另外 react 或者 vue 所说的虚拟 dom 的性能好,是指我们在没有对页面做特殊优化的情况下,框架依然能够提供不错的性能保障。例如以下场景,我们每次从服务端接收数据后就重新渲染列表,如果我们通过普通dom操作不做特殊优化,每次都重新渲染所有列表项,性能消耗比较高。而像react等框架会通过key对列表项做标记,只对发生变化的列表项重新渲染,如此一来性能便提高了。
思考上面这个场景,如果我们操作真实dom时也对列表项做标记,只对发生变化的列表项重新渲染,省去了虚拟dom diff等环节,那么性能是比虚拟dom还要高的。
svelte便实现了这种优化,通过将数据和真实dom的映射关系,在编译的时候通过 ast 计算并保存起来,数据发生变动时直接更新dom,由于不依赖虚拟dom,初始化和更新时都都十分迅速。
我们都知道 react 和 vue 都是基于运行时的框架,打包后除了用户自己编写的代码之外,还有框架本身的 runtime。而 svelte 是通过静态编译减少框架运行时的代码量。
https://www.npmtrends.com/react-vs-react-dom-vs-vue-vs-svelte[1]
参照 npm trends,react、vue和svelte的 minzipped 体积分别为:42.2kb、22.9kb和1.6kb,足以看出 svelte 的短小精悍。
但是上面这个单看框架的体积稍微有些片面,svelte 由于在编译时将组件直接解释为 js,所以相对来说组件编译后的代码量会比 vue 和 react 编译后要大一些。假如有 n 个组件,svelte 每个组件编译后个规模为 a,vue 或者 react 每个组件编译后的规模为 b:
在 a > b 的情况下,随着 n 的数量的增多,svelte 项目在体积上并不会占据太大的优势。
与 vue 对比
Vue 方面,尤雨溪曾将 vue3 和 svelte 做了对比:https://github.com/yyx990803/vue-svelte-size-analysis[2]
基于真实的 todomvc 场景构建组件,编译以后Svelte 的组件输出大小是Vue的1.7倍,在 SSR 的情况下,这一比例会上升到2.1倍。在不开启 SSR 的情况下,大概19个组件后就会抹平运行时体积的大小差异,开启 SSR 的情况下,大概 13 个组件后就会抹平差异。
与 react 对比
Jacek Schae 也曾将 svelte 和 react进行对比,也是在组件数量达到一定的阈值之后, svelte 的体积优势就不再存在。
可见,大型项目中使用 svelte 的体积问题还有待考究。
无需复杂的状态管理库,Svelte 为 JavaScript 自身添加反应能力。后面的源码解读部分会讲解 svelte 的响应式实现。
Svelte 是 Rich Harris[3] (rollup 作者),2016 年 svelte 开始开源, 2019 年开始引起较为广泛的关注。
Github 上 svelte[4] 现在是 49.9k star:
Npm 上 svelte[5] 的周下载量大概在 15w 左右:
虽然从 star 数和下载量来说离 react、vue 和 angular 还有较大差距,但是鉴于其出道比较晚也是可以理解。而且从框架的调研[6]来看,近两年来其用户满意度和感兴趣度都是高居第一,使用和知名度也是在急速上升的。
总体来看,未来可期!
svelte 的源码由两大部分组成,compiler 和 runtime。compiler 的作用是将 svelte 模版语法编译为浏览器能够识别的js SvelteComponent,而 runtime 则是在浏览器中帮助业务代码运作的运行时函数。
Svelte 如其介绍所说,在 complier 阶段完成了大部分的工作,而 complier 又分为 parse 和 complie 两部分:
parse 会读取 .svelte
文件的内容进行解析。
<
作为标识,包括 HTMLElement、style标签、script标签以及用户自定义的 svelte 组件以及 svelte 实现的一些特殊标签如 svelte:head
、svelte:options
、svelte:window
以及svelte:body
等。{
作为标识,识别的内容除了模板语法之外,还包括 svelte 的逻辑渲染(else……if、each)等语法、{``@html``}
、{``@debug}
等。最终parse会将.svelte
的内容解析成含有 html
、css
、instance
、module
四部分的ast。
Instance 是指 script 标签中响应式的属性和方法,module 是使用 <script context="module"
声明 的无响应的变量和方法。
Complie 首先会将 parse 过程中拿到的语法树(包含 html,css,instance 和 module)转换为 Component,然后在 render_dom 中通过 code-red 中的 print 函数将component 的转换为 js 可运行代码,最终输出 complier 的结果。
我们以一个简单的例子来看下,点击按钮,count加1:
<script>
let count = 0;
const addCount = () => {
count += 1;
};
</script>
<div>
<button on:click={addCount}>增加</button>
<p>count is: {count}</p>
</div>
svelte编译后的结果为:
/* App.svelte generated by Svelte v3.42.4 */
import {
SvelteComponent,
append,
detach,
element,
init,
insert,
listen,
noop,
safe_not_equal,
set_data,
space,
text,
} from 'svelte/internal';
function create_fragment(ctx) {
let div;
let button;
let t1;
let p;
let t2;
let t3;
let mounted;
let dispose;
return {
c() {
div = element('div');
button = element('button');
button.textContent = '增加';
t1 = space();
p = element('p');
t2 = text('count is: ');
t3 = text(/*count*/ ctx[0]);
},
m(target, anchor) {
insert(target, div, anchor);
append(div, button);
append(div, t1);
append(div, p);
append(p, t2);
append(p, t3);
if (!mounted) {
dispose = listen(button, 'click', /*addCount*/ ctx[1]);
mounted = true;
}
},
p(ctx, [dirty]) {
if (dirty & /*count*/ 1) set_data(t3, /*count*/ ctx[0]);
},
i: noop,
o: noop,
d(detaching) {
if (detaching) detach(div);
mounted = false;
dispose();
},
};
}
function instance($$self, $$props, $$invalidate) {
let count = 0;
const addCount = () => {
$$invalidate(0, (count += 1));
};
return [count, addCount];
}
class App extends SvelteComponent {
constructor(options) {
super();
init(this, options, instance, create_fragment, safe_not_equal, {});
}
}
export default App;
我们看到编译后的结果中,有一个 create_fragement
的方法和 instance
方法。
另外还从 svelte/internal
引入了 append
、detach
、element
、insert
、listen
等方法,从源码[7]中可以知道都是一些很简单的对原生 dom 操作的封装。
create_fragment
是和每个组件生成 dom 相关的方法,里面定义了 c
、 m
、p
、i
、o
、d
等一系列内置方法,从缩写上不好理解,我们可以看下源码[8]中其类型定义:
export interface Fragment {
key: string|null;
first: null;
/* create */ c: () => void;
/* claim */ l: (nodes: any) => void;
/* hydrate */ h: () => void;
/* mount */ m: (target: HTMLElement, anchor: any) => void;
/* update */ p: (ctx: any, dirty: any) => void;
/* measure */ r: () => void;
/* fix */ f: () => void;
/* animate */ a: () => void;
/* intro */ i: (local: any) => void;
/* outro */ o: (local: any) => void;
/* destroy */ d: (detaching: 0|1) => void;
}
instance
方法中返回了包含组件实例中属性和方法的数组,将相应的数据绑定在组件实例的 $$.ctx
上,并且根据用户定义的触发属性修改的方法去调用一个 $$invalidate
方法,我们来看下$$invalidate
这个方法干了什么:
$$.instance
:instance(component, options.props || {}, (i, ret, ...rest) => {
const value = rest.length ? rest[0] : ret;
if ($$.ctx && not_equal($$.ctx[i], ($$.ctx[i] = value))) {
if (!$$.skip_bound && $$.bound[i]) $.bound[i](value "i] "i]) $.bound[i") $.bound[i");
if (ready) make_dirty(component, i);
}
return ret;
});
$$invalidate
接收2个或更多参数。第一个参数是 i 是 属性在 $$.ctx
,第二个参数 ret 是定义的属性改变的逻辑函数。然后判断属性重新赋值后与之前的值是否相等,若不相等,则会调用 make_dirty
更新相关的 ui。
function make_dirty(component, i) {
if (component.$$.dirty[0] === -1) {
dirty_components.push(component);
schedule_update();
component.$$.dirty.fill(0);
}
component.$$.dirty[(i / 31) | 0] |= (1 << (i % 31));
}
每个组件的 $$
属性上有一个 dirty 数组,用于标记 instance 中需要更新的属性下标,当 dirty 第一项为 -1
时,表示这个组件当前是干净的,将其 push 到 dirty_components 中,然后执行 schedule_update
方法。
schedule_update 中会异步去执行 flush 函数:
export function schedule_update() {
if (!update_scheduled) {
update_scheduled = true;
resolved_promise.then(flush);
}
}
flush 中对刚刚的 dirty_components 进行遍历,执行 update 函数.
for (let i = 0; i < dirty_components.length; i += 1) {
const component = dirty_components[i];
set_current_component(component);
update(component.$$);
}
update函数会调用组件 update
生命周期钩子函数,将 dirty 数组重新置为 -1,然后调用 fragment 的 p(update) 去更新ui。
function update($$) {
if ($$.fragment !== null) {
$$.update();
run_all($$.before_update);
const dirty = $$.dirty;
$$.dirty = [-1];
$$.fragment && $$.fragment.p($$.ctx, dirty);
$$.after_update.forEach(add_render_callback);
}
}
回到上面的 make_dirty
方法,svelte 是通过如下操作对属性进行脏标记的:
component.$$.dirty[(i / 31) | 0] |= (1 << (i % 31));
了解了位运算,那我们看上面脏标记的代码,(i / 31) | 0
对每个 instance 返回的数组下标除以 31 后和 0 做或运算,即除 31 向下取整,(1 << (i % 31))
i 对 31 取余之后向左进行移位操作。通过上述的两步操作,可以了解到 dirty 数组存储了一系列的 32 位整数,通过这一操作,提高了内存利用率,每个数组项可以存储31个属性是否需要更新。
例如如下32位的整数43,对应的32位二进制为:
Dirty = [43]
43 -> 0000 0000 0000 0000 0000 0000 0010 1011
二进制中为1的位代表需要更新的 instance 中数组第几项,即第1、2、4、6项属性需要更新。
Svelte 框架中自己实现了 store[9],无需安装单独的状态管理库。
Svelte 官方目前没有自己的路由,社区实现的路由库:
目前官方主推的 ssr 框架,具备以下的特点:
服务端渲染(SSR)
路由
typescript支持
less, scss支持
serverless
vite打包
Sapper[13]
sapper开发比较早,也是官方的 ssr 框架,但是 Rich Harris 在2020年10月的svelte峰会上表示:sapper永远不会发布1.0版本。也就是说 sapper 不会发布稳定版甚至被放弃,而 svelte kit 则是它的继任者。
Svelte 偏向于性能,目前在跨平台方面还没有进行探究。
svelte-native[14] (社区库)
暂不支持
可以 electron[15] 结合开发桌面应用
Svelte 现在组件库数量尚可,但是都不够完备,如 table 等复杂组件都没有实现
缺少官方的测试工具,社区单元测试库:
svelte-testing-library[19]
总结来说,svelte 的周边生态目前还不够完备,但由于起步较晚可以理解。
.svelte
文件后缀支持
支持 less、 scss 及 postcss
我们平台组最近正在进行 web component 组件库开发的选型调研,svelte 也作为备选的框架之一。传统的框架如 vue、react 如果想要开发web component,需要每个组件都打包一份体积庞大的运行时,而 svelte 的运行时会根据你的功能按需引入,所以十分适合 web component 的开发场景。
下面是通过 svelte 开发一个简单的 web component 的实例:
1 . 通过官方提供的脚手架创建一个组件
npx degit sveltejs/component-template custom-test-button
2 . 修改相关的文件配置:
修改 package.json
包名称
{
"name": "CustomTestButton",
"svelte": "src/index.js",
"module": "dist/index.mjs",
"main": "dist/index.js",
"scripts": {
"build": "rollup -c",
"prepublishOnly": "npm run build"
},
"devDependencies": {
"@rollup/plugin-node-resolve": "^9.0.0",
"rollup": "^2.0.0",
"rollup-plugin-svelte": "^6.0.0",
"svelte": "^3.0.0"
},
"keywords": [
"svelte"
],
"files": [
"src",
"dist"
]
}
修改 rollup.config.js
文件的内容:
import svelte from 'rollup-plugin-svelte';
import resolve from '@rollup/plugin-node-resolve';
import pkg from './package.json';
const name = pkg.name
.replace(/^(@\S+/)?(svelte-)?(\S+)/, '$3')
.replace(/^\w/, (m) => m.toUpperCase())
.replace(/-\w/g, (m) => m[1].toUpperCase());
export default {
input: 'src/index.js',
output: [
{ file: pkg.module, format: 'es' },
{ file: pkg.main, format: 'umd', name },
],
plugins: [svelte({ customElement: true }), resolve()],
};
3 . 增加组件内容
如下定义了一个组件内容
<svelte:options tag="custom-test-button" />
<script>
export let value = '点击';
export let type = 'default';
</script>
<button class={`custom-test-button ${type}`}>{value}</button>
<style>
.custom-test-button {
height: 32px;
padding: 0 8px;
box-sizing: border-box;
line-height: 32px;
font-size: 14px;
border: 1px solid rgba(0, 0, 0, 0.2);
background-color: #fff;
}
.primary {
background-color: #42b983;
color: #fff;
border: none;
}
.danger {
background-color: #f44336;
color: #fff;
border: none;
}
</style>
4 . 在项目目录执行 npm run build
将组件打包,假设打包后的文件为 index.js
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>svelte web component</title>
</head>
<body>
<script src="./index.js"></script>
<custom-test-button value="测试按钮" type="danger"></custom-test-button>
</body>
</html>
在 vue 的 html 中引入 index.js
:
<body>
<noscript>
<strong
>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work
properly without JavaScript enabled. Please enable it to
continue.</strong
>
</noscript>
<div id="app"></div>
<script src="./custom.js"></script>
<!-- built files will be auto injected -->
</body>
然后在 vue 组件中使用
<template>
<div>
<custom-test-button :value="'测试'" type="danger"></custom-test-button>
</div>
</template>
同 vue,就不做过多介绍了
整体来说,svelte 继前端三大框架之后推陈出新,以一种新的思路实现了响应式,由于起步时间不算很长,目前来说其生态还不够完备, 在大型项目中的应用目前也还有待考究,但是在一些简单页面如活动页、静态页等场景感觉目前还是十分适合的,个人对其未来发展表示看好。
由于其简洁的语法以及与 vue 语法相似的特点,上手成本十分小,大家感兴趣可以稍花一点点时间了解一下,丰富自己的武器库。
[1]https://www.npmtrends.com/react-vs-react-dom-vs-vue-vs-svelte: https://www.npmtrends.com/react-vs-react-dom-vs-vue-vs-svelte
[2]https://github.com/yyx990803/vue-svelte-size-analysis: https://github.com/yyx990803/vue-svelte-size-analysis
[3]Rich Harris: https://github.com/Rich-Harris
[4]svelte: https://github.com/sveltejs/svelte
[5]svelte: https://www.npmjs.com/package/svelte
[6]调研: https://2020.stateofjs.com/en-US/technologies/front-end-frameworks/
[7]源码: https://github.com/sveltejs/svelte/blob/master/src/runtime/internal/dom.ts
[8]源码: https://github.com/sveltejs/svelte/blob/master/src/runtime/internal/Component.ts
[9]store: https://github.com/sveltejs/svelte/blob/master/src/runtime/store/index.ts
[10]svelte-routing: https://github.com/EmilTholin/svelte-routing
[11]svelte-spa-router: https://github.com/italypaleale/svelte-spa-router
[12]sveltekit: https://github.com/sveltejs/kit
[13]Sapper: https://github.com/sveltejs/sapper
[14]svelte-native: https://github.com/halfnelson/svelte-native
[15]electron: https://github.com/electron/electron
[16]svelte-material-ui: https://github.com/hperrin/svelte-material-ui
[17]carbon-components-svelte: https://github.com/carbon-design-system/carbon-components-svelte
[18]smelte: https://smeltejs.com/
[19]svelte-testing-library: https://github.com/testing-library/svelte-testing-library
[20]Svelte for VS Code: https://marketplace.visualstudio.com/items?itemName=svelte.svelte-vscode
[21]Svelte 3 Snippets: https://marketplace.visualstudio.com/items?itemName=fivethree.vscode-svelte-snippets
[22]Svelte Intellisense: https://marketplace.visualstudio.com/items?itemName=ardenivanov.svelte-intellisense
本文由哈喽比特于3年以前收录,如有侵权请联系我们。
文章来源:https://mp.weixin.qq.com/s/TiC4FYAS5px5PjUk6hoD4A
京东创始人刘强东和其妻子章泽天最近成为了互联网舆论关注的焦点。有关他们“移民美国”和在美国购买豪宅的传言在互联网上广泛传播。然而,京东官方通过微博发言人发布的消息澄清了这些传言,称这些言论纯属虚假信息和蓄意捏造。
日前,据博主“@超能数码君老周”爆料,国内三大运营商中国移动、中国电信和中国联通预计将集体采购百万台规模的华为Mate60系列手机。
据报道,荷兰半导体设备公司ASML正看到美国对华遏制政策的负面影响。阿斯麦(ASML)CEO彼得·温宁克在一档电视节目中分享了他对中国大陆问题以及该公司面临的出口管制和保护主义的看法。彼得曾在多个场合表达了他对出口管制以及中荷经济关系的担忧。
今年早些时候,抖音悄然上线了一款名为“青桃”的 App,Slogan 为“看见你的热爱”,根据应用介绍可知,“青桃”是一个属于年轻人的兴趣知识视频平台,由抖音官方出品的中长视频关联版本,整体风格有些类似B站。
日前,威马汽车首席数据官梅松林转发了一份“世界各国地区拥车率排行榜”,同时,他发文表示:中国汽车普及率低于非洲国家尼日利亚,每百户家庭仅17户有车。意大利世界排名第一,每十户中九户有车。
近日,一项新的研究发现,维生素 C 和 E 等抗氧化剂会激活一种机制,刺激癌症肿瘤中新血管的生长,帮助它们生长和扩散。
据媒体援引消息人士报道,苹果公司正在测试使用3D打印技术来生产其智能手表的钢质底盘。消息传出后,3D系统一度大涨超10%,不过截至周三收盘,该股涨幅回落至2%以内。
9月2日,坐拥千万粉丝的网红主播“秀才”账号被封禁,在社交媒体平台上引发热议。平台相关负责人表示,“秀才”账号违反平台相关规定,已封禁。据知情人士透露,秀才近期被举报存在违法行为,这可能是他被封禁的部分原因。据悉,“秀才”年龄39岁,是安徽省亳州市蒙城县人,抖音网红,粉丝数量超1200万。他曾被称为“中老年...
9月3日消息,亚马逊的一些股东,包括持有该公司股票的一家养老基金,日前对亚马逊、其创始人贝索斯和其董事会提起诉讼,指控他们在为 Project Kuiper 卫星星座项目购买发射服务时“违反了信义义务”。
据消息,为推广自家应用,苹果现推出了一个名为“Apps by Apple”的网站,展示了苹果为旗下产品(如 iPhone、iPad、Apple Watch、Mac 和 Apple TV)开发的各种应用程序。
特斯拉本周在美国大幅下调Model S和X售价,引发了该公司一些最坚定支持者的不满。知名特斯拉多头、未来基金(Future Fund)管理合伙人加里·布莱克发帖称,降价是一种“短期麻醉剂”,会让潜在客户等待进一步降价。
据外媒9月2日报道,荷兰半导体设备制造商阿斯麦称,尽管荷兰政府颁布的半导体设备出口管制新规9月正式生效,但该公司已获得在2023年底以前向中国运送受限制芯片制造机器的许可。
近日,根据美国证券交易委员会的文件显示,苹果卫星服务提供商 Globalstar 近期向马斯克旗下的 SpaceX 支付 6400 万美元(约 4.65 亿元人民币)。用于在 2023-2025 年期间,发射卫星,进一步扩展苹果 iPhone 系列的 SOS 卫星服务。
据报道,马斯克旗下社交平台𝕏(推特)日前调整了隐私政策,允许 𝕏 使用用户发布的信息来训练其人工智能(AI)模型。新的隐私政策将于 9 月 29 日生效。新政策规定,𝕏可能会使用所收集到的平台信息和公开可用的信息,来帮助训练 𝕏 的机器学习或人工智能模型。
9月2日,荣耀CEO赵明在采访中谈及华为手机回归时表示,替老同事们高兴,觉得手机行业,由于华为的回归,让竞争充满了更多的可能性和更多的魅力,对行业来说也是件好事。
《自然》30日发表的一篇论文报道了一个名为Swift的人工智能(AI)系统,该系统驾驶无人机的能力可在真实世界中一对一冠军赛里战胜人类对手。
近日,非营利组织纽约真菌学会(NYMS)发出警告,表示亚马逊为代表的电商平台上,充斥着各种AI生成的蘑菇觅食科普书籍,其中存在诸多错误。
社交媒体平台𝕏(原推特)新隐私政策提到:“在您同意的情况下,我们可能出于安全、安保和身份识别目的收集和使用您的生物识别信息。”
2023年德国柏林消费电子展上,各大企业都带来了最新的理念和产品,而高端化、本土化的中国产品正在不断吸引欧洲等国际市场的目光。
罗永浩日前在直播中吐槽苹果即将推出的 iPhone 新品,具体内容为:“以我对我‘子公司’的了解,我认为 iPhone 15 跟 iPhone 14 不会有什么区别的,除了序(列)号变了,这个‘不要脸’的东西,这个‘臭厨子’。