前端早早聊大会-与掘金联合举办-全年学习

jj.001.jpeg

前言

大家下午好,我是来自京东凹凸实验室的陈嘉健,今天我的分享主题是如何借助 Taro Next 横穿跨端业务线。

jj.002.jpeg

Taro 是一款开源的多端统一开发框架,它让我们只编写一份代码,就可以让这份程序运行在各个小程序端、H5端、RN 端,在开源两年多以来收获了业界的很多关注,然后现在在 GitHub 上面的 Star 数有 25000+,同时业界也有非常多的团队正在使用 Taro 框架进行开发。那么在过去的一年里面,我们谋划着一个比较大的重构,也就是我今天将要分享的 Taro Next。

jj.003.jpeg

今天我的分享将会分为 5 个部分,首先是重构特有的背景,然后介绍 Taro Next 的一些新特性,接着是 Taro Next 的开发实践,然后会对 Taro Next 的部分原理进行简单分析,最后是对今天的分享做一个总结与展望。

一、Taro Next 背景

jj.004.jpeg

首先来介绍一下为什么我们要重构,主要有两个原因,第一个是为了解决目前架构上面的一些缺陷,第二个是希望新增对于多个框架的支持

1、Taro 架构

jj.005.jpeg

首先来看一下目前 Taro 的整体架构,它分为两个部分,第一部分是编译时,第二部分是运行时。编译时会先对用户的 React 代码进行编译,转换成各个端上的小程序都可以运行的代码,然后再在各个小程序端上面都配上一个对应的运行时框架进行适配,最终让这份代码运行在各个小程序端上面。那么这个架构有什么问题呢?

2、Taro 编译时缺陷

jj.006.jpeg

首先看一下编译的实现过程:编译时是借助 babel 去对源代码进行词法、语法分析、语义分析,得出一棵抽象语法树 AST,然后对这棵抽象语法树进行一些转换操作,最终得出目标代码。

这样的实现过程有三大缺点:

  • JSX 支持程度不完美。Taro 对 JSX 的支持是通过编译时的适配去实现的,但 JSX 又非常之灵活,因此还不能做到 100% 支持所有的 JSX 语法。
  • 不支持 source-map。Taro 对源代码进行了一系列的转换操作之后,就不支持 source-map 了,用户调试、使用这个项目就会不方便。
  • 维护和迭代十分困难。Taro 编译时代码非常的复杂且离散,维护迭代都非常的困难。

    3、Taro 运行时缺陷

jj.007.jpeg

再看一下运行时的缺陷。现在对于每个小程序平台,我们都会提供对应的一份运行时框架进行适配。当我们修改一些 Bug 或者新增一些特性的时候,需要我们同时去修改多份运行时框架,这显然是不合理的。

4、希望支持更多框架

jj.008.jpeg

另一方面我们也希望去支持更多的框架。目前 Taro 只支持 React,但是公司内部也有很多使用 Vue 技术栈的团队,她们也希望享受到 Taro 编译到多端的能力,因此我们就想能不能去支持 Vue,甚至 jQuery、Angular 等框架。

二、Taro Next 新特性

jj.009.jpeg

说完重构 Taro 的原因之后,我们再来说说 Taro 重构之后 Taro Next 的新特性。

1、Taro Next 架构

jj.010.jpeg

这是 Taro Next 的整体架构图,即使各个 Framework 的框架拥有不同的组件库、生命周期、API,但我们仍然可以选定微信小程序作为一个基准,然后使用各种框架,去对接微信小程序的组件库、生命周期、API,然后在 Taro 运行时,内部对各个平台的缺失部分进行补全。例如在小程序里面,它没有 DOM 和 BOM,那我们就去实现对应的DOM 和 BOM,然后在 H5 端并没有微信小程序规范的组件库、路由规范、API 等等,我们也要去实现,最后用 Webpack 去对运行时的代码进行打包,就可以让整份代码运行在不同的平台上面。

2、Taro Next 优点一

jj.011.jpeg

