[译] 制做 Vue 3 的过程


原文连接: https://increment.com/frontend/making-vue-3前端

在过去的一年里,Vue 团队一直在研究 Vue.js 的下一个主要版本,咱们但愿在 2020 年上半年发布。这项工做在撰写本文时还在进行中),关于 Vue 新的主要版本的想法是在 2018 年末造成的,当时 Vue 2 的代码库大约有两年半的时间。在通用软件的生命周期中,这听起来可能并不长,但在这段时间内,前端的格局发生了翻天覆地的变化。vue

有两个关键的考虑因素促使咱们对 Vue 进行了新的主要版本(和重写)。第一,主流浏览器中新的 JavaScript 语言功能的广泛存在。第二,当前代码库中暴露出来的设计和架构问题。算法

为何要重写

利用新的语言功能

随着 ES2015 的标准化,JavaScript(即 ECMAScript,缩写为 ES 的缩写)获得了重大改进,主流浏览器终于开始为这些新增长的功能提供了像样的支持。特别是一些新添加的功能为咱们提供了极大的机会,使 Vue 的能力获得了极大的提高。typescript

其中最值得一提的是 Proxy,它容许框架对对象进行拦截操做。Vue 的一个核心特性是可以监听用户定义状态的变化,并对 DOM 进行反应式更新。Vue 2 经过用 getter 和 setter 替换状态对象上的属性,实现了这种反应性。切换到 Proxy 可让咱们消除 Vue 现有的限制,好比没法检测到新的属性添加,并提供更好的性能。后端

然而,Proxy 是一个原生语言的功能,在传统的浏览器中没法彻底复用。为了利用它,咱们知道咱们必须调整框架的浏览器支持范围--这是一个重大的突破性改变,只能在新的主要版本中提供。数组

解决架构问题

在现有的代码库中修复这些问题,须要进行巨大的、危险的重构,几乎至关于重写。
在维护 Vue 2 的过程当中,因为现有架构的限制,咱们已经积累了不少问题,这些问题很难解决。例如,模板编译器的编写方式使得适当的源码映射支持变得很是具备挑战性。此外,虽然 Vue 2 在技术上能够构建针对非 DOM 平台的更高级别的渲染器,但咱们不得不对代码库进行分叉,并重复了大量的代码来实现这一点。在当前的代码库中修复这些问题,将须要进行巨大的、危险的重构,几乎至关于重写。浏览器

同时,咱们还以各类模块内部的隐式耦合形式积累了技术债务,以及彷佛不属于任何地方的浮动代码。这使得咱们很难孤立地理解代码库中的某一部分,并且咱们注意到,贡献者不多有信心进行一些可有可无的修改。重写将给咱们提供了一个机会,让咱们在考虑到这些问题的状况下从新思考代码组织。前端框架

初步原型设计阶段

咱们在 2018 年末开始了 Vue 3 的原型开发,初步目标是验证这些问题的解决方案。在这个阶段,咱们主要集中在为进一步开发打下坚实的基础上。架构

切换到 typescript

Vue 2 最初是用普通的 ES 编写的。在原型设计阶段后不久,咱们意识到类型系统对于这样的项目来讲是很是有帮助的。类型检查大大减小了在重构过程当中引入意外 bug 的机会,并帮助贡献者更有信心地进行简单的修改。咱们采用了 Facebook 的 Flow 类型检查器,由于它能够逐步添加到现有的纯 ES 项目中。Flow 在必定程度上起到了必定的帮助,但咱们并无如愿以偿地受益;特别是,不断的破坏性修改让升级成为一种痛苦。与 TypeScript 与 Visual Studio Code 的深度集成相比,对集成开发环境的支持也并不理想。app

咱们还注意到,用户愈来愈多地将 Vue 和 TypeScript 一块儿使用。为了支持他们的用例,咱们不得不将 TypeScript 声明与源代码分开编写和维护,而源代码使用的是不一样的类型系统。转换到 TypeScript 将使咱们可以自动生成声明文件,减轻了维护负担。

内部包的解耦

咱们还采用了一个单体化的设置,框架由内部包组成,每一个包都有本身的 API、类型定义和测试。咱们但愿让这些模块之间的依赖关系更加明确,让开发者更容易阅读、理解和修改。这对于咱们努力下降项目的贡献障碍和提升项目的长期可维护性是很是关键的。

设置 RFC 流程

到 2018 年年末,咱们已经有了一个工做原型,有了新的反应式系统和虚拟 DOM 渲染器。咱们已经验证了咱们想作的内部架构改进,但只有面向公众的 API 改动的粗略草稿。如今是时候把它们变成具体的设计了。

