制作Vue 3

2020-05-28 21:33:54

在过去的一年里,Vue团队一直在研究Vue.js的下一个主要版本,我们希望在2020年上半年发布。(在撰写本文时,这项工作正在进行中。)。Vue新主版的想法是在2018年末形成的,当时Vue 2的代码库大约有两年半的历史。在通用软件的生命周期中,这听起来不是很长的时间,但在这段时间里,前端的格局发生了巨大的变化。

两个关键的考虑因素导致了Vue的新的主要版本(和重写):第一,新的JavaScript语言特性在主流浏览器中的普遍可用性。第二,当前代码库中的设计和架构问题已经暴露了一段时间。

随着ES2015的标准化,JavaScript(正式名称为ECMAScript,缩写为ES)得到了重大改进,主流浏览器终于开始为这些新功能提供像样的支持。特别是一些人为我们提供了极大地提高Vue的开发能力的机会。

其中最值得注意的是Proxy,它允许框架拦截对对象的操作。Vue的一个核心特性是能够监听对用户定义的状态所做的更改,并反应性地更新DOM。VUE 2通过用getter和setter替换state对象上的属性来实现此反应性。切换到Proxy将允许我们消除Vue的现有限制,例如无法检测新的属性添加,并提供更好的性能。

但是,代理是一种本机语言功能,不能在传统浏览器中完全多填充。为了利用它,我们知道我们必须调整框架的浏览器支持范围-这是一个重大的突破性变化,只能在新的主要版本中提供。

在维护Vue 2的过程中,我们积累了许多问题,这些问题由于现有架构的限制而难以解决。例如,模板编译器的编写方式使得正确的源代码映射支持非常具有挑战性。此外,虽然Vue 2在技术上支持构建面向非DOM平台的高级渲染器,但为了实现这一点,我们必须派生代码库并复制大量代码。在当前的代码库中修复这些问题将需要巨大的、高风险的重新考虑,这几乎等同于重新编写代码。

与此同时,我们积累了各种模块内部与似乎不属于任何地方的浮动代码之间的隐式耦合形式的技术债务。这使得孤立地理解代码库的一部分变得更加困难,我们注意到贡献者很少有信心做出重要的更改。重写将使我们有机会重新思考代码组织,同时考虑到这些事情。

我们在2018年末开始制作Vue 3的原型,初步目标是验证这些问题的解决方案。在这一阶段,我们主要集中在为进一步的发展奠定坚实的基础。

VUE 2最初是用简明的ES写成的。在原型阶段之后不久,我们意识到一个类型系统对于这样一个规模如此庞大的项目将非常有帮助。类型检查极大地减少了在重构过程中引入意外错误的机会,并帮助贡献者更有信心进行重大更改。我们采用了Facebook的流类型检查器,因为它可以逐渐添加到现有的Plain-ES项目中。流在一定程度上起到了帮助作用,但是我们并没有像我们希望的那样从中受益;尤其是,不断的突破性变化使升级成为一件痛苦的事情。与TypeScript与Visual Studio代码的深度集成相比,对集成开发环境的支持也不理想。

我们还注意到,用户越来越多地同时使用Vue和TypeScript。为了支持他们的用例,我们必须与使用不同类型系统的源代码分开创作和维护类型脚本声明。切换到TypeScript将允许我们自动生成申报文件,从而减轻维护负担。

我们还采用了Monorepo设置,其中框架由内部包组成,每个包都有各自的API、类型定义和测试。我们希望使这些模块之间的依赖关系更加明确,使开发人员更容易阅读、理解和更改所有模块。这是我们努力降低项目的贡献门槛并提高其长期可维护性的关键。

到2018年底,我们有了一个使用新反应系统和虚拟DOM渲染器的工作原型。我们已经验证了我们想要进行的内部架构改进,但只有面向公众的API更改的粗略草稿。现在是时候把它们变成具体的设计了。