整体来说 Taro Next 有三个优点。第一点是 Taro Next 更强大了,我们现在不限制语言和语法,可以运行各种真实的框架,还提供了渲染 HTML 和跨框架组件这些比较特色的功能。

3、Taro Next 优点二

jj.012.jpeg

第二点是 Taro Next 更迅速了。我们剥除了 AST 操作,让构建速度更快。其次,我们也提升了项目初始化、更新操作的性能。除了 Taro 自身的优化外,我们还提供了一系列的优化选项给用户,让用户可以根据自己的实际情况对项目进行调用。

4、Taro Next 优点三

image.png

第三点是更灵活了,我们现在提供了一个插件化的系统,用户可以根据自己的场景去拓展一些自己的能力。然后我们把一些 Taro 的内部配置项、Webpack 的配置项给暴露出来,用户可以配置的项目就更加之多。其次对 Taro 的依赖做了瘦身,现在 Taro 用户可以根据自己的实际情况去安装对应的依赖,而不是像以前一样把所有的依赖都打包到 Taro 的 cli 里面。

三、开发实践

jj.014.jpeg

看完 Taro Next 新特性之后,再了解一下怎么样去使用 Taro 进行开发。

jj.015.jpeg

这一部分会分为 5 个小节,首先是开发前的一些准备工作,然后是开始编码,项目进阶,多端开发,还有性能优化。

1、准备工作

jj.016.jpeg

先来看一下我们开发之前要做一些什么样的准备工作。

环境准备

jj.017.jpeg

首先我们需要安装 Taro 的 CLI 工具。这里分两种情况,如果是要新建一个项目的话,可以使用 init 命令去新建一个全新的项目,如果用户自己本来就有一个微信小程序项目的话,我们也提供了 convert 命令,让用户可以把自己的微信小程序项目给转换到 Taro 项目中来,得到 Taro 项目之后,可以运行 build 命令,然后把 Taro 项目运行到各个端上面。

项目配置准备

jj.018.jpeg

在开始编码之前,也需要去对 Taro 进行一些配置。Taro 有一个配置文件,按区域划分,可分为公共配置、小程序配置、H5 配置。按种类划分,它就分为 Taro 内部配置项和 Webpack 相关的配置项。

babel 配置准备

jj.019.jpeg

接下来看一下对于 babel 的配置,目前 Taro 框架的 babel 配置是放在刚刚我们说的 Taro 配置里面的,但在 Taro Next 我们升级了 babel7,因此用户可以直接使用 babel 的配置文件 babel.config.js 进行配置,配置文件里面默认会带有 Taro 的 preset,Taro 的 preset 里面会有一些常用的 presets 和 plugins,比如 @babel/preset-env、@babel/plugin-transform-runtime 等等,如果用户在使用 React 或者 TypeScript 时,也会去加入对应的 presets。

App、Page 配置准备

jj.020.jpeg

接下来看一下 App 和 Page 配置和小程序相对应,Taro 里面也有一个入口配置文件 app.config.js,每个页面也会有自己对应的配置,但和小程序、目前的 Taro 只能写 JSON 形式的配置不一样的是,Tarol Next 使用了@babel/register 去加载这些配置文件,因此用户可以使用 ES Next 的语法去编写这些配置,使用起来就会更加灵活。

2、开始编码

jj.021.jpeg

做完项目开始之前要做的配置之后,再看一下我们怎么样开始编码。

入口组件

jj.022.jpeg

和小程序一样,我们首先编写一个入口组件 app.js。从代码架构上面看就是一个普通的 React Component,还有 Vue 的实例。首先我们需要去引入对应的框架,如果我们引入了样式的话,这份样式就会成为全局的样式。那么在组件内部也可以使用到 Taro 提供的一系列生命周期方法。入口组件会有一个 render 函数,render 函数里面是我们需要渲染的页面。

页面组件

jj.023.jpeg

