compiler-overview

svelte小记

compile

解析:

  1. 解析源码成为抽象语法树(AST)

  2. 跟踪引用和依赖项

  3. 创建代码块和fragments

  4. 生成代码

大致可以用下图来解释:

如果用一段伪代码来实现,就是:

const source = fs.readFileSync('App.svelte');

// parse source code into AST
const ast = parse(source);

// tracking references and dependencies
const component = new Component(ast);

// creating code blocks and fragments
const renderer =
  options.generate === 'ssr' ? SSRRenderer(component) : DomRenderer(component);

// Generate code
const { js, css } = renderer.render();

fs.writeFileSync('App.js', js);
fs.writeFileSync('App.css', css);

1.解析源码到AST

// parse source code into AST
const ast = parse(source);

Svelte是HTML的超级。Svelte实现了自己的解析器用于Svelte语法,处理和。

怎么处理的呢?当这个解析器碰到标签的时候,使用acorn去解析标签内的内容。当解析器碰到标签时,使用css-tree去解析CSS内容。

并且,Svelte解析器区别对待了script的实例,和module script,。

Svelte的AST就像下面这样:

{
  html: { type: 'Fragment', children: [...] },
  css: { ... },
  instance: { context: 'default', content: {...} },
  module: { context: 'context', content: {...} },
}

可以在ASTExplorer试试Svelte的解析器。可以在HTML > Svelte下找到。

源码的何处是解析器

解析从这里开始。解析器在src/compiler/parse/index.ts实现。

哪里可以学习Javascript中的解析

“JSON Parser with JavaScript”这个文章里有介绍,并指导一步步的写了一个JSON解析器。

如果第一次看解析器,还是狠推荐去学习一下上文。

2.跟踪引用和依赖

// tracking references and dependencies
const component = new Component(ast);

在这一步,Svelte会traverse这个AST去跟踪所有声明和引用的变量及依赖项。

a. Svelte创建一个Component实例

Component这个类,存储Svelte组件的信息,包含信息如下:

b. Traverse 实例脚本和模块脚本的 AST

Component会遍历(traverse)AST的实例脚本和模块脚本去 找出所有的声明,引用和更新的变量

Svelte会在遍历模版之前标识出所有可用的变量。当在遍历模版的过程中遇到变量时,Svelte会标记这个变量被模板引用了(referenced)。

c. Traverse template(遍历模板)

Svelte会遍历模板的AST,并且会从这个模板AST创建一个Fragment树。

每个fragment节点会包含一些信息:

- expression and dependencies(表达式和依赖关系)

逻辑块({#if}),mustache标签({ data }),包含表达式和表达式的依赖项。

- scope

{#each}{#await}逻辑块及let:绑定,为子模板创建了新的变量。

Svelte为AST中的每种类型的节点创建不同的片段节点,因为不同类型的片段节点处理事情的方式不同

d. Traverse 实例脚本 AST

遍历模板后,Svelte就知道组件中是否更新或引用了某个变量。

有了这些信息,Svelte尝试为优化输出做一些准备,譬如:

  • 确定哪些变量或函数可以安全的从instance函数提升。

  • 确定反应性的声明不需要反应。

e. 更新CSS选择器去组件内样式声明

Svelte更新CSS选择器,在必要时向选择器添加.Svelte -xxx class。

在这一步的最后,Svelte有足够的信息来生成编译后的代码,下一个步骤再看。

哪里可以找到这个源码

可以从这里开始拜读。其中Component实现在src/compiler/compile/Component.ts

在哪里可以学习有关JavaScript遍历(traversing)的信息

“Manipulating AST with JavaScript”这个文章里有相关的知识点。

3.创建代码块和片段

// creating code blocks and fragments
const renderer =
  options.generate === 'ssr' ? SSRRenderer(component) : DomRenderer(component);

在这一步,Svelte创建了一个Renderer实例,该实例跟踪生成编译输出所需的必要信息。取决于输出DOM还是SSR代码(参见编译选项中的generate), Svelte分别实例化不同的Renderer

DOM Renderer

DOM Renderer保持跟踪一个块和一个上下文

一个Block包含代码片段去生成create_fragment函数。

上下文跟踪一个实例变量列表,并呈现在编译输出的$$.ctx中。

在renderer中,Svelte从片段树创建了一个渲染树

渲染树中的每个节点都实现了render函数,该函数生成用于创建和更新该节点的DOM的代码。

SSR Renderer

SSR渲染器提供了一些帮助来在编译后的输出中生成模板文本,比如add_string(str)add_expression(node)

Renderer在源码的哪里可以找到

DOM Renderer实现在src/compiler/compile/render_dom/Renderer.ts,SSR Renderer实现在src/compiler/compile/render_ssr/Renderer.ts

4. 生成代码

// Generate code
const { js, css } = renderer.render();

不同的渲染器以不同的方式渲染。

DOM Renderer 遍历渲染树并在此过程中调用每个节点的render函数。Block实例被传递到render函数中,因此每个节点都将代码插入到适当的create_fragment函数中。

另一方面,SSR Renderer 依赖于不同的节点处理程序将字符串或表达式插入最终模板文字中。

render函数返回jscss,分别通过rollup的rollup-plugin-svelte和webpack的svelte-loader用于bundler使用。

runtime

为了在编译输出中删除重复的代码,Svelte提供了util函数,可以在src/runtime/internal中找到它,例如:

  • dom相关的, append, insert, detach

  • 调度相关的, schedule_update, flush

  • 声明周期相关,onMount,beforeUpdate

  • 动画相关的,create_animation

参考:svelte-compiler-handbook

Last updated