解决微信小程序的一系列痛点

小程序开发没有融入目前主流的工程化开发思想,很多业界开发模式与工具没有在小程序开发中得到相应体现。

开发方式上:


  • 没有自定义文件预处理,无法直接使用 Sass、Less 以及较新的 ES.Next 语法;
  • 字符串模板太过孱弱,小程序的字符串模板仿的是 Vue,但是没有提供 Vue 那么多的语法糖,当实现一些比较复杂的处理时,写起来就非常麻烦,虽然提供了 ​​wxs​​ 作为补充,但是使用体验还是非常糟糕;
  • 缺乏测试套件,无法编写测试代码来保证项目质量,也就不能进行持续集成,自动化打包。

代码规范上:

小程序的规范有很多不统一的地方,例如内置组件的属性名,有时候是全小写,有时候是 ​​CamelCase​​ (驼峰)格式,有时候又是中划线分割的形式,这样就导致编码的时候得不时查阅文档才能确定写法。

taro支持多端

Write once, run anywhere

一次编译,到处运行。

多端转换原理

Taro 是基于 React 语法来进行开发的,把一份类React源码,通过“编译”转换成兼容目标端的形式。

也就是对代码文件进行一系列转换操作,最终获得可以在小程序运行的代码。而 React 最开始就是为了解决 Web 开发而生的,所以对代码稍加改动,也可以直接生成在 Web 端运行的代码,而同属 React 语法体系下的 React Native,也能够很便捷地提供支持。同理其他平台,如快应用、百度小程序等,将源码进行编译转换操作,也能获得该平台下的对应语法代码。


Taro小程序分享——taro搭载鸿蒙_小程序

抹平多端差异

通过上面可以将 Taro 源码编译成不同端上可以运行的代码了,但是不同的平台都有自己的特性,每一个平台都不尽相同,这些差异主要体现在不同的组件标准不同的 API 标准以及不同的运行机制上。

以小程序和web进行对比:


Taro小程序分享——taro搭载鸿蒙_小程序_02

小程序和 Web 端上组件标准API 标准有很大差异,这些差异仅仅通过代码编译手段是无法抹平的。

例如不能直接在编译时将小程序的 ​​<view />​​​ 直接编译成 ​​<div />​​​,虽然看上去有些类似,但是他们的组件属性有很大不同的,仅仅依靠代码编译,无法做到一致,同理,众多 ​​API​​ 也面临一样的情况。

**Taro 的解决方案:**采用了定制一套运行时标准来抹平不同平台之间的差异。

这一套标准主要以三个部分组成,包括标准运行时框架标准基础组件库标准端能力 API,其中运行时框架和 API 对应 @taro/taro,组件库对应 @tarojs/components,通过在不同端实现这些标准,从而达到去差异化的目的。


Taro小程序分享——taro搭载鸿蒙_小程序_03

起初,Taro团队是想重新制定一套标准规范,发现在所有端都得实现这个标准成本过于高昂。

后来就以微信小程序的组件库和 API 来作为 Taro 的运行时标准,另外百度小程序以及支付宝小程序都是遵循的微信小程序的标准,这样一来,Taro 也实现了在这两个平台上的转换。


Taro小程序分享——taro搭载鸿蒙_小程序_04

taro搭配鸿蒙

最近看到他们的动态是在研究让Taro搭配鸿蒙系统,我这边也运行了一下,不过关键插件没发布并不能运行起来,哭~

整体原理:

​taro cli​​​ ->​​根据不同的type解析不同的插件​​​ -> ​​利用webpack编译各端的代码​​​ -> ​​利用各端自己的插件配置磨平各端的代码​​​ -> ​​生成harmony代码​


Taro小程序分享——taro搭载鸿蒙_taro_05

Taro小程序分享——taro搭载鸿蒙_taro_06

其中@tarojs/mini-runner的原理是这样的:

​taro代码​​​ ->​​AST解析​​​ -> ​​解析Render代码​​​ -> ​​利用脚本转换​​​ -> ​​生成代码​


Taro小程序分享——taro搭载鸿蒙_自定义_07

