揭秘Svelte编译器是如何将svelte代码转换成原生js代码

揭秘Svelte编译器是如何将svelte代码转换成原生js代码

我们目前比较熟悉的前端框架大部分都是依赖于一个 diffing 引擎,该引擎将可视 DOM 与内存虚拟 DOM 的副本进行同步,例如vue。这就存在一定的内存开销和效率问题。

但是Svelte是不同的。它是一个编译器。它生成直接转换成原生可视化树的JavaScript),没有虚拟dom,没有diff算法,没有内存开开销,他生成的就是原生js代码。

例如svelte代码

<h1>Hello World</h1>

他会将这段代码转换成

const element = document.createElement('h1')
element.textContent = "Hello World"
document.body.appendChild(element)
那么为啥这么麻烦要转换呢?因为数据绑定

这意味着我们可以<h1>{someValue}</h1>以声明方式编写,并且我们不需要像element.textContent = someValue每次someValue更改时那样编写命令式语句。Svelte 为我们生成同步代码。

一、svelte编译器的工作原理

编译器接收.svelte文件,将它们解析为 AST抽象语法树,分析树,并生成 Javascript 和 CSS。

为简洁起见,以下示例进行了简化。

解析标签

回想一下,.svelte文件的结构类似于.html文件:

<script>// js goes here</script>

<style>/* css goes here */<style>

<!-- More (visual) html tags here -->
<h1>...</h1>
<p>...</p>


第一步是解析文档并为标签创建 3 个标签:<script>标签、<style>标签和可视dom标签(其他所有内容)。

解析 CSS

这些<style>标签被解析出来,以便我们可以为每个 CSS 规则添加一个唯一的前缀。

例如:

h1 {
color: teal;
}
变成:
h1.random-code-abc123 {
color: teal;
}


添加唯一前缀是为了避免与其他组件中定义的 CSS 规则发生冲突。

包css-tree用于遍历 CSS 并检查表达式。

import {parse, walk, generate} from 'css-tree'

// parse CSS source to AST
const input = '.example { color: teal }'
const ast = parse(input)
const randomPrefix = 'xyz123'
const selectors = []

// traverse AST and looking for selectors
walk(ast, node => {
  // check if this node is a selector
  if (node.type === 'Selector') {
    // capture this node, so we can modify it later
    selectors.push(node)
  }
})

// modify the AST
selectors.forEach(selector => {
  // add a `ClassSelector` with name `.xyz123`
  // it will turn `.example` into `.example.xyz123`
  selector.children.insertData({
    type: 'ClassSelector',
    name: randomPrefix
  })
})

// generate CSS text from AST
const output = generate(ast)

// print the CSS text
console.log(output)
//> .example.xyz1234{color:teal}


解析 JavaScript

Svelte 解析<script>标签以提取exports语句并找到反应性语句。

使用acorn包将 JavaScript 源代码转换为 AST 。

例如,假设您定义了一个 prop export let name。所有export let语句都可以通过使用estree-walker 遍历AST 来定位:

import {parse} from 'acorn'
import {walk} from 'estree-walker'

// define source code with 2 exported props
const sourceCode = "export let title, color"

// parse the source code
// enable `sourceType: 'module'` since want to allow exports 
const ast = parse(sourceCode, {sourceType: 'module'})

// walk the AST
walk(ast, {
  enter(node) {
    // check if this node is a "named export"
    if (node.type === 'ExportNamedDeclaration') {

      // named exports can have many names, so map the ids
      const props = node.declaration.declarations.map(declaration => declaration.id.name)

      // print 'em
      console.log(`We got props: ${props.join(', ')}`)
      //> We got props: title, color
    }
  }
})


解析可视标签

其余的标签是视觉标签,如<h1>,<p>等。

Svelte 使用它自己的标签解析器,但你可以使用parse5来做同样的工作:

import { parseFragment } from 'parse5'

const source = "<h1 class='snazzy'>Hello World!</h1>"
const fragment = parseFragment(source)

fragment.childNodes.forEach(node => {
  console.log(node)
})


它输出:
{
  nodeName: 'h1',
  tagName: 'h1',
  attrs: [ { name: 'class', value: 'snazzy' } ],
  namespaceURI: 'http://www.w3.org/1999/xhtml',
  childNodes: [
    {
      nodeName: '#text',
      value: 'Hello World!',
      parentNode: ...
    }
  ] 
}


这为我们提供了 HTML 文档的完整树,我们将使用它来生成等效的 JavaScript 代码。

二、完整的例子

假设我们有一个.svelte像这样的简单文件:

<script>
  export let name;

  function handleClick(e) {
    e.preventDefault()
    alert(`Hello ${name}!`)
  }
</script>

<h1 class="snazzy" on:click=handleClick>Hello {name}!</h1>


编译器生成.js如下所示:
// target: this is the target element to mount the component
// props: a list of props, defined with `export let`
export default function component({ target, props }) {
  // code generated to extract the props into variables:
  let { name } = props; 

  // all functions are copied directly from the <script> tag
  function handleClick(e) {
    e.preventDefault();
    alert(`Hello ${name}!`);
  }

  // variables are declared for each element and text node:
  let e0, t1, b2, t3;

  // returns an object with lifecycle functions to create, mount, detach and update the component. 
  return {
    // called when the components is created
    // creates elements/nodes, adds attributes and wires up event handlers
    create() {
      e0 = document.createElement("h1")
      t1 = document.createTextNode("Hello ")
      b2 = document.createTextNode(name)
      t3 = document.createTextNode("!")

      e0.setAttribute("class", "snazzy")
      e0.addEventListener("click", handleClick)
    },

    // called when the component is mounted to the `target`
    // it just appends things
    mount() {
      e0.appendChild(t1)
      e0.appendChild(b2)
      e0.appendChild(t3)

      target.append(e0)
    },

    // called to change the value of props
    update(changes) {
      // check if name changed
      if (changes.name) {
        // update `name` variable and all binding to `name`
        b2.data = name = changes.name
      }
    },

    // called to remove the component from the DOM
    detach() {
      e0.removeEventListener("click", handleClick)
      target.removeChild(e0)
    }
  };
}


现在我们可以将这个组件挂载到 DOM 中:

import MyComponent from './component'

// instantiate the component
const component = MyComponent({
  target: document.body,
  props: {name: "World"}
})

// create the nodes
component.create()

// append the nodes into the target
component.mount()

总结

Svelte 是一个解析.svelte文件、分析文件然后生成 JavaScript 文件的编译器。JavaScript 文件包含在值更改时挂载组件、处理事件和修补 DOM 的逻辑。

{{collectdata}}

网友评论0