我们知道我们必须及早仔细地做这件事。VUE的广泛使用意味着破坏更改可能会导致用户的大量迁移成本和潜在的生态系统碎片化。为了确保用户能够就突发更改提供反馈,我们在2019年初采用了RFC(请求评论)流程。每个RFC都遵循一个模板,各部分重点介绍动机、设计细节、权衡和采用策略。由于该过程是在GitHub回购中进行的,建议以拉取请求的形式提交,因此在评论中有机地展开了讨论。

事实证明,RFC过程非常有帮助,它作为一个思维框架,迫使我们充分考虑潜在变化的所有方面,并允许我们的社区参与设计过程并提交深思熟虑的功能需求。

性能对前端框架至关重要。尽管Vue 2号称具有竞争力的性能,但重写提供了一个通过试验新的渲染策略来走得更远的机会。

VUE有一个相当独特的呈现策略:它提供类似HTML的模板语法,但是将模板编译成返回虚拟DOM树的呈现函数。该框架通过递归遍历两个虚拟DOM树并比较每个节点上的每个属性来确定要更新实际DOM的哪些部分。多亏了现代JavaScript引擎执行的高级优化,这种有点蛮力的算法通常相当快,但是更新仍然涉及大量不必要的CPU工作。当您查看一个主要包含静态内容而只有几个动态绑定的模板时,效率低下尤为明显-整个虚拟DOM树仍然需要递归遍历才能找出更改了什么

幸运的是,模板编译步骤使我们有机会执行模板的静态分析,并提取有关动态部件的信息。VUE 2通过跳过静态子树在一定程度上做到了这一点,但是由于过于简单的编译器架构,更高级的优化很难实现。在Vue 3中,我们使用适当的AST转换流水线重写了编译器,这允许我们以转换插件的形式编写编译时优化。

有了新的架构,我们希望找到一种可以尽可能多地消除开销的呈现策略。一种选择是放弃虚拟DOM,直接生成命令式DOM操作,但这将消除直接创作虚拟DOM呈现函数的能力,我们发现这对高级用户和库作者非常有价值。此外,这将是一个巨大的突破性变化。

其次是消除不必要的虚拟DOM树遍历和属性比较,这往往会在更新期间产生最大的性能开销。为了实现这一点,编译器和运行库需要协同工作:编译器分析模板并生成带有优化提示的代码,而运行库提取提示并尽可能采用快速路径。这里有三个主要的优化措施:

首先,在树级,我们注意到在没有动态改变节点结构的模板指令(例如,v-if和v-for)的情况下,节点结构保持完全静态。如果我们将模板划分为嵌套的块“,则每个块中的节点结构将再次变为完全静态。当我们更新块中的节点时,我们不再需要递归地遍历树-可以在平面数组中跟踪块内的动态绑定。这种优化将我们需要执行的树遍历量减少了一个数量级,从而规避了大量虚拟DOM的开销。“\。

其次,编译器会主动检测模板中的静态节点、子树甚至数据对象,并将它们挂起到生成的代码中的呈现函数之外。这避免了在每次渲染时重新创建这些对象,从而大大提高了内存使用率并降低了垃圾回收的频率。“\。

第三,在元素级别,编译器还根据需要执行的更新类型,通过动态绑定为每个元素生成优化标志。例如,具有动态类绑定和多个静态属性的元素将收到一个标志,该标志指示只需要进行类检查。运行库将拾取这些提示,并采用专用的快速路径。

综合起来,这些技术显著提高了我们的渲染更新基准,有时Vue 3占用的CPU时间还不到Vue 2的十分之一。

框架的大小也会影响其性能。这是Web应用程序唯一需要关注的问题,因为需要动态下载资产,并且在浏览器解析了必要的JavaScript之前,应用程序不会交互。对于单页应用程序尤其如此。虽然Vue一直以来都是相对轻量级的--Vue 2的运行时大小约为23KB gzip--但我们注意到了两个关键问题:

首先,并不是每个人都使用该框架的所有功能。例如,从未使用过渡功能的应用程序仍需支付与过渡相关的代码的下载和解析费用。

