Svelte笔记四:compile源码解析

Svelte 的 runtime 之所以可以如此简洁,是因为在 compiler 的阶段已经完成了很多工作。compiler 是一个将模版语言转化成为可执行代码的一个过程,在运行时的帮助下,实现单向数据流的过程,这样才是 svelte 的运行原理。

svelte 官方的 webpack 插件svelte-loader和 rollup 插件rollup-plugin-svelte的主要都是基于 svelte.compile,这个编译器主要分为两部分 parse 和 compile,parse 是解析的过程,解析 script 和 style 等 tag 标签以及 each 和 ifelse 等 mustache 模版语法。compile 则是包含了 parse 的动作,将解析出来的 ast 语法树转换为可执行的代码。

parse

在 svelte 的语法中,svelte 模版可以分为几个部分:

  • html
  • css
  • instance
  • module

html 的模版逻辑可以分为三大类,tag,mustache 和 text,其中 text 是指没有语意的静态文本。

tag

tag 标签除了 HTML 原生的 Element,还包含了自定义的组件,和 svelte 定义的特殊组件:

  • svelte:head
  • svelte:options
  • svelte:window
  • svelte:body

等等,与此同时,Element 上的 attribute 也会读取并解析成为语法树。

mustache

mustache 则是模版语言中的语法。

例如 if else 语法:

{#if user.loggedIn}
<button on:click="{toggle}">Log out</button>
{:else}
<button on:click="{toggle}">Log in</button>
{/if}

例如 each 语法:

<ul>
  {#each cats as { id, name }, i}
  <li>
    <a target="_blank" href="https://www.youtube.com/watch?v={id}">
      {i + 1}: {name}
    </a>
  </li>
  {/each}
</ul>

源码中有大量的硬编码和判断,将以上这些模版语法转换成为相应 type 为Fragment的语法树 TemplateNode,记录所有标签的位置。数据结构在 src/compiler/interfaces.ts里。

svelte 的 css 组件样式都存放在 style 标签里面,工程代码里的样式字符会被转换成 type 为Style的语法树。

svelte 的 script 被分为 instance 和 module 两种。instance 指的是svelte runtime 源码解析中提及的 instance 方法,是用于处理组件内部的变化逻辑。module 则是处理组件外部的逻辑,它 export 出来的方法可以被组件外部调用。例如下面这个例子 stopAll 这个方法可以被组件外部调用,用于暂停组件的音乐播放器。

<script context="module">
  const elements = new Set();

  export function stopAll() {
    elements.forEach((element) => {
      element.pause();
    });
  }
</script>

script 的解析主要靠的是code-red,它是基于acorn的封装。它的作用主要是解析 js 语法和打印 js 代码,主要有三个函数,分别是 b,x 和 print。b 代表 body,x 代表 expression,print 代码打印。解析主要用到 b 和 x。将项目的业务代码解析成为语法树,再分别装入 instance 和 module。

compile

语法的最终编译是来自code-red的 print 将调整后的语法树转换成为代码。

第一步将 parse 过程中拿到的语法树(包含 html,css,instance 和 module)转换为 Component,第二步将 Component 进入render dom处理并生成 component 的转换代码以及 runtime,第三步输出 compiler 的结果。

《Svelte 笔记三:runtime 源码解读》介绍了转换后代码重要的组成部分是 create_fragment,该函数内拥有很多 element 的创建,它会是组件初始化中非常重要的函数,所有的节点都会定义在src/compiler/compile/nodes中。组件化是将所有的 Component 组成一个树状结构,render dom 将匹配的节点转换为 runtime 可识别的代码。

重点聊一下数据流,《runtime 源码解读》讲到 svelte 是单向数据流,将代码中变量的赋值转换成为$$invalidate,它并没有采用 vue 中的双向绑定的机制,而是直接在语法树中判断变量的赋值的语句,待变量触发变化时,引起 Fragment 的 update 以及 Component 的 update。

node.type === "AssignmentExpression" &&
  node.operator === "=" &&
  nodes_match(node.left, node.right) &&
  tail.length === 0;

总结

svelte 是一个极简化的框架,从完备的 compiler 和极简的 runtime 可以看出。一个前端工程师的主要目标是寻找一款贴近浏览器原生的框架,svelte 是你不二的选择。

题外话

shopee,又称虾皮,是一家腾讯投资的跨境电商平台。这里加班少,技术氛围好。如果想和我并肩作战一起学习,可以找我内推。邮箱weiping.xiang@shopee.com,非诚勿扰。