我们写完一个入口组件之后,就要开始编写一系列的页面了。一个普通的页面组件,它同样也是一个 React 的 Component 或者 Vue 的 Component。如果在页面上我们引用了样式文件,这份样式文件就会成为页面级别的样式。同样的在页面内部也可以使用 Taro 提供的一系列生命周期方法。需要注意的是,React 如果要使用 Taro 的一些基础组件,需要从 @tarojs/component 先 import 再使用。而 Vue 则可以直接使用,因为我们借助 vue-loader 去分析用户使用了哪些基础组件。

组件化

jj.024.jpeg

我们在编写页面的时候会写很多的组件,所有的框架,他们的组件化语法我们都是完美支持的。但是这里需要注意的是,这些组件并不对应小程序的自定义组件,也就是说这些组件之间是没有样式隔离的。假设这些组件都各自拥有一份样式,它们最后会被编译为页面样式的一部分。

生命周期

jj.025.jpeg

接下来看一下我们可以使用哪些生命周期,以页面的生命周期为例,Taro 的生命周期分为两部分:第一部分是框架原有的生命周期方法,第二部分就是会以小程序一些比较特色的生命周期方法进行拓展

组件库

jj.026.jpeg

下面介绍组件库,刚刚也有提及,组件在 React 里面需要先引用再使用,原因是什么呢?无论是 Vue 或 React,都需要去引用 Taro 的组件库,Taro 组件库在小程序端会直接使用小程序对应的组件,但是在 H5 端,因为只有 HTML 标签,并没有小程序规范的组件,因此我们就使用了 Web Components 去实现了对应微信小程序规范的组件库。

API

jj.027.jpeg

接下来看一下 API,在小程序里面所有小程序规范的 API 在 Taro 里面都是完全支持的,使用上我们首先要从 @tarojs/taro 包里面引入 Taro 对象,然后就可以去调用 Taro 对象里的小程序 API。Taro API 它具体做了什么东西呢?在小程序端其实会直接调用小程序底层的 API,只是额外对这些 API 做了 Promise 化的操作,在 H5 端因为没有这些 API,所以我们对大部分常用小程序规范的 API 进行了一层 Mock。

Ref

jj.028.jpeg

最后来介绍一下 Ref,Taro Next 的 Ref 和目前 Taro 的实现有点不一样。如果我们使用框架的 Ref 语法,获取到的会是 Taro 的 HTMLElement 元素,我们可以去获取 HTMLElement 元素的一些节点属性或方法,甚至可以设置 style 以此来去更新视图等。但是如果我们要获取一些节点相关的信息,只能通过小程序提供获取真实渲染节点的方法,去获取到视图层对应的渲染节点,然后再去获取它的尺寸信息。

3、项目进阶

jj.029.jpeg

接下来是介绍项目进阶。

状态管理

jj.030.jpeg

在我们的项目越来越庞大的时候,我们可能会引入一些状态管理工具,在目前的 Taro 架构中,如果我们要使用 redux 或 mobx 的话,需要官方先对这些状态管理工具进行兼容,对接 Taro 的渲染机制,用户才可以安装使用兼容之后的包。

jj.031.jpeg

但是在 Taro Next 里面这些都是不需要的,用户可以直接使用 React-Redux、Mobx、Vuex 等状态管理工具。

样式工具

jj.032.jpeg

接下来看一下样式工具,刚才也提到了组件之间其实是没有样式隔离的。如果我们要使用样式作用域,我们可以使用CSS Modules。CSS Modules 的配置可以直接在 Taro 的配置文件里面开启。如果用户忠爱于 CSS in JS 这种写法的话,可以使用 linaria 去实现,具体的用法在文档里面有介绍,这里就不作展开了。

渲染 HTML

jj.033.jpeg

然后是渲染 HTML,我们有时候会需要直接去渲染 HTML 字符串,React 的 dangerouslySetInnerHTML 或 Vue 的 v-html 指令都是可以直接使用的。在实现这个功能的时候,我们也去看了业界很多优秀的 HTML Parser 方案,他们都非常棒,但是有两个问题,第一个是体积太大了,这样放在小程序里面就不太合适。第二个是他们都会先解析成为 DOM 层,然后我们还需要去对 DOM 层进行一次遍历,这样才能得到可以 setData 的数据,这里就会多了一层遍历。考虑到这两个问题,我们就自己实现了一个 HTML Parser,它能解析用户的 HTML 字符串,直接得到可以 setData 的数据结构,大大加快渲染 HTML 的速度。

