前言
Taro Next 架构揭晓。 今天晨读文章由京东@授权分享。
正文从这里开始~~
随着小程序开发的普及,小程序开发框架也层出不穷。 然而,每个框架目前都绑定到专用的 DSL,例如类或 Vue 类。 在一个框架内,开发者无法根据团队的技术栈自由选择DSL,也无法共享框架本身的生态系统和工具。
本次分享将介绍Taro如何在小程序上运行各种语法(如:/Vue等)的前端框架,并讨论支持多种DSL的框架的实现探索,以便开发者可以使用任何流行的框架/语法/DSL 编写小程序应用,同时复用相关生态。
小程序开发流程
2017年1月9日凌晨,万众期待的微信小程序正式上线。
此前,京东投资了一个小型前端团队。 经过一个月的封闭开发和以每周一个版本的速度迭代,京东终于第一时间发布了自己的“京东购物”小程序。 虽然功能和界面目前还不清楚。 看上去有些粗糙,但在当时完全符合微信小程序“触手可及,用完即走”的理念。
当时微信小程序的开发存在依赖管理混乱、工程流程落后、ES Next支持不完善、命名标准不一致等缺点。 现在看来这些问题都有各种官方或非官方的解决方案,但在当时小程序开发的探索阶段,这些问题都是一些痛点。
有一句话我个人很喜欢,那就是“当一种语言的能力不足,并且用户的操作环境不支持其他选项时,该语言就会成为‘编译目标’语言”。
纵观前端的历史,无论是CSS预处理器的流行,还是各种模板的流行,甚至技术的诞生,都印证了这一说法,微信小程序也不例外。 因此,各种小程序开发框架百花齐放,层出不穷。
这些小程序开发框架的主要区别在于DSL。 这一点从标志的颜色就可以看出来。 除滴滴自定义DSL外,其余绿色标志遵循Vue语法(如),蓝色标志遵循语法(如Taro)。
微信小程序之后,各大厂商都发布了自己的小程序平台,如支付宝、百度、头条、QQ等,再加上快用、网易、360、京东等,小程序赛道日趋丰富。并且更受欢迎。 拥堵,开发者需要适应的小程序平台越来越多。 因此,各种大大小小的程序开发框架也进行了多端适配。
因此,在这个时间点回顾整个小程序开发框架的流程,你会发现整个2018年甚至2019年初,小程序开发框架的主要区别和重点在于:DSL和多端适配。
芋头的由来和初衷
俗话说“创业孵化科技,科技服务创业”,太郎的诞生源于业务需求的增加。 当时我们团队需要负责:京东购物,同时还要负责其他行业。 该团队的人力资源捉襟见肘。 同时,上述业务都或多或少有多端需求,比如微信小程序、H5、(京东主流APP基本都内置了渲染引擎),可以预见,未来,可能需要适配更多的小程序平台,而为每个终端开发一套代码是不现实的,这会导致:研发成本增加,代码维护困难。
当时我们团队开发了一个自研的类框架:整个团队的技术栈因此被彻底改造。 当时市面上还没有遵循语法的小程序框架。 因此,我们开发了Taro,希望能够使用语法来编写小程序。 程序同时通过“once Run”实现跨终端执行。
自 2018 年 6 月 7 日整个 Taro 框架开源以来,一直保持高速迭代。 这些迭代主要集中在三个方面:
经过团队一年多的努力,Taro 得到了社区的广泛认可。 截至2019年12月18日,Taro拥有250名用户,社区积极提交了150+个开发案例:taro-user-,其中有很多案例。
但尽管如此,太郎还是有一些问题无法解决,或者换句话说:没那么容易解决。 比如:与 DSL 的强绑定、JSX 适配工作量大、社区贡献复杂等等。归根结底,这些问题很大一部分都是 Taro 的架构问题。
因此,我们团队一直在等待合适的机会来改进整个架构,修复一些项目快速迭代所欠下的技术债务。
最重要的是,简单的项目维护迭代已经不能满足我们团队躁动的心了。 我们渴望借此机会取得技术突破。
小程序跨框架开发探索
在讲Taro架构之前,我们先回顾一下小程序的架构。
微信小程序主要分为逻辑层和视图层,以及它们下面的原生部分。 逻辑层主要负责JS运行,视图层主要负责页面渲染。 它们主要与数据通信并调用原生API。 这也是大多数小程序的架构,包括微信小程序。
由于原生部分对于前端开发者来说就像是一个黑匣子,所以整个架构图中的原生部分可以省略。 同时我们还对逻辑层和视图层进行了简化,最终可以得到一个极简版的小程序架构图:
也就是说,只需要在逻辑层调用对应的App()/Page()方法,在方法中处理数据、提供生命周期/事件函数等,同时提供对应的模板和用于在视图层中渲染的样式。 运行小程序。 这也是大多数小程序开发框架重点关注和处理的部分。
Taro 当前架构
Taro目前的架构主要分为:编译时和运行时。
编译过程主要是将Taro代码转换成小程序代码,例如:JS、WXML、WXSS、JSON。 运行时主要进行生命周期、事件、数据等部分的一些处理和对接。
太罗编译时间
有过插件开发经验的人应该非常熟悉下面的流程。 Taro在编译时也遵循这个过程。 使用 - 将 Taro 代码解析为抽象语法树,然后使用 - 对抽象语法树进行一系列修改和转换操作。 最后,再通过 - 生成对应的目标代码。
详情请参阅:-
整个编译过程中最复杂的部分是JSX编译。
我们都知道JSX是一种语法扩展,其编写方式千变万化,非常灵活。 这里我们用穷举的方法来一一适应JSX可能的写法。 这部分的工作量是非常繁重的。 其实Taro有很多,是为了更好的支持JSX的各种写法。
但尽管如此,我们并不能完全覆盖所有情况,所以我们仍然建议您按照官方规范编写代码。 同时我们还提供丰富的插件来协助您编写标准化的代码。
我们团队内部一直有一个笑话:如果你觉得用Taro开发的时候bug少了,说明你的代码写得很规范。
太郎运行时
接下来我们可以对比编译后的代码,发现编译后的代码中,缺少核心方法。 同时代码中添加了 和 ,它们是Taro运行时的核心。
// 编译前
import Taro, { Component } from '@tarojs/taro'
import { View, Text } from '@tarojs/components'
import './index.scss'
export default class Index extends Component {
config = {
navigationBarTitleText: '首页'
}
componentDidMount () { }
render () {
return (
<View className=‘index' onClick={this.onClick}>
Hello world!
)
}
}
// 编译后
import {BaseComponent, createComponent} from '@tarojs/taro-weapp'
class Index extends BaseComponent {
// ...
_createDate(){
//process state and props
}
}
export default createComponent(Index)
大概的UML图如下,主要是替换重写了一些核心方法: 与代码编译后实际运行时无关。
主要功能是调用()构建页面; 连接事件、生命周期等; 执行 Diff Data 并调用方法来更新数据。
总结
因此,整个Taro当前架构的特点是:
其他解决方案的架构
小程序开发框架百花齐放,我们也从社区得到了很多启发。
接下来我们看一下遵循vue语法的小程序开发框架的代表:如何实现。
看过Vue源码的同学一定对上面的文件夹和结构很熟悉。 本质上是/vue@2.4.1代码的fork,保留了Vue的能力,并增加了对小程序平台的支持。
源码中的具体表现是:在Vue源码文件夹下添加mp目录,并在其中实现(编译时)和(运行时)支持。
实现也分为:编译时和运行时。
编译时间
编译过程中所做的事情与Taro非常相似:将Vue SFC代码编译成小程序代码文件(JS、WXML、WXSS、JSON)。
最大的区别在于,Taro 将 JSX 编译为小程序模板,而将 Vue 模板编译为小程序模板。 不过由于Vue模板和小程序模板的相似性,这方面的工作量比Taro要少很多。
运行
运行时和Vue的运行时是强相关的。 首先,我们来看看Vue的运行时。
单个 .vue 文件由三部分组成: 、 、 。
橙色路径部分在编译过程中会通过vue-中的ast进行分析,最终生成一个函数。 执行该函数将生成一棵虚拟 DOM 树。 虚拟 DOM 树是真实 DOM 树的抽象。 树中的节点称为 do。
Vue 获取虚拟 DOM 树后,可以与上一个旧的虚拟 DOM 树进行 diff 比较。 此阶段之后,vue 将使用真实的 DOM 操作方法(例如等)来操作 DOM 节点并更新视图。
同时,在绿色路径部分,实例化Vue时,会对数据进行响应式处理。 当检测到数据发生变化时,会调用一个函数来生成最新的虚拟DOM树,然后与旧的虚拟DOM树进行比较。 要继续,找到修改成本最小的节点并修改它。
运行时,该阶段的DOM操作相关方法首先会被清空,即不执行任何操作。 其次,在创建Vue实例的同时,也会偷偷调用Page()来生成小程序的页面实例。 那么运行时阶段会直接调用$()方法。 该方法会获取页面实例上维护的数据,然后通过该方法更新到视图层。
整体示意图如下:
一些总结和想法
因此,与Taro的重新编译和轻运行时不同,它是:一半编译时间,一半运行时间。 这从代码量的对比也可以大致反映出来。
Taro的WXML模板也是通过代码编译得到的; 与Taro不同的是,它是运行时无关的,本质上是在一个小程序中运行Vue,并实现了Vue@2.4.1的大部分功能(只有少数功能由于小程序模板的限制而无法实现,例如:, 槽, v-html); 整个框架是基于比较完整的工程。
其他小程序框架的实现原理和效果的差异也给我们带来了一些思考:
编译时间OR运行时:Taro选择重新编译的主要原因是出于性能考虑。 毕竟,同等条件下,编译时完成的工作越多,意味着运行时完成的工作越少,性能会更好; 另外,重新编译也保证了编译后Taro代码的可读性。 但从长远来看,计算机硬件的性能变得越来越冗余。 如果我们牺牲一点可以忍受的性能来换取整个框架更大的灵活性和更好的适应性,我们认为是值得的。
模板静态编译OR动态构建:虽然Taro和模板都是通过静态编译生成的,但社区中也有动态构建的例子,例如:
DSL限制:我们能否实现一个小程序开发框架并摆脱DSL限制?
新架构Taro Next的适配与实现
这次,我们从浏览器的角度来思考前端的本质:无论使用什么框架进行开发,是否是Vue,运行后的最终代码都会调用浏览器的BOM/DOM。 API,如:、、等
因此,我们创建了 taro- 包,然后在这个包中实现了一个高效精简版的 DOM/BOM API(下面的 UML 图仅反映了几个主要类的结构和关系):
然后,我们将插件注入到小程序的逻辑层中。
这样,当小程序运行时,就会有一个高效精简版的DOM/BOM API。
完成
DOM/BOM注入后,理论上Nerv/可以直接运行。 但它有点特殊,因为-DOM包含了大量浏览器兼容的类代码,导致包太大。 这部分代码我们不需要,所以我们需要做一些定制和优化。
在16+中,架构如下:
最上面一层是核心部分,中间部分是-。 它的职责是维护这棵树。 它内部实现了Diff/算法来决定何时更新以及更新什么。
它负责特定平台的渲染工作,提供宿主组件、处理事件等。例如——DOM是渲染器,负责渲染DOM节点、处理DOM事件。
因此,我们实现了 taro- 包来连接 - 和 taro- 的 BOM/DOM API:
具体实现主要分为两步:
经过以上步骤,小程序运行时代码其实就可以正常运行了,并且会生成一棵Taro DOM Tree。 那么如何将庞大的Taro DOM Tree更新到页面呢?
首先,我们将小程序的所有组件一一模板化,得到小程序组件对应的模板。 下图是小程序的视图组件模板化后的样子:
然后我们:基于组件,动态“递归”渲染整个树。
具体过程是首先遍历Taro DOM Tree根节点的子元素,然后根据每个子元素的类型选择对应的模板来渲染子元素,然后在每个模板中我们会遍历Taro DOM Tree根节点的子元素当前元素。 递归遍历整个节点树。
整个Taro Next实现流程图如下:
Vue实现
虽然在开发的时候它和Vue的差别那么大,但实际上,在BOM/DOM API实现之后,它们之间的差别就非常小了。
Vue 和 Vue 最大的区别在于运行时方法,它执行一些运行时处理,例如生命周期对齐。
其他部分如通过BOM/DOM方法构建和修改DOM Tree以及渲染原理等与.
完成
说到这里,就不得不提到Web。 Web 是在标准浏览器 API 之上实现的核心绘图层。 本质上,它最终调用的是BOM/DOM API。 所以理论上是可以适应的,但是我们不会在这方面投入太多的精力,最终会像快应用一样交给社区去实现和维护。