其次,随着我们添加新功能,框架会无限增长。当我们考虑添加新特性的权衡时,这给出了束大小不成比例的权重。因此,我们倾向于只包括我们的大多数用户将使用的功能。

理想情况下,用户应该能够在构建时删除未使用的框架功能的代码-也称为“树摇动”-并且只为他们使用的内容付费。这还将允许我们交付我们的部分用户会发现有用的功能,而不会增加其他用户的有效负载成本。

在Vue 3中,我们通过将大多数全局API和内部助手移到ES模块导出来实现这一点。这允许现代捆绑程序静态分析模块依赖关系,并删除与未使用的导出相关的代码。模板编译器还会生成树抖动友好的代码,只有在模板中实际使用某个功能时才会导入该功能的帮助器。

框架的某些部分永远不能动摇,因为它们对于任何类型的应用程序都是必不可少的。我们称这些不可缺少的部分的度量为基线大小。VUE 3的基线大小约为10KB gzip-不到Vue 2的一半,尽管增加了许多新功能。

我们还希望提高Vue处理大规模应用程序的能力。我们最初的VUE设计侧重于较低的入门门槛和平缓的学习曲线。但是,随着Vue被更广泛地采用,我们对包含数百个模块的项目的需求有了更多的了解,并且随着时间的推移,这些项目由数十名开发人员维护。对于这些类型的项目,像TypeScript这样的类型系统和干净地组织可重用代码的能力是至关重要的,而Vue 2在这些方面的支持并不理想。

在设计Vue 3的早期阶段,我们试图通过提供对使用类创作组件的内置支持来改进TypeScript集成。挑战在于,让类变得可用所需的许多语言特性(如类字段和装饰符)仍然是提案-在正式成为JavaScript的一部分之前,这些特性可能会有所更改。所涉及的复杂性和不确定性让我们质疑添加Class API是否真的合理,因为它只提供稍好的TypeScript与集成。

我们决定研究其他方法来解决伸缩问题。受Reaction Hooks的启发,我们考虑公开较低级别的反应性和组件生命周期API,以支持一种更自由形式的组件逻辑创作方式,称为组合API。合成API不是通过指定一长串选项来定义组件,而是允许用户像编写函数一样自由表达、合成和重用有状态组件逻辑,同时提供出色的TypeScript和支持

我们对这个想法真的很兴奋。尽管组合API是为解决特定类别的问题而设计的,但从技术上讲,只有在创作组件时才可以使用它。在提案的第一稿中,我们有点超前了,并暗示我们可能会在将来的版本中用组合API替换现有的Options API。这导致了社区成员的大规模抵制,这给我们上了一堂宝贵的课,让我们能够清晰地沟通长期计划和意图,以及理解用户的需求。在听取了我们社区的反馈后,我们完全修改了提案,明确表示组合API将是Options API的补充和补充。修订后的提案得到了积极得多的接受,并收到了许多建设性的建议。

在Vue 100多万开发人员的用户群中,有对HTML/CSS只有基本了解的初学者、从jQuery转行的专业人士、从另一个框架迁移过来的老手、寻找前端解决方案的后端工程师,以及大规模处理软件的软件架构师。开发人员配置文件的多样性与用例的多样性相对应:一些开发人员可能希望在遗留应用程序上分散交互性,而另一些开发人员可能正在进行一次性项目,周转时间很快,但维护问题有限;架构师可能不得不处理大规模、多年的项目以及在项目的整个生命周期中不断变化的开发人员团队。

VUE的设计不断受到这些需求的影响,因为我们寻求在各种权衡之间取得平衡。VUE的口号是渐进式框架,“封装了这个过程产生的分层API设计。初学者可以通过CDN脚本、基于HTML的模板和直观的Options API享受流畅的学习曲线,而专家可以通过功能齐全的CLI、渲染函数和API组合来处理雄心勃勃的使用案例。

要实现我们的愿景,还有很多工作要做-最重要的是,更新支持库、文档和工具以确保顺利迁移。在接下来的几个月里,我们将努力工作,我们迫不及待地想看看社区将用Vue 3创造什么