Svelte 编译器:工作原理
编译器的工作原理
解析标签
整合起来
想要更多?
想了解更多关于 Svelte 的信息吗?我刚刚发布了一些简短的视频课程✨
大多数前端框架依赖于差异引擎,该引擎将可视化 DOM 与内存中的 DOM 副本同步。
Svelte则不同。它是一个编译器。它生成直接更新可视化树的代码(JavaScript),无需diff 操作。
可以将其想象为将 html 转换<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
文件,将其解析为 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>
标签和视觉标签(其他所有内容)。
解析 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
语句(即 props)并查找反应语句。
使用acorn包将 JavaScript 源代码转换为 AST 。
例如,假设你定义了一个 prop 。可以通过使用estree-walker遍历 AST 来找到export let name
所有语句:export let
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 的逻辑。
为了学习,我构建了一个小版本的编译器:https://github.com/joshnuss/micro-svelte-compiler
它只完成了真实编译器的一小部分,但它是一个有用的学习工具。
提示 #1:如果您想查看更多示例,请查看Svelte REPLJS Tab
中的。提示 #2:AST Explorer是检查 AST 的绝佳学习工具。
编码愉快!
✌
想要更多?
如果您想了解有关 Svelte 的更多信息,请查看我的简短视频课程✨
鏂囩珷鏉ユ簮锛�https://dev.to/joshnuss/svelte-compiler-under-the-hood-4j20