什么是 Vue 模板?
Vue 模板是一种用 HTML 语法来描述 Vue 组件的视图的方式,
例如:
<template><div class="example">{{ msg }}</div>
</template>
<script>export default {data() {return {msg: "Hello world!",};},};
</script>
<style>.example {color: red;}
</style>
这里的 template 标签内的内容就是 Vue 模板,它可以使用一些 Vue 特有的指令和表达式,例如 v-if、v-for、{{ }} 等,来实现动态的视图渲染。
为什么要预编译 Vue 模板?
Vue 模板本身是一种字符串,要想让它在浏览器中显示出来,就需要将它转换为一个可以创建和更新 DOM 的函数,这个函数就叫做渲染函数。
Vue 提供了两种方式来生成渲染函数:
- 运行时编译:这种方式是在浏览器中,使用 vue-template-compiler 的浏览器版本,将模板字符串动态地编译为渲染函数,然后执行渲染函数,创建和更新 DOM。这种方式的优点是可以使用任意的模板字符串,甚至可以从服务器端获取模板字符串,实现动态的视图渲染。缺点是需要额外的编译时间和编译器的代码体积,以及可能的 CSP 限制,因为编译器会使用 new Function 来创建渲染函数,这在一些严格的安全策略下是不允许的。
- 预编译:这种方式是在构建时,使用 vue-template-compiler 的 Node.js 版本,将模板字符串静态地编译为渲染函数,并将渲染函数打包到最终的 js 文件中,然后在浏览器中直接执行渲染函数,创建和更新 DOM。这种方式的优点是可以避免运行时编译的开销和限制,提高性能和安全性。缺点是不能使用动态的模板字符串,只能使用固定的模板字符串,或者使用 render 函数来手动编写渲染函数。
预编译 Vue 模板的好处是显而易见的,它可以让我们的 Vue 应用更快更安全地运行,而不需要牺牲太多的灵活性。因此,预编译 Vue 模板是一种推荐的做法,尤其是在使用 webpack 或其他构建工具的情况下,我们可以很方便地使用 vue-loader 或其他方式来实现预编译。
vue-template-compiler 是如何工作的?
vue-template-compiler 的工作原理是,它会将 Vue 模板字符串转换为一个包含渲染函数和静态渲染函数的对象,这个对象可以被 vue-loader 或其他方式引用,从而创建一个 Vue 组件。
分为以下几个步骤:
-
首先,它会使用 html-parser 来解析模板字符串,将其转换为一个抽象语法树 (AST)。AST 是一种用 JavaScript 对象来表示 HTML 结构的方式,它包含了元素、属性、文本、表达式等节点,以及它们之间的关系。例如,上面的模板字符串的 AST 大致如下:
{type: 1, // 元素节点tag: "div", // 标签名attrsList: [{ name: "class", value: "example" }], // 属性列表attrsMap: { class: "example" }, // 属性映射children: [{type: 2, // 表达式节点expression: "_s(msg)", // 表达式内容text: "{{ msg }}", // 文本内容},], // 子节点列表 }
-
然后,它会使用 optimize 来优化 AST,标记出静态节点和静态根节点,这样可以在渲染时跳过它们,提高性能。静态节点是指不依赖于数据的节点,例如纯文本节点或固定属性的元素节点。静态根节点是指只有一个子节点,并且这个子节点是静态节点的元素节点。例如,上面的模板字符串的 AST 中,div 节点就是一个静态根节点,它的子节点 {{ msg }} 就是一个静态节点。optimize 会在 AST 的每个节点上添加一个 static 属性,表示是否是静态节点,以及一个 staticRoot 属性,表示是否是静态根节点。例如,上面的模板字符串的 AST 的 optimize 后的结果大致如下:
{type: 1,tag: "div",attrsList: [{ name: "class", value: "example" }],attrsMap: { class: "example" },children: [{type: 2,expression: "_s(msg)",text: "{{ msg }}",static: true, // 静态节点},],static: false, // 非静态节点staticRoot: true, // 静态根节点 }
-
接着,它会使用 codegen 来生成渲染函数的代码,包括创建元素、绑定属性、插入文本、添加事件等。codegen 会遍历 AST,将其中的节点转换为相应的渲染函数的代码片段,然后将这些代码片段拼接成一个完整的渲染函数,并添加一些必要的辅助函数和变量。例如,上面的模板字符串的 AST 的 codegen 后的结果大致如下:
with (this) {return _c("div",{ staticClass: "example" },[_v(_s(msg))],1 /* STATIC */); }
这里的 _c、_v、_s 等都是 Vue 提供的辅助函数,用于创建元素、创建文本、转义字符串等。最后的 1 是一个标志位,表示这个节点是静态的,可以跳过更新。
-
最后,它会导出一个包含渲染函数和静态渲染函数的对象,可以被 vue-loader 或其他方式引用,从而创建一个 Vue 组件。渲染函数是用于创建和更新动态节点的函数,静态渲染函数是用于创建和缓存静态节点的函数。例如,上面的模板字符串的导出结果大致如下:
export default {render: function () {with (this) {return _c("div",{ staticClass: "example" },[_v(_s(msg))],1 /* STATIC */);}},staticRenderFns: [], };
这里的 staticRenderFns 是一个空数组,表示没有静态节点需要缓存。如果有静态节点需要缓存,它会包含一些静态渲染函数,用于创建和返回静态节点。