Optimization of vue3 template compilation

Shenfq 2020-11-11 15:51:59
optimization vue3 vue template compilation


Vue3 It's been a while since the official release , I wrote an article some time ago (《Vue Template compilation principle 》) analysis Vue The principle of template compilation of . Today's article is going to study Vue3 Under the template compilation and Vue2 The difference under , as well as VDOM Next Diff Optimization of algorithm .

Compile entry

Read about Vue3 My classmates must know Vue3 New combinations have been introduced Api, In components mount The stage calls setup Method , Then we will judge render Does the method exist , If it doesn't exist compile Methods will template Turn into render.

// packages/runtime-core/src/renderer.ts
const mountComponent = (initialVNode, container) => {
const instance = (
initialVNode.component = createComponentInstance(
// ...params
)
)
// call setup
setupComponent(instance)
}
// packages/runtime-core/src/component.ts
let compile
export function registerRuntimeCompiler(_compile) {
compile = _compile
}
export function setupComponent(instance) {
const Component = instance.type
const { setup } = Component
if (setup) {
// ... call setup
}
if (compile && Component.template && !Component.render) {
// without render Method
// call compile take template To render Method
Component.render = compile(Component.template, {...})
}
}

This part is all about runtime-core The code in , It was mentioned in the previous article Vue Divided into full version and runtime edition . If you use vue-loader Handle .vue file , In general, I will .vue In the document template It can be directly processed into render Method .

// Need compiler
Vue.createApp({
template: '<div>{{ hi }}</div>'
})
// Unwanted
Vue.createApp({
render() {
return Vue.h('div', {}, this.hi)
}
})

The full version and runtime The difference is , The full version will introduce compile Method , If it is vue-cli The generated project will erase this part of the code , take compile The process is in the packaging stage , To optimize performance .runtime-dom Provided in registerRuntimeCompiler Method is used to inject compile Method .

Main process

In the full version of index.js in , Called registerRuntimeCompiler take compile For injection , Now let's look at the injected compile What does the method mainly do .

// packages/vue/src/index.ts
import { compile } from '@vue/compiler-dom'
// Compile cache
const compileCache = Object.create(null)
// Inject compile Method
function compileToFunction(
// Templates
template: string | HTMLElement,
// Compile configuration
options?: CompilerOptions
): RenderFunction {
if (!isString(template)) {
// If template It's not a string
// They think it's a DOM node , obtain innerHTML
if (template.nodeType) {
template = template.innerHTML
} else {
return NOOP
}
}
// If it exists in the cache , Get directly from the cache
const key = template
const cached = compileCache[key]
if (cached) {
return cached
}
// If it is ID Selectors , This gets DOM After the element , take innerHTML
if (template[0] === '#') {
const el = document.querySelector(template)
template = el ? el.innerHTML : ''
}
// call compile obtain render code
const { code } = compile(
template,
options
)
// take render code Turn into function
const render = new Function(code)();
// return render Method at the same time , Put it in the cache
return (compileCache[key] = render)
}
// Inject compile
registerRuntimeCompiler(compileToFunction)

Talking about Vue2 The template compilation has already talked about ,compile The method is divided into three steps ,Vue3 The logic of is similar to :

  1. Template compilation , Convert template code to AST;
  2. Optimize AST, Convenient follow-up virtual DOM to update ;
  3. The generated code , take AST Into executable code ;
// packages/compiler-dom/src/index.ts
import { baseCompile, baseParse } from '@vue/compiler-core'
export function compile(template, options) {
return baseCompile(template, options)
}
// packages/compiler-core/src/compile.ts
import { baseParse } from './parse'
import { transform } from './transform'
import { transformIf } from './transforms/vIf'
import { transformFor } from './transforms/vFor'
import { transformText } from './transforms/transformText'
import { transformElement } from './transforms/transformElement'
import { transformOn } from './transforms/vOn'
import { transformBind } from './transforms/vBind'
import { transformModel } from './transforms/vModel'
export function baseCompile(template, options) {
// analysis html, Turn into ast
const ast = baseParse(template, options)
// Optimize ast, Tag static nodes
transform(ast, {
...options,
nodeTransforms: [
transformIf,
transformFor,
transformText,
transformElement,
// ... Omitted part of transform
],
directiveTransforms: {
on: transformOn,
bind: transformBind,
model: transformModel
}
})
// take ast Convert to executable code
return generate(ast, options)
}

Calculation PatchFlag

The general logic here is not much different from the previous one , Mainly optimize The method has become transform Method , Some default syntax will be used transform. these transform It's the follow-up virtual DOM The key to optimization , Let's see transform Code for .

