解决微信小程序的一系列痛点
小程序开发没有融入目前主流的工程化开发思想,很多业界开发模式与工具没有在小程序开发中得到相应体现。
开发方式上:
- 没有自定义文件预处理,无法直接使用 Sass、Less 以及较新的 ES.Next 语法;
- 字符串模板太过孱弱,小程序的字符串模板仿的是 Vue,但是没有提供 Vue 那么多的语法糖,当实现一些比较复杂的处理时,写起来就非常麻烦,虽然提供了
wxs
作为补充,但是使用体验还是非常糟糕; - 缺乏测试套件,无法编写测试代码来保证项目质量,也就不能进行持续集成,自动化打包。
代码规范上:
小程序的规范有很多不统一的地方,例如内置组件的属性名,有时候是全小写,有时候是 CamelCase
(驼峰)格式,有时候又是中划线分割的形式,这样就导致编码的时候得不时查阅文档才能确定写法。
taro支持多端
Write once, run anywhere
一次编译,到处运行。
多端转换原理
Taro 是基于 React 语法来进行开发的,把一份类React源码,通过“编译”转换成兼容目标端的形式。
也就是对代码文件进行一系列转换操作,最终获得可以在小程序运行的代码。而 React 最开始就是为了解决 Web 开发而生的,所以对代码稍加改动,也可以直接生成在 Web 端运行的代码,而同属 React 语法体系下的 React Native,也能够很便捷地提供支持。同理其他平台,如快应用、百度小程序等,将源码进行编译转换操作,也能获得该平台下的对应语法代码。
抹平多端差异
通过上面可以将 Taro 源码编译成不同端上可以运行的代码了,但是不同的平台都有自己的特性,每一个平台都不尽相同,这些差异主要体现在不同的组件标准与不同的 API 标准以及不同的运行机制上。
以小程序和web进行对比:
小程序和 Web 端上组件标准与 API 标准有很大差异,这些差异仅仅通过代码编译手段是无法抹平的。
例如不能直接在编译时将小程序的 <view />
直接编译成 <div />
,虽然看上去有些类似,但是他们的组件属性有很大不同的,仅仅依靠代码编译,无法做到一致,同理,众多 API
也面临一样的情况。
**Taro 的解决方案:**采用了定制一套运行时标准来抹平不同平台之间的差异。
这一套标准主要以三个部分组成,包括标准运行时框架、标准基础组件库、标准端能力 API,其中运行时框架和 API 对应 @taro/taro,组件库对应 @tarojs/components,通过在不同端实现这些标准,从而达到去差异化的目的。
起初,Taro团队是想重新制定一套标准规范,发现在所有端都得实现这个标准成本过于高昂。
后来就以微信小程序的组件库和 API 来作为 Taro 的运行时标准,另外百度小程序以及支付宝小程序都是遵循的微信小程序的标准,这样一来,Taro 也实现了在这两个平台上的转换。
taro搭配鸿蒙
最近看到他们的动态是在研究让Taro搭配鸿蒙系统,我这边也运行了一下,不过关键插件没发布并不能运行起来,哭~
整体原理:
taro cli
->根据不同的type解析不同的插件
-> 利用webpack编译各端的代码
-> 利用各端自己的插件配置磨平各端的代码
-> 生成harmony代码
其中@tarojs/mini-runner的原理是这样的:
taro代码
->AST解析
-> 解析Render代码
-> 利用脚本转换
-> 生成代码
编译鸿蒙时,CLI 会调用 @tarojs/mini-runner
包,这个包主要是对代码进行编译。mini-runner
主要做了这些事情:
- 负责根据开发者的编译配置调整 Webpack 配置
- 注入自定义的 PostCSS 插件。(如 postcss-pxtransform)
- 注入自定义的 Webpack 插件。
- 注入自定义的 Webpack Loaders。(Loaders 位于
@tarojs/taro-loader
包中) - 调用 Webpack 开启编译。
- 修改 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>
开发运行步骤:
- 下载taro的源码编译、对鸿蒙的适配进行link。
- 另外再创建一个taro项目,编译配置
- 配置一下鸿蒙
taro build --type harmony --watch
使用鸿蒙开发者工具的 previewer、真机、远程真机进行预览
打开鸿蒙编辑器内部是这样的:
项目的配置主要在MyApplication/entry/src/main/config.json文件夹里面,有三个部分组成:
属性名称 | 含义 | 数据类型 | 是否可缺省 |
app | 表示应用的全局配置信息。同一个应用的不同HAP包的app配置必须保持一致。 | 对象 | 否 |
表示应用在具体设备上的配置信息。 | 对象 | 否 | |
module | 表示HAP包的配置信息。该标签下的配置只对当前HAP包生效。 | 对象 | 否 |
由于关键配置会在本月底开源,目前没办法完整把项目跑起来,所以介绍就到这里啦,有后续感兴趣的可以一起看看呀~在这里插入代码片