咱们知道咱们必须尽早、谨慎地完成这项工做。Vue 的普遍使用意味着破坏性的改变可能会致使用户的大量迁移成本和潜在的生态系统碎片化。为了确保用户可以提供对打破性改动的反馈,咱们在 2019 年初采用了 RFC(征求意见)流程。每一个 RFC 都遵循一个模板,其中的章节集中在动机、设计细节、权衡和采用策略等方面。因为该流程是在 GitHub repo 中进行的,建议以拉动请求的形式提交,所以讨论在评论中有机地展开。

事实证实,RFC 流程很是有帮助,它做为一个思想框架,迫使咱们充分考虑到了潜在变革的全部方面,并容许咱们的社区参与到设计过程当中,提交深思熟虑的功能请求。

更快、更小

性能对于前端框架来讲是相当重要的。尽管 Vue 2 拥有极具竞争力的性能,但经过实验新的渲染策略,重写提供了一个更进一步的机会。

克服虚拟 DOM 的瓶颈

Vue 有一个至关独特的渲染策略。它提供了一个相似于 HTML 的模板语法,但将模板编译成了返回虚拟 DOM 树的渲染函数。该框架经过递归地走过两个虚拟 DOM 树,并比较每一个节点上的每个属性,计算出实际 DOM 的哪些部分须要更新。因为现代 JavaScript 引擎进行了高级优化,这种有点蛮力的算法通常来讲是至关快的,可是更新仍然会涉及到不少没必要要的 CPU 工做。当你看一个基本上是静态内容和只有几个动态绑定的模板时,效率低下的问题就特别明显--整个虚拟 DOM 树仍然须要递归地走一遍,以找出改变了什么。

幸运的是,模板编译步骤让咱们有机会对模板进行静态分析并提取动态部分的信息。Vue 2 经过跳过静态子树在必定程度上作到了这一点,但因为编译器架构过于简单化,更高级的优化很难实现。在 Vue 3 中,咱们用适当的 AST transform pipeline 重写了编译器,这使得咱们能够用变换插件的形式来进行编译时的优化。

有了新的架构,咱们但愿找到一种可以尽量消除开销的渲染策略。一个选择是抛弃虚拟 DOM,直接生成必要的 DOM 操做,但这将消除直接编写虚拟 DOM 渲染函数的能力,咱们发现这对高级用户和库做者来讲是很是有价值的。另外,这将是一个巨大的突破性改变。

接下来最好的办法就是去掉没必要要的虚拟 DOM 树遍历和属性比较,这些每每在更新过程当中的性能开销最大。为了实现这个目标,编译器和运行时须要协同工做。编译器分析模板,并生成带有优化提示的代码,而运行时则接收这些提示并尽量地采起快速路径。这里有三个主要的优化工做。

首先,在树级,咱们注意到,在没有模板指令动态改变节点结构的状况下,节点结构保持彻底静态(例如,v-if 和 v-for)。若是咱们把一个模板划分红由这些结构指令分隔的嵌套 "块",那么每一个块内的节点结构又变得彻底静态。当咱们更新块内的节点时,咱们再也不须要递归地遍历块内的树形动态绑定,能够在一个平面数组中跟踪。这种优化避免了虚拟 DOM 的大部分开销,减小了咱们须要执行的树状遍历量,减小了一个数量级。

其次,编译器会主动检测模板中的静态节点、子树,甚至是数据对象,并在生成的代码中把它们挂在渲染函数以外。这就避免了在每次渲染时从新建立这些对象,极大地提升了内存使用量,减小了垃圾回收的频率。

第三,在元素层面,编译器还会根据每一个具备动态绑定的元素须要执行的更新类型,为其生成一个优化标志。例如,一个具备动态类绑定和多个静态属性的元素将收到一个标志,提示只须要进行类检查。运行时将接收到这些提示并采起专用的快速路径。

CPU 时间 即执行 JavaScript 计算所花费的时间,不包括浏览器 DOM 操做。

综合起来,这些技术大大改善了咱们的渲染更新基准,Vue 3 有时只须要不到 Vue 2 的十分之一的 CPU 时间。

最大限度地减小软件包的大小

框架的大小也会影响到它的性能。这对于 Web 应用来讲是一个独特的问题,由于资产须要实时下载,在浏览器解析了必要的 JavaScript 以后,应用程序才会进行交互。这对于单页面应用来讲尤为如此。虽然 Vue 一直以来都是相对轻量级的--Vue 2 的运行时大小约为 23 KB gzipped,但咱们注意到了两个问题。

首先,不是每一个人都会使用该框架的全部功能。例如,一个从未使用过过渡功能的应用仍然要支付过渡相关代码的下载和解析成本。

