Taro 和原生混合开发插件

曹麦 | 2020-10-16 | 浏览量:
小程序

介绍

本文将会基于我们团队开源的 plugin-taro-wx-mix Taro 插件进行讲解。我们的插件可以帮助有 Taro 原生混合开发需求的 Taro 用户在最少量的修改原生代码的情况下快速进行 Taro 原生混合开发。

开始

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 安装
yarn add plugin-taro-wx-mix -D
// or
npm install plugin-taro-wx-mix -D

// 修改项目 config/index.js 中的 plugins 配置为如下:
const config = {
...
plugins: [
...其余插件
['plugin-taro-wx-mix', {
// 需要配置原生代码包地址
dir: 'src/native'
}]
]
...
}

使用

引入上面依赖之后, 就可以直接在开发环境中使用了。

1
2
3
4
5
6
7
8
config = {
// 原生开发包的配置
wxAppJson: {
pages: [],
},
// Taro 开发包的配置
pages: [],
};

原理

尝试

  1. 通过 webpack 实现类似 qiankun 的小程序微前端方案
  • 在这个方案中,计划用 webpack 插件通过分包的方法,把不同的小程序包,在 app.js 逻辑抽离的情况下合并成同一个小程序。希望能够让不同版本的 taro 代码,甚至是不同框架的小程序代码,可以在同一个小程序中运行。

  • 可惜理想很丰满,现实很骨感。这个方案仅限于可以在 taro 1.x 版本中运行。从 taro 2.x 开始,taro 开始用 webpack 打包,会往 app.js 里面注入很多 Taro 相关的代码。对应的代码包,需要依赖 app.js 才能正常运行。更别说 Taro 3.x 开始,Taro 实现了一套运行时的方案,它是基于 React 和 React-Reconciler 实现的。必须往 app.js 里面注入相关 chunk 来实现 runtime。如果想自己实现一套这样的机制,难度不亚于自己实现一个 taro-runtime。开发成本以及维护成本过高。

  1. 通过 Taro convert 把旧代码转换成新的 Taro 代码
  • Taro 官方为了方便原生用户加入 Taro 的大家庭当中,可以通过 Taro-cli 提供 taro convert 方法,实现原生代码向 Taro 代码的转换。但是这个方案也有很多弊端。

  • 这个方法的原理是,通过 Taro with-weapp 这个装饰器把原生代码 App(options) / Page(options) / Component(options) 的 options 注入到 react 的 class component 内部。在这个过程中就会产生很多问题。class component 的 this 并不是指向原生的 App / Page / Component ,而是指向 class component 的实例。这样会导致原生很多 hack 的代码失效。

最后方案

Taro 官方提供了 Taro 和 native 的混写的方案。但是这个方案有很多弊端,经过这个方案打包后的原生代码,会出现很多路径不一致的问题。旧代码和新代码无法很好的解耦。所以我们选择不在 config.pages 里加入原生代码页面路径,避免原生页面被作为 Entry 被打包进了 webpack 流程里,最后通过 Taro 插件的形式,把原生页面路径注入,最后输出的 app.json 里。

混合开发需要考量的点

如果说在 pc 端微前端的方案是为了解耦不同历史依赖,那么在内存有限性能有限的小程序开发中,我们要处理的第一件事情就是要统一不同技术栈之间的依赖。在我们设计的混合开发方案当中,旧包的依赖是直接复制到新包的,这就会造成有些依赖在新旧包之间重复引入,在寸土寸金的小程序中这种内存的浪费是致命的。

如何解决?

App.js 是一个很好的资源共享池,所以我们决定以原生代码的 getApp 作为共享资源的入口,让原生代码的依赖,都集中到 getApp 处引入,让原生代码能直接引入新代码依赖。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 新包的 app.js 需要传入原生代码目录的名字。
require('./mixin')('sample');

// mixin.js
module.exports = nativeDirName => {
const oldApp = App;
let options;
App = option => {
options = option;
};
(function() {
require(`./${nativeDirName}/app.js`);
})();
App = oldApp;
const oldGetApp = getApp;
let res = Object.assign(options, oldGetApp(), {
...需要被新代码覆盖的资源
});
getApp = () => {
return res;
};
};

image

如何覆盖?

随后我们就遇到了下一个问题,我们需要确认哪些资源是可以共享的,哪些资源是要被覆盖的,哪些依赖已经被更新了,但用法不兼容的。

技术方式:

  1. 共享,一些状态管理,以及没有经历什么迭代的工具函数,我们共享同一份就可以了。
  2. 覆盖,一些旧包代码可能已经落后于新的依赖了,如我们加入很多新业务逻辑 Request 库。但新依赖依然兼容旧依赖,这部分我们可以直接覆盖旧的代码。覆盖的同时,要观察旧包代码,看看新依赖是否涵盖了旧包代码一些重复的业务逻辑以保证旧代码的洁净。
  3. 兼容,新旧依赖之间还可能存在作用相同,但 api 不同的情况。例如在旧包里,我们自己实现了一个 eventCenter,功能和 Taro 的 eventCenter 相同,但是 Taro 的更强大。为了最少量的修改旧包代码逻辑,在 Taro eventCenter 的基础上进行兼容封装,让 Taro eventCenter 直接能在旧包上使用。

更新迭代

新版本的更新迭代,旧包还是不可避免要进行修改。在这个问题上只能去权衡,哪些页面需要继续使用旧代码,哪些页面适合重写。

最后

我们的混合方案未必能满足大家混合开发的需求,不过希望能给大家一点启发。也欢迎大家来我们插件的 repo 点个 star。plugin-taro-wx-mix

评论,需翻墙