我们目前比较熟悉的前端框架大部分都是依赖于一个 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)那么为啥这么麻烦要转换呢?因为数据绑定。
一、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-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}
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: ... } ] }
二、完整的例子
假设我们有一个.svelte像这样的简单文件:
<script> export let name; function handleClick(e) { e.preventDefault() alert(`Hello ${name}!`) } </script> <h1 class="snazzy" on:click=handleClick>Hello {name}!</h1>
// 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) } }; }
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 的逻辑。
网友评论0