编译鸿蒙时,CLI 会调用 ​​@tarojs/mini-runner​​​ 包,这个包主要是对代码进行编译。​​mini-runner​​ 主要做了这些事情:


  1. 负责根据开发者的编译配置调整 Webpack 配置
  2. 注入自定义的 PostCSS 插件。(如 postcss-pxtransform)
  3. 注入自定义的 Webpack 插件。
  4. 注入自定义的 Webpack Loaders。(Loaders 位于 ​​@tarojs/taro-loader​​ 包中)
  5. 调用 Webpack 开启编译。
  6. 修改 Webpack 的编译产物,调整最终的编译结果。

最后再根据​​@tarojs/plugin-platform-harmony​​进行调整,对组件、API、路由进行磨平。

mini-runner的文件目录:

├── src
| ├── config 项目配置文件
| ├── dependencies 项目依赖文件
| ├── loader 自定义loader
| | ├── quickappStyleLoader 快应用
| | └── miniTemplateLoader 小程序模版解析器
| ├── plugins 自定义插件
| ├── prerender 自定义的代码结构
| ├── template 自定义模版
| | ├── comp 组件模版
| | ├── component 自定义组件模版
| | └── custom-wrapper 自定义小程序插件
| ├── utils 公共配置
| ├── webpack webpack配置
| | ├── base.conf 基础配置
| | ├── build.conf 打包配置
| | ├── chain 插件和解析器
| | └── postcss.conf 对css转换的配置
| └── index webpack的启动
└── index 入口文件


为了让 React、Vue 等框架直接运行在鸿蒙端,解决方案是在小程序的逻辑层模拟浏览器环境,包括实现 DOM、BOM API 等。

​@tarojs/runtime​​ 是 Taro 的运行时适配器核心,它实现了精简的 DOM、BOM API、事件系统、Web 框架和小程序框架的桥接层等。此包主要是对小程序和h5运行进行适配,因为 ReactDOM 体积较大,且包含很多兼容性代码。因此 Taro 借助 react-reconciler 实现了一个自定义渲染器用于代替 ReactDOM。渲染器位于 @tarojs/react 包中。

Web 框架就可以使用 Taro 模拟的 API 渲染出一颗 Taro DOM 树,但是这一切都运行在小程序的逻辑层。而小程序的 xml 模板需要提前写死,Taro 选择了利用小程序 ​​<template>​​​ 可以引用其它 ​​<template>​​​ 的特性,把 Taro DOM 树的每个 DOM 节点对应地渲染为一个个 ​​<template>​​​。这时只需要把 Taro DOM 树的序列化数据进行 setData,就能触发 ​​<template>​​ 的相互引用,从而渲染出最终的 UI。

而鸿蒙这边没有​​<template>​​​的概念,我们用自定义组件​​element​​代替。

运行时我们会将上述说的Taro DOM 树以一个整体变量 ​​root​​​ 去运行代码。再根据​​root.cn​​下的数组去遍历和递归我们自定义组件。

页面内容:

<element name="container" src="../../container/index.hml"></element>
<element name="navbar" src="../../container/components-harmony/navbar/index.hml"></element>
<element name="tabbar" src="../../container/components-harmony/tabbar/index.hml"></element>

<div class="container">
<navbar title="{{taroNavBar.title}}" background="{{taroNavBar.background}}" text-style="{{taroNavBar.textStyle}}" st="{{taroNavBar.style}}"></navbar>
<div class="body" style="padding-top: 44px;padding-bottom: {{isShowTaroTabBar ? '56px' : '0'}}">
<refresh if="{{enablePullDownRefresh}}" type="pulldown" refreshing="{{isRefreshing}}" onrefresh="onPullDownRefresh">
<container root="{{root}}"></container>
</refresh>
<container else root="{{root}}"></container>
</div>
<tabbar if="{{isShowTaroTabBar}}" data="{{taroTabBar}}" selected="{{selected}}"></tabbar>
</div>


root数据:

{
"root": {
"cn": [
{
"cl": "layout bg-gray-lightbg",
"cn": [
{
"cn": [
{
"nn": "#text",
"v": "订单"
}
],
"nn": "view",
"uid": "_n_885"
},
{
"cl": "layout-area-slot w-full h-210 area-slot",
"cn": [],
"nn": "pure-view",
"uid": "_n_888"
}
],
"nn": "pure-view",
"uid": "_n_886"
}
],
"uid": "pages/tab-bar/order/index?$taroTimestamp=1636613920366"
},
}