第二,随着咱们添加新的功能,框架不断地无限增加。当咱们考虑增长新功能的权衡时,这就赋予了捆绑大小不成比例的权重。所以,咱们倾向于只包含大多数用户会使用的功能。

理想的状况下,用户应该可以在构建的时候,为未使用的框架功能丢弃代码,也就是所谓的 "动摇树"--只为他们使用的功能付费。这也可让咱们在不增长其余用户的付费成本的状况下,为一部分用户提供有用的功能。

在 Vue 3 中,咱们经过将大部分的全局 API 和内部帮助器转移到 ES 模块导出中来实现了这一点。这使得现代捆绑器可以静态分析模块的依赖性,并丢弃与未使用的导出相关的代码。模板编译器还生成了树状的友好代码,只有在模板中实际使用某个功能的时候才会导入帮助器。

框架中的一些部分永远不能被树形动摇,由于它们对于任何类型的 app 都是必不可少的。咱们把这些必不可少的部分的衡量标准称为基线大小。Vue 3 的基线大小约为 10KB 左右,尽管增长了许多新功能,但它的基线大小还不到 Vue 2 的一半。

解决规模化的需求

咱们还但愿提升 Vue 的处理大规模应用的能力。咱们最初的 Vue 设计的重点是低准入门槛和温和的学习曲线。但随着 Vue 的应用愈来愈普遍,咱们更多的了解到了包含数百个模块的项目的需求,而且由几十个开发人员长期维护的项目。对于这些类型的项目来讲,像 TypeScript 这样的类型系统和干净利落地组织可重用代码的能力是相当重要的,而 Vue 2 在这些方面的支持并不理想。

在设计 Vue 3 的早期阶段,咱们试图经过提供对使用类编写组件的内置支持来改进 TypeScript 的集成。咱们所面临的挑战是,咱们须要的许多语言特性,如类字段和装饰符等,在正式成为 JavaScript 的一部分以前,仍然是建议--并且可能会发生变化。所涉及的复杂性和不肯定性让咱们怀疑增长类 API 是否真的合理,由于它除了稍微好一点的 TypeScript 集成以外,并无提供其余任何东西。

咱们决定研究其余方式来攻击缩放问题。受 React Hooks 的启发,咱们想到了暴露出底层的反应性和组件生命周期 API,以实现一种更自由的方式来编写组件逻辑,称为 Composition API。与其经过指定一长串选项来定义一个组件,Composition API 容许用户像写函数同样自由地表达、编译和重用有状态的组件逻辑,同时提供了出色的 TypeScript 支持。

咱们对这个想法很是兴奋。虽然 Composition API 是为了解决特定类别的问题而设计的,但在技术上,只有在编写组件时才可使用它。在提案的第一稿中,咱们有点超前了,并暗示咱们可能会在将来的版本中用 Composition API 替换现有的 Options API。这致使了社区成员的巨大反击,这给咱们上了宝贵的一课,让咱们明白了如何清晰地传达长期计划和意图,以及了解用户的需求。在听取了社区成员的反馈后,咱们对提案进行了完全的修改,明确了 Composition API 将是对 Options API 的补充和补充。修改后的提案获得了更多的好评,并收到了不少建设性的建议。

寻求平衡

开发者简介的多样性与用例的多样性相对应。
在 Vue 的用户群中,超过 100 万的开发者中,有只懂 HTML/CSS 的初学者,也有从 jQuery 迁移过来的专业人士,有从其余框架迁移过来的老手,有寻找前端解决方案的后端工程师,也有处理规模化软件的软件架构师。开发者配置文件的多样性与用例的多样性相对应。一些开发人员可能但愿在传统的应用程序上洒上交互性,而另外一些开发人员多是一次性的项目,周转速度快,但维护问题有限;架构师可能须要处理大型的、多年期的项目,而且在项目的生命周期内,开发人员的团队起伏不定。

Vue 的设计一直在不断地塑造和了解这些需求,在各类权衡中寻求平衡。Vue 的口号是 "渐进式框架",这句话归纳了这个过程当中产生的分层 API 设计。初学者能够经过 CDN 脚本、基于 HTML 的模板和直观的 Options API 享受平稳的学习曲线,而专家们能够经过全功能的CLI、渲染函数和 Composition API 来处理宏大的用例。

为了实现咱们的愿景,还有不少工做要作--最重要的是,更新支持库、文档和工具以确保顺利迁移。咱们将在将来的几个月里努力工做,咱们火烧眉毛地想看看社区将用 Vue 3 创造出什么。

相关文章
相关标签/搜索