4、多端开发

jj.034.jpeg

我们开发完一个项目之后,可能会有一些适配到多端的工作,接下来就介绍一下多端开发相关的知识。

原生小程序页面/组件

jj.035.jpeg

首先是使用原生的小程序页面/组件,使用一个原生的小程序页面很简单,只要在入口配置文件里面去配置对应的路由,然后指向那些原生小程序页面就可以了。如果需要使用原生小程序的组件,只要在页面配置里配置 usingComponents 字段,就可以在页面里去使用这些原生小程序组件。

跨平台开发

jj.036.jpeg

接下来是跨平台开发,这里我们提供了两种方式,第一种是环境变量。在 Taro 里面有一个环境变量叫 TARO_ENV,开发者可以使用它去做一些条件判断,根据不同平台使用不同的逻辑。但是如果我们的代码里面有很多这些 if else 的话,其实是很难维护的。

image.png

因此我们提供了第二种方式,多端文件。开发者可以像上图左侧所示来组织代码,命名规则是文件名 + 平台名 + 后缀,然后在 import 的时候只需要去 import 对应的文件名就可以了。在 Webpack 打包的时候会根据目前的平台再去引用对应的文件。

跨框架开发

jj.038.jpeg

最后会介绍一下跨框架的开发,我们考虑到可能有些用户需要开发一些在 React 和 Vue 上可以同时使用的组件,我们也提供了一个类似 jQuery 的写法,让用户去编写一个跨框架的组件,然后我们就可以在 React 和 Vue 中同时使用这些组件了。

5、性能优化

jj.039.jpeg

在开发完一个项目之后,可能我们会去做一些性能优化的工作,接下来就介绍一下性能优化相关的知识。

Taro 对 setData 的优化

jj.040.jpeg

首先介绍一下 Taro 对 setData 的优化。第一点是 Taro Next 的 setData 是最小颗粒度的 setData。考虑有以下场景,如果我们要 setData 一个很大的列表,在开发原生小程序的时候,常常会担心会不会把一些冗余的数据 setData 到视图层。但是在 Taro Next 里面,我们首先会对这个数据做一个 Hydrate 操作,去识别出渲染时候真正使用到的数据,最终只会 setData 这些渲染用到的数据,这样就可以大大减少 setData 的数据量。

jj.041.jpeg

第二点是按路径更新。我们在更新的时候会对新旧的 Taro DOM Tree 进行对比,然后将 diff 的结果使用小程序的按路径更新语法进行 setData,同样也可以大大减少 setData 的数据量。

预渲染

jj.042.jpeg

说完了 Taro 本身对于性能的一些优化之外,接下来介绍一下 Taro 提供给用户的一些优化选项。第一个是预渲染Prerender, Taro 整体渲染流程大概如上图左侧。 React 或 Vu 首先会去渲染出 Taro 的 DOM 树,Taro 内部会对 Taro DOM 树进行一层 Hydrate 操作得出可以 setData 的数据,最后再把数据 setData 到视图层。如果我们初始化的时候,DOM 树非常复杂,那我们 setData 的数据量就会非常大,这样页面就会出现短暂的白屏。因此我们提供了一个预渲染功能,用户可以使用预渲染的功能去预渲染出一个占位页面,然后等到 Taro DOM 树真正的 setData 完成之后,才展示可以真正交互的页面。

React

jj.043.jpeg

如果用户在使用 React 的话,那么 shouldComponentUpdate、PureComponent、React.Memo,这些方法都是可以使用的。

长列表优化

jj.044.jpeg

接下来介绍的是长列表优化。长列表在我们开发 Web 应用时是一个常见的性能瓶颈,因此我们提供了一个VirtualList 虚拟列表组件,虚拟列表组件只会渲染在可视窗口中可见的 item,超出视口之外的这些 item 会被占位的元素所代替,这样就可以限制我们渲染的 item 数量,使得列表的滚动更加顺滑。