container的内容:

<element name="container" src="./index.hml"></element>
<element name="taro-textarea" src="./components-harmony/textarea/index.hml"></element>


<block for="{{i in root.cn}}">

<block if="{{i.nn == 'view'}}">
<div hover-class="{{i.hoverClass===undefined?'none':i.hoverClass}}" hover-stop-propagation="{{i.hoverStopPropagation===undefined?false:i.hoverStopPropagation}}" hover-start-time="{{i.hoverStartTime===undefined?50:i.hoverStartTime}}" hover-stay-time="{{i.hoverStayTime===undefined?400:i.hoverStayTime}}" @touchstart="{{eh}}" @touchmove="{{eh}}" @touchend="{{eh}}" @touchcancel="{{eh}}" @longtap="{{eh}}" animation="{{i.animation}}" @animationstart="{{eh}}" @animationiteration="{{eh}}" @animationend="{{eh}}" @transitionend="{{eh}}" style="{{i.st}}" class="{{i.cl}}" @click="{{eh}}" id="{{i.uid}}">
<container root="{{i}}"></container>
</div>
</block>

<block if="{{i.nn == 'textarea'}}">
<taro-textarea value="{{i.value}}" placeholder="{{i.placeholder}}" placeholder-style="{{i.placeholderStyle}}" placeholder-class="{{i.placeholderClass===undefined?'textarea-placeholder':i.placeholderClass}}" disabled="{{i.disabled}}" maxlength="{{i.maxlength===undefined?140:i.maxlength}}" auto-focus="{{i.autoFocus===undefined?false:i.autoFocus}}" focus="{{i.focus===undefined?false:i.focus}}" auto-height="{{i.autoHeight===undefined?false:i.autoHeight}}" fixed="{{i.fixed===undefined?false:i.fixed}}" cursor-spacing="{{i.cursorSpacing===undefined?0:i.cursorSpacing}}" cursor="{{i.cursor===undefined?-1:i.cursor}}" selection-start="{{i.selectionStart===undefined?-1:i.selectionStart}}" selection-end="{{i.selectionEnd===undefined?-1:i.selectionEnd}}" @focus="{{eh}}" @blur="{{eh}}" @linechange="{{eh}}" @input="{{eh}}" @confirm="{{eh}}" name="{{i.name}}" headericon="{{i.headericon}}" showcounter="{{i.showcounter===undefined?false:i.showcounter}}" menuoptions="{{i.menuoptions===undefined?[]:i.menuoptions}}" softkeyboardenabled="{{i.softkeyboardenabled===undefined?true:i.softkeyboardenabled}}" @translate="{{eh}}" @share="{{eh}}" @search="{{eh}}" @optionselect="{{eh}}" @selectchange="{{eh}}" style="{{i.st}}" class="{{i.cl}}" @click="{{eh}}" id="{{i.uid}}">

</taro-textarea>
</block>

...

</block>


开发运行步骤:

  1. 下载taro的源码编译、对鸿蒙的适配进行link。

  1. 另外再创建一个taro项目,编译配置

Taro小程序分享——taro搭载鸿蒙_taro_08

  1. 配置一下鸿蒙

​taro build --type harmony --watch​

使用鸿蒙开发者工具的 previewer、真机、远程真机进行预览


Taro小程序分享——taro搭载鸿蒙_自定义_09

打开鸿蒙编辑器内部是这样的:

项目的配置主要在MyApplication/entry/src/main/config.json文件夹里面,有三个部分组成:

属性名称

含义

数据类型

是否可缺省

​app​

表示应用的全局配置信息。同一个应用的不同HAP包的app配置必须保持一致。

对象

​deviceConfig​

表示应用在具体设备上的配置信息。

对象

​module​

表示HAP包的配置信息。该标签下的配置只对当前HAP包生效。

对象

Taro小程序分享——taro搭载鸿蒙_自定义_10

由于关键配置会在本月底开源,目前没办法完整把项目跑起来,所以介绍就到这里啦,有后续感兴趣的可以一起看看呀~​​在这里插入代码片​