// packages/compiler-core/src/transform.ts
export function transform(root, options) {
const context = createTransformContext(root, options)
traverseNode(root, context)
}
export function traverseNode(node, context) {
context.currentNode = node
const { nodeTransforms } = context
const exitFns = []
for (let i = 0; i < nodeTransforms.length; i++) {
// Transform Will return an exit function , After processing all the child nodes, execute
const onExit = nodeTransforms[i](node, context)
if (onExit) {
if (isArray(onExit)) {
exitFns.push(...onExit)
} else {
exitFns.push(onExit)
}
}
}
traverseChildren(node, context)
context.currentNode = node
// Execution so Transform Exit function of
let i = exitFns.length
while (i--) {
exitFns[i]()
}
}

Let's focus on transformElement The logic of :

// packages/compiler-core/src/transforms/transformElement.ts
export const transformElement: NodeTransform = (node, context) => {
// transformElement There's no logic being executed , Instead, it directly returns an exit function
// explain transformElement You need to wait until all the child nodes have finished processing
return function postTransformElement() {
const { tag, props } = node
let vnodeProps
let vnodePatchFlag
const vnodeTag = node.tagType === ElementTypes.COMPONENT
? resolveComponentType(node, context)
: `"${tag}"`
let patchFlag = 0
// Detect node properties
if (props.length > 0) {
// Detect the dynamic part of node attributes
const propsBuildResult = buildProps(node, context)
vnodeProps = propsBuildResult.props
patchFlag = propsBuildResult.patchFlag
}
// Detect child nodes
if (node.children.length > 0) {
if (node.children.length === 1) {
const child = node.children[0]
// Check whether the child node is dynamic text
if (!getStaticType(child)) {
patchFlag |= PatchFlags.TEXT
}
}
}
// format patchFlag
if (patchFlag !== 0) {
vnodePatchFlag = String(patchFlag)
}
node.codegenNode = createVNodeCall(
context,
vnodeTag,
vnodeProps,
vnodeChildren,
vnodePatchFlag
)
}
}

buildProps The node's properties will be traversed once , Because the internal source code involves a lot of other details , Here's the simplified code , Just keep patchFlag Related logic .

export function buildProps(
node: ElementNode,
context: TransformContext,
props: ElementNode['props'] = node.props
) {
let patchFlag = 0
for (let i = 0; i < props.length; i++) {
const prop = props[i]
const [key, name] = prop.name.split(':')
if (key === 'v-bind' || key === '') {
if (name === 'class') {
// If you include :class attribute ,patchFlag | CLASS
patchFlag |= PatchFlags.CLASS
} else if (name === 'style') {
// If you include :style attribute ,patchFlag | STYLE
patchFlag |= PatchFlags.STYLE
}
}
}
return {
patchFlag
}
}

The code above shows only three kinds of patchFlag The type of :

  • A node has only one text child node , And the text contains dynamic data TEXT = 1
<p>name: {{name}}</p>
  • Nodes contain variable class attribute CLASS = 1 << 1
<div :class="{ active: isActive }"></div>
  • Nodes contain variable style attribute STYLE = 1 << 2
<div :style="{ color: color }"></div>

You can see PatchFlags It's all numbers 1 after Shift left operator Calculated .

export const enum PatchFlags {
TEXT = 1, // 1, Binary system 0000 0001
CLASS = 1 << 1, // 2, Binary system 0000 0010
STYLE = 1 << 2, // 4, Binary system 0000 0100
PROPS = 1 << 3, // 8, Binary system 0000 1000
...
}

You can see from the code above ,patchFlag The initial value of 0, Every time the patchFlag It's all about execution | ( or ) operation . If the current node is only a dynamic text child node and has dynamic style attribute , final patchFlag by 5( Binary system :0000 0101).

<p :style="{ color: color }">name: {{name}}</p>
patchFlag = 0
patchFlag |= PatchFlags.STYLE
patchFlag |= PatchFlags.TEXT
// Or operations : Only one of the two corresponding bits is 1, It turns out that the corresponding bit is 1.
// 0000 0001
// 0000 0100
// ------------
// 0000 0101 => Decimal system 5

patchFlag

We put the code above in Vue3 Run in :

const app = Vue.createApp({
data() {
return {
color: 'red',
name: 'shenfq'
}
},
template: `<div>
<p :style="{ color: color }">name: {{name}}</p>
</div>`
})
app.mount('#app')

Last generated render The method is as follows , Basically consistent with our previous description .

function render() {}

render Optimize

Vue3 In virtual DOM Diff when , Will take out patchFlag And what needs to be done diff Type to proceed &( And ) operation , If the result is true Before entering the corresponding diff.

patchFlag Judge

Take the previous template as an example :

<p :style="{ color: color }">name: {{name}}</p>

If at this time name Changes have taken place ,p The node enters diff Stage , This will determine patchFlag & PatchFlags.TEXT , It turns out to be true at this time , indicate p The node has text modification .

patchFlag