体积优化

jj.045.jpeg

最后看一下体积相关的优化。Taro 的代码会使用 Webpack 进行打包,因此 Webpack 里面的优化工具我们都是可以使用的。例如代码压缩、tree-shaking、side-effects 等。如果用户还是觉得自己的包比较大,那么就可以使用 webpack-bundle-analyzer 插件去分析自己的包依赖,做出更加细致的优化。

jj.046.jpeg

接下来说一下分包,如果用户的主包比较大,可以拆分出一些分包,分包功能在 Taro Next 里面是完全支持的。但这里有一个问题,分包的 node_modules 依赖项默认会被打包到主包的 vendor.js 里面,这时候我们可以使用 splitChunks 做出更细致的优化,把分包对应的 node_modules 依赖单独进行拆分,把拆分后的文件放到对应的分包里面,进一步减少主包的大小。

四、原理浅析

jj.047.jpeg

说完怎么样去使用 Taro Next 之后,相信大家已经对 Taro Next 产生了一定的兴趣,这里会对 Taro Next 的部分原理进行一个讲解,包括小程序及 H5 的实现原理。

1、设计思路

jj.048.jpeg

首先介绍一下我们在小程序端的设计思路。可以看到无论是 React 或者 Vue,他们都会使用到浏览器的 DOM 和 BOM API,然后再渲染到浏览器上。那么我们同样也可以在小程序里面实现一层 DOM 和 BOM,从而让这些框架运行到小程序上。

2、DOM渲染方案

jj.049.jpeg

但是这里有一个问题,即使我们去得到一棵 Taro DOM 树,那要怎么样去 setData 到视图层?因为小程序并没有提供动态创建节点的能力,我们需要考虑如何使用相对静态的 wxml 来渲染相对动态的 Taro DOM 树。我们使用了模板拼接的方式,根据运行时提供的 DOM 树数据结构,各 templates 递归地相互引用,最终可以渲染出对应的动态 DOM 树。

jj.050.jpeg

这里先看一个比较简单的 view 模板实例。上方是一个 view 组件模板,首先我们需要在 template 里面写一个 view,然后把它所有的属性全部列出来(把所有的属性都列出来是因为小程序里面不能去动态地添加属性)。接下来是遍历渲染所有子节点,这些子节点首先会去引用中间层模板,然后中间层模板会根据对应的 nodeName,再去找到对应的组件模板。通过这样的方式把模板一步一步拼接起来,就可以渲染出我们动态的 DOM 树。

3、适配 React

1593261032.jpeg

接下来看一下怎么样去适配 React。React 架构主要分为 React Core、Reconcliers、Renderers,但是 React DOM 渲染器的体积比较大,里面有很多兼容性代码,放到小程序里面的话就太大了。因此我们就想自己去实现一个渲染器,我们可以提供一个 hostConfig 以对接 Taro DOM 的各 API,从而去实现一个小程序的渲染器。

image.jpeg

接下来看一下 React 的整体的渲染流程。首先 React 会使用 taro-react 这个包里面提供的小程序的渲染器,然后再配合 taro-runtime 里面的 createReactPage 函数,去把页面渲染出对应的 Taro DOM 树,然后我们会对 Taro DOM 树做一个 Hydrate 操作,得到需要 setData 的数据,然后进行 setData,视图层会根据这些 data 数据对所有的模板进行拼接,从而渲染出对应的页面。

4、Vue 渲染流程

image.jpeg

接下来看一下 Vue 的渲染流程。因为 Vue 这边并没有很多冗余代码,因此我们可以直接使用,Vue 同样需要配合
taro-runtime 包里面的 createVuePage 方法,把页面渲染出一个 Taro DOM 树,然后进行 setData,在视图层对这些模板拼接,最终渲染出对应页面。

5、Taro Next 小程序端架构

image.jpeg

