原文连接: The process: Making Vue 3 – Increment: Frontend
翻译: 谷歌翻译
翻译调整:Mario
在过去的一年中,Vue团队一直在忙于开发Vue.js的下一个主版本,咱们但愿在2020年上半年发布该版本。(在撰写本文时,这项工做仍在进行中)。一个新的Vue主版本的想法造成于2018年末,当时Vue 2的代码库已有两年半的历史了。在通用软件的生命周期中听起来可能并不长,但在此期间,前端环境发生了巨大变化。前端
有两个主要的因素使咱们考虑开发新的(重写版的)Vue主版本:首先,主流浏览器广泛提供了新的JavaScript语言功能。其次,随着时间的推移,当前代码库中的设计和体系结构问题已经暴露出来。vue
为何要改写
利用新的语言功能
随着ES2 015 的标准化,JavaScript(正式称为ECMAScript,缩写为ES)得到了重大改进,主流浏览器终于开始为这些新功能提供不错的支持。一些改进尤为为咱们提供了极大提升Vue功能的机会。git
其中最值得注意的是Proxy,它容许框架拦截对象上的操做。Vue的一个核心功能是可以侦听用户定义状态的更改并响应式更新DOM的能力。Vue 2经过使用getter和setter替换状态对象上的属性来实现这种响应能力。切换到Proxy将使咱们消除Vue的现有限制,例如没法检测到新的属性添加并提供更好的性能。github
可是,Proxy是一种语言原生功能,不能在旧版浏览器中彻底Polyfill。为了应用它,咱们知道咱们必须调整框架的浏览器支持范围,这是一个非兼容性更新,只能在新的主版本中发布。算法
解决架构问题
在维护Vue 2的过程当中,因为现有架构的局限性,咱们积累了许多难以解决的问题。例如,模板编译器的编写方式使适当的source-map支持很是具备挑战性。一样,虽然Vue 2从技术上容许构建针对非DOM平台的更高级别的渲染器,但咱们必须分叉代码库并复制大量代码,才能实现这一点。要在当前的代码库中解决这些问题,将须要进行大量风险较大的重构,这几乎等同于重写。后端
同时,因为各类模块内部与彷佛不属于任何模块的浮动代码之间的隐式耦合,咱们积累了大量的技术债务,这使得孤立地理解代码库的一部分变得更加困难;而且咱们注意到,贡献者不多会对进行重要的更改充满信心。重写将使咱们有机会牢记这些注意事项来从新考虑代码组织。api
初始原型阶段
咱们于2018年末开始对Vue 3进行原型设计,其初步目标是验证这些问题的解决方案。在此阶段,咱们主要致力于为进一步发展奠基坚实的基础。数组
切换到TYPESCRIPT
Vue 2最初是用纯ES编写的。在原型开发阶段以后不久,咱们意识到类型系统对于这种规模的项目将很是有益。类型检查极大地减小了在重构期间引入意外错误的机会,并有助于贡献者更自信地进行重要的更改。咱们引入了Facebook的Flow类型检查,由于它能够逐步添加到现有的普通ES项目。Flow的确有所助益,可是咱们得到的收益并不如预期。尤为是持续的非兼容性更新使升级变得痛苦。与TypeScript与Visual Studio Code的深度集成相比,集成开发环境的支持也不理想。浏览器
咱们还注意到,愈来愈多的用户同时使用Vue和TypeScript。为了支持它们的应用场景,咱们不得不单独编写和维护TypeScript声明,只是由于源代码使用了不一样的类型系统。切换到TypeScript将使咱们可以自动生成声明文件,从而减轻了维护负担。前端框架
解耦内部封装
咱们还采用了单一存储库的结构,整个框架由内部软件包组成,每一个内部软件包都具备本身的单独API、类型定义和测试。咱们但愿使这些模块之间的依赖关系更加明确,从而使开发人员更容易阅读、理解并进行全部更改。这是咱们努力下降项目贡献壁垒并提升其长期可维护性的关键。
设置RFC流程
到2018年末,咱们有了一个使用新的响应式系统和虚拟DOM渲染器的工做原型。咱们已经验证了咱们想要进行的内部体系结构改进,可是只包含了面向公众的API更改的雏形,如今是将它们变成具体设计的时候了。
咱们知道咱们必须尽早谨慎地作到这一点。Vue的普遍使用意味着非兼容性更新可能致使用户大量迁移成本和潜在的生态系统碎片化。为了确保用户可以提供有关非兼容性更新的反馈,咱们在2019年初引入了RFC(征求意见)流程。每一个RFC使用了一个由多个小节组成的模块,各个小节分别聚焦于动机、设计细节、权衡和应用策略。因为此过程是在GitHub存储库中进行的,提案是做为请求请求提交的,所以讨论会在注释中天然展开。
做为一个思想框架,它迫使咱们要充分考虑潜在变化的方方面面,让咱们的社区参与设计过程,并提交深思熟虑出来的功能要求,该RFC的过程已被证实很是有用。
更快更小
性能对于前端框架相当重要。尽管Vue 2具备出色的性能,但经过尝试新的渲染策略,重写提供了进一步发展的机会。
克服虚拟DOM的瓶颈
Vue有一个至关独特的渲染策略:它提供相似于HTML的模板语法,但将模板编译为可返回虚拟DOM树的渲染函数。框架经过递归遍历两个虚拟DOM树并比较每一个节点上的每一个属性来肯定实际DOM的哪些部分须要更新。因为现代JavaScript引擎执行了高级优化,所以这种略显暴力的算法一般很快,可是更新仍然涉及许多没必要要的CPU工做。当您查看包含大量静态内容且只有少许动态绑定的模板时,效率低下尤为明显——整个虚拟DOM树仍然须要递归遍历以了解什么地方发生了变化。
幸运的是,模板编译步骤使咱们有机会对模板进行静态分析并提取有关动态部分的信息。Vue 2经过跳过静态子树在某种程度上作到了这一点,可是因为过于简单的编译器体系结构,难以实施更高级的优化。在Vue 3中,咱们使用适当的AST转换管道重写了编译器,这使咱们可以以转换插件的形式编写编译时优化。
有了新的体系结构,咱们但愿找到一种渲染策略,以尽量减小开销。一种选择是放弃虚拟DOM并直接生成命令式DOM操做,但这将阉割了对高级用户和库做者极具价值的、直接编写虚拟DOM渲染功能的能力。另外,这会是一个巨大的非兼容性更新。
其次,最好的方法是消除没必要要的虚拟DOM树遍历和属性比较,这在更新过程当中每每会带来最大的性能开销。为了实现这一点,编译器和运行时须要协同工做:编译器分析模板并生成带有优化提示的代码,而运行时将拾取提示并在可能的状况下采用快速路径。这里有三个主要的优化工做:
首先,在树的层面上,咱们注意到,节点结构会在没有模板指令(例如,v-if和v-for)动态改变节点结构的场合下彻底保持静态。若是咱们将模板分为由这些结构指令分隔的嵌套“块”,每一个块内的节点结构又会变得彻底静态。当咱们更新一个块内的节点时,咱们再也不须要递归遍历树——块内的动态绑定可由一个扁平数组跟踪。经过将咱们须要执行的树遍历量减小一个数量级,此优化可避免虚拟DOM的大部分开销。\
其次,编译器会主动检测模板中的静态节点、子树甚至数据对象,并将其提高到生成代码中的渲染函数以外。这样能够避免在每次渲染时从新建立这些对象,从而大大提升了内存使用率并减小了垃圾回收的频率。\
第三,在元素级别,编译器还会根据须要执行的更新类型为具备动态绑定的每一个元素生成一个优化标志。例如,具备动态类绑定和许多静态属性的元素将收到一个标志,指示仅须要进行类检查。运行时将获取这些线索并采用专用的快速路径。
CPU时间,即执行JavaScript计算所花费的时间,不包括浏览器DOM操做。
综上所述,这些技术显著提升了咱们的渲染更新标准,有时,使用Vue 3只须要Vue 2的CPU时间的不到十分之一。
最小化打包尺寸
框架的大小也会影响其性能。这是Web应用程序的一个独有的关注点,由于资源须要即时下载,而且在浏览器解析必要的JavaScript以前,该应用程序将没法与用户交互,对于单页应用程序尤为如此。尽管Vue一直是相对轻量级的(gzip压缩过的Vue 2的运行时大小约为23KB),但咱们注意到了两个问题:
首先,并非每一个人都使用框架的全部功能。例如,从未使用过渡功能的应用仍需付出与过渡相关的代码下载、解析的代价。
其次,当咱们不断添加新功能时,框架会无限制地增加。所以在进行是否要添加新功能的权衡时,会给予打包尺寸不相称的权重。结果,咱们倾向于仅包含给大多数用户使用的功能。
理想状况下,用户应该可以在构建时删除未使用的框架功能的代码——也称为“摇树”——只为他们使用的功能付出代价。这也将使咱们可以发布一部分用户以为有用的功能,而不增长其余用户的负担。
在Vue 3中,咱们经过将大多数全局API和内部辅助工具移至ES模块来实现了这一目标。这使现代的打包器能够静态分析模块依赖并删除与未使用的模块相关的代码。模板编译器还会生成摇树友好的代码,仅导入该功能实际上在模板中使用的辅助工具。
框架的某些部分永远不会摇树,由于它们对于任何类型的应用程序都是必不可少的。咱们将这些必不可少的部分的尺寸称为基准尺寸。尽管增长了许多新功能,但gzip压缩的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旨在解决一大类特定的问题,但从技术上讲,能够仅在编写组件时才使用它。在该提案的初稿中,咱们有所超前地暗示咱们可能会在未来的版本中将现有的选项API替换为Composition API。这致使社区成员的严重抵制,这给咱们上了关于如何清楚地传达长期计划和意图,以及了解用户需求的宝贵一课。在听取了社区的反馈以后,咱们对提案进行了彻底的重写,从而明确代表Composition API将对选项API构成增益和补充。修改后的提案获得了更为积极的接受,并收到了许多建设性的建议。
寻求平衡
在Vue超过一百万的开发人员中,有只有HTML/CSS基础知识的初学者,有从jQuery迁移的专业人员,有从另外一个框架迁移过来的经验丰富的老兵,有正在寻找前端解决方案的后端工程师以及处理规模软件开发的软件架构师。开发人员类型的多样性与应用场景的多样性相对应:一些开发人员可能但愿在旧版应用程序上略增交互性,而另外一些开发人员可能从事快速交付的、维护需求有限的一次性项目,架构师可能不得不在整个生命周期中应对项目的庞大规模、漫长周期以及开发团队频繁变更。
在咱们寻求平衡各类折衷方案的同时,Vue的设计不断受到这些需求的影响和启发。Vue的口号,“渐进式框架”封装了由此过程产生的分层API设计。初学者可使用CDN脚本、基于HTML的模板和直观的选项API来轻松学习,而专家可使用功能齐全的CLI、渲染函数和Composition API来处理耗时费力的应用场景。
要实现咱们的愿景,还有许多工做要作——最重要的是,更新支持库、文档和工具以确保顺利迁移。在接下来的几个月中,咱们将继续努力,咱们火烧眉毛地想看到社区将经过Vue 3创造什么。
关于做者
Evan You 是独立的开源开发人员。他是Vue.js(当今使用最普遍的前端框架之一)的建立者和项目负责人。