patchFlag = 5
patchFlag & PatchFlags.TEXT
// Or operations : Only the corresponding two binary digits are 1 when , The result is 1.
// 0000 0101
// 0000 0001
// ------------
// 0000 0001 => Decimal system 1
if (patchFlag & PatchFlags.TEXT) {
if (oldNode.children !== newNode.children) {
// Modify the text
hostSetElementText(el, newNode.children)
}
}

But go ahead patchFlag & PatchFlags.CLASS When judging , Because the nodes are not dynamic Class, The return value is 0, So it will not be used for this node class Attributes in diff, To optimize performance .

patchFlag

patchFlag = 5
patchFlag & PatchFlags.CLASS
// Or operations : Only the corresponding two binary digits are 1 when , The result is 1.
// 0000 0101
// 0000 0010
// ------------
// 0000 0000 => Decimal system 0

summary

Actually Vue3 There are a lot of performance optimizations , It's just going to be patchFlag One tenth of the content was given out ,Vue3 Before it was officially released, it was said that Diff The process goes through patchFlag To optimize performance , So I'm going to look at his optimization logic , On the whole, there are still gains .
image

版权声明
本文为[Shenfq]所创,转载请带上原文链接,感谢

  1. [front end -- JavaScript] knowledge point (IV) -- memory leakage in the project (I)
  2. This mechanism in JS
  3. Vue 3.0 source code learning 1 --- rendering process of components
  4. Learning the realization of canvas and simple drawing
  5. gin里获取http请求过来的参数
  6. vue3的新特性
  7. Get the parameters from HTTP request in gin
  8. New features of vue3
  9. vue-cli 引入腾讯地图(最新 api,rocketmq原理面试
  10. Vue 学习笔记(3,免费Java高级工程师学习资源
  11. Vue 学习笔记(2,Java编程视频教程
  12. Vue cli introduces Tencent maps (the latest API, rocketmq)
  13. Vue learning notes (3, free Java senior engineer learning resources)
  14. Vue learning notes (2, Java programming video tutorial)
  15. 【Vue】—props属性
  16. 【Vue】—创建组件
  17. [Vue] - props attribute
  18. [Vue] - create component
  19. 浅谈vue响应式原理及发布订阅模式和观察者模式
  20. On Vue responsive principle, publish subscribe mode and observer mode
  21. 浅谈vue响应式原理及发布订阅模式和观察者模式
  22. On Vue responsive principle, publish subscribe mode and observer mode
  23. Xiaobai can understand it. It only takes 4 steps to solve the problem of Vue keep alive cache component
  24. Publish, subscribe and observer of design patterns
  25. Summary of common content added in ES6 + (II)
  26. No.8 Vue element admin learning (III) vuex learning and login method analysis
  27. Write a mini webpack project construction tool
  28. Shopping cart (front-end static page preparation)
  29. Introduction to the fluent platform
  30. Webpack5 cache
  31. The difference between drop-down box select option and datalist
  32. CSS review (III)
  33. Node.js学习笔记【七】
  34. Node.js learning notes [VII]
  35. Vue Router根据后台数据加载不同的组件(思考-&gt;实现-&gt;不止于实现)
  36. Vue router loads different components according to background data (thinking - & gt; Implementation - & gt; (more than implementation)
  37. 【JQuery框架,Java编程教程视频下载
  38. [jQuery framework, Java programming tutorial video download
  39. Vue Router根据后台数据加载不同的组件(思考-&gt;实现-&gt;不止于实现)
  40. Vue router loads different components according to background data (thinking - & gt; Implementation - & gt; (more than implementation)
  41. 【Vue,阿里P8大佬亲自教你
  42. 【Vue基础知识总结 5,字节跳动算法工程师面试经验
  43. [Vue, Ali P8 teaches you personally
  44. [Vue basic knowledge summary 5. Interview experience of byte beating Algorithm Engineer
  45. 【问题记录】- 谷歌浏览器 Html生成PDF
  46. [problem record] - PDF generated by Google browser HTML
  47. 【问题记录】- 谷歌浏览器 Html生成PDF
  48. [problem record] - PDF generated by Google browser HTML
  49. 【JavaScript】查漏补缺 —数组中reduce()方法
  50. [JavaScript] leak checking and defect filling - reduce() method in array
  51. 【重识 HTML (3),350道Java面试真题分享
  52. 【重识 HTML (2),Java并发编程必会的多线程你竟然还不会
  53. 【重识 HTML (1),二本Java小菜鸟4面字节跳动被秒成渣渣
  54. [re recognize HTML (3) and share 350 real Java interview questions
  55. [re recognize HTML (2). Multithreading is a must for Java Concurrent Programming. How dare you not
  56. [re recognize HTML (1), two Java rookies' 4-sided bytes beat and become slag in seconds
  57. 【重识 HTML ,nginx面试题阿里
  58. 【重识 HTML (4),ELK原来这么简单
  59. [re recognize HTML, nginx interview questions]
  60. [re recognize HTML (4). Elk is so simple