接下来看一下 Taro Next 小程序端的整体架构。首先是用户的 React 或 Vue 的代码会通过 CLI 进行 Webpack 打包,其次在运行时我们会提供 React 和 Vue 对应的适配器进行适配,然后调用我们提供的 DOM 和 BOM API,最后把整个程序渲染到所有的小程序端上面。

6、H5 端架构

image.jpeg

再来看一下 H5 端的架构,同样的也是需要把用户的 React 或者 Vue 代码通过 Webpack 进行打包。然后在运行时我们需要去做三件事情:第一件事情是我们需要去实现一个组件库,组件库需要同时给到 React 、Vue 甚至更加多的一些框架去使用,因此我们就使用了 Stencil 去实现了一个基于 WebComponents 且遵循微信小程序规范的组件库,第二、三件事是我们需要去实现一个小程序规范的 API 和路由机制,最终我们就可以把整个程序给运行在浏览器上面。

五、总结与展望

image.jpeg

最后说一下对于今天分享的一个总结与展望。

1、总结

image.jpeg

总结分为四点:

  • 首先介绍了我们重构 Taro 的背景,重构是为了解决架构问题,还有提供多框架的支持
  • 其次介绍了 Taro Next 的一些新特性,Taro Next 现在更强大、更迅速、更灵活了。
  • 然后介绍了怎么样使用 Taro Next 去进行开发实践。
  • 最后介绍了小程序端还有 H5 端的整体实现原理。

2、可定制化拓展

image.jpeg

下一步我们会添加可定制化拓展框架的能力,让 Taro 成为开放式的多端框架。背景是我们现在最近正在适配 Vue3,但我们发现要适配 Vue3 我们需要去改动很多处源码相关的地方,而且源码里充斥着各种对于框架的条件判断代码。因此我们就想把 Taro 设计得更加开放。目标是可以直接以 Taro 插件的形式去新增对于框架的支持,而并不需要去改动 Taro 的源码。

3、Taro3 新版本

image.png

我们 Taro3 的正式版将会在 7 月初进行发布,相信本文发出时正式版已经发布了。

4、联系方式

image.jpeg

欢迎大家浏览我们的 Github,可以给我们提 Issues 和 PR。如果有合作意愿或者简历投递的话,可以联系我们的邮箱。扫描上方二维码,即可加入到 Taro 官方交流群。今天我的推荐书是《黑客与画家》,讲述的是 Paul 对技术和创业的思考。今天的分享就到此结束,谢谢大家!

六、QA

Q:请教JJ:从 Taro 2.0.2 升级到 Taro Next 需要注意什么,升级是无缝的吗?

A:其实还是有一些语法上面需要去修改的,因为以前我们是不需要去引用 React 的,但现在去使用 React 的话,首先需要去引用 React 的包。如果使用 Vue 的话,也需要去引用 Vue 的包。之后我们会提供一个升级相关的插件,给用户现在的语法进行修改,然后进行无缝升级。现在如果想体验的话,也可以参考文档上面的升级指南,其实要改的语法也并不多。

Q:请教JJ:有没有基于 Taro React 模式的 SSR 最佳实践,现在 Taro Next 是已经发布正式版本还是预览版本?

A:SSR 的相关功能我们还没去做,之后我们可能会去做,正式版会在 7 月 1 日发布,现在是 RC 版本,距离正式版大概还有两个小版本。

Q:请教JJ:看下我的理解对不对?3.0 相比之前主要是增加了运行时的 ReactAdapter、VueAdapter,而不用编译时的模板 AST 编译转换?这样性运行时能会不会差一点?

A:是的,从宏观上可以这样说,从一个编译型的框架转变成一个运行时的框架,现在那些编译时的操作基本是没有了,性能上面的话肯定会比之前通过编译时去适配的话会更差一点。但我们考虑到一点,就是之后那些硬件设备,还有环境都会变得更好,所以我们觉得牺牲一点点性能,使用户获得更好的开发体验,这个是值得的。之后我们的方向也会往运行时的框架这一块去靠。


别忘了点下方送稻谷