将TUIKit从uni-app迁移到taro

TUIKit是腾讯的一个基于 Chat SDK 的 UI 组件库,目前支持 H5、小程序、uni-app,但并没有对 Taro 的支持。

由于我的项目是基于 Taro 的,因此我先要把它 uni-app 的版本迁移到 Taro 上去。

准备工作

chat-uikit-uniapp克隆到本地,把里面原有的 .git 和 sample 删掉,自己初始化一个 git。

当然,在初始化 git 之前,可以把项目名先改成chat-uikit-taro,毕竟 Taro 才是主体目标嘛。

1
2
3
4
5
6
7
git clone https://github.com/TencentCloud/chat-uikit-uniapp.git
cd chat-uikit-uniapp
rimraf .git
rimraf sample
git init
git add .
git commit -m "init"

修改库本身

Taro

把所有uni.替换成Taro.,注意有些带有-uni.png的要手动排除掉。

由于 Taro 需要手动import,在所有vue文件的script部分统一补上。

1
2
3
4
<script setup lang="ts">
import Taro from "@tarojs/taro";
...
</script>

以及

1
2
3
4
<script lang="ts" setup>
import Taro from "@tarojs/taro";
...
</script>

生命周期

Taro 的生命周期钩子和 uni-app 也不一样,需要找到并替换掉,搜索@dcloudio/uni-app,便可找到所有钩子的引入位置,所有钩子替换完后再把@dcloudio/uni-app替换成@tarojs/taro。注意要把不相关的选项手动排除掉(如emit)。

  • onLoad —— useLoad
  • onUnload —— useUnload
  • onReady —— useReady
  • onHide —— useDidHide

事件

Taro 的事件处理和 uni-app 也不一样,做以下的替换:

  • $on —— eventCenter.on
  • $off —— eventCenter.off
  • $emit —— eventCenter.trigger

编译宏

Taro 的编译宏和 uni-app 并不相同。

先搜索#ifdef

adapter-vue.ts中,直接把vue指定为vue3版本。

1
2
3
4
5
let vueVersion = 3;
let framework = "vue3";
export * from "vue";
console.warn(`[adapter-vue]: vue version is ${vueVersion}`);
export { vueVersion, framework };

entry-chat-only.ts中,做出以下的修改:

1
2
3
4
5
6
7
8
9
10
...
import { TUIChatKit } from "../../index.ts";

export const initChat = (options: Record<string, string>) => {
...
if (process.env.TARO_ENV === "weapp") {
TUIChatKit.init();
}
...
};

再搜索#ifndef

server.ts中,将locale相关代码注释掉。

1
2
3
4
5
6
...
// import TUILocales from './locales';
...
// TUITranslateService.provideLanguages({ ...TUILocales });
// TUITranslateService.useI18n();
...

去除 ts 里对 vue 的引入

Taro 的 ts 文件里不能直接引入 vue 文件,不然会把编译好的字符串全部直出到页面上。

TUIKit/index.ts中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import { genTestUserSig } from "./debug";
import Server from "./server";
// import TUIComponents, {
// TUIChat,
// TUIConversation,
// TUIContact,
// TUISearch,
// TUIGroup,
// } from "./components";
// import TUIKit from "./index.vue";

const TUIChatKit = new Server();
TUIChatKit.init();

export {
// TUIKit,
TUIChatKit,
// TUIComponents,
// TUIChat,
// TUIConversation,
// TUIContact,
// TUISearch,
// TUIGroup,
genTestUserSig,
};

重命名重名组件

TUIKit有一个Icon.vue的组件,很容易跟项目所用的组件库里的组件重名,要给它换个名称。

在所有vue文件中搜索Icon.vue,替换成IconImage.vue,同时将import Icon from替换成import IconImage from,再把<Icon替换成<IconImage,把components/common目录下的Icon.vue重命名为IconImage.vue

调整样式

搜索:not(not)相关样式,把它们全部注释掉。

Taro 不支持局部样式,把所有的scoped去除。

TUIKit/assets/styles/common.scss中,把最顶上的样式重置删除(不是注释,直接删)。

1
2
3
4
5
6
7
// body, div, ul, ol, dt, dd, li, dl, h1, h2, h3, h4, p {
// margin:0;
// padding:0;
// font-style:normal;

// /* font:12px/22px"\5B8B\4F53",Arial,Helvetica,sans-serif; */
// }

解决样式覆盖冲突

上一步把scoped去除后,样式就变成了全局样式,可能会产生样式污染的问题,最常见的是.btn会污染项目里原本就有的.btn

.btn改个名吧,统一替换成.tui-btn,注意要只改样式和html,不要影响到其他逻辑里的btn

替换导航路径

下面会写到 Taro 的页面跟 uni-app 并不相同,因此导航的路径也会不同。

在所有文件中搜索/TUIKit/components/,按以下规则进行替换:

  • /TUIKit/components/TUIChat/video-play —— /TUIKit/pages/TUIChat-video-play/TUIChat-video-play
  • /TUIKit/components/TUIChat/index —— /TUIKit/pages/TUIChat/TUIChat
  • /TUIKit/components/TUIContact/index —— /TUIKit/pages/TUIContact/TUIContact
  • /TUIKit/components/TUIConversation/index —— /TUIKit/pages/TUIConversation/TUIConversation
  • /TUIKit/components/TUIGroup/index —— /TUIKit/pages/TUIGroup/TUIGroup
  • /TUIKit/components/TUISearch/index —— /TUIKit/pages/TUISearch/TUISearch

替换变量

将所有isUniFrameWork替换成isTaroFrameWork

TUIKit/utils/env.ts中,做以下的修改:

1
2
3
4
5
6
7
import Taro from "@tarojs/taro";

// declare const uni: any;

...

export const isTaroFrameWork = typeof Taro !== "undefined";

解决滚动失效问题

目前 Taro 貌似不支持通过设置scroll-viewscroll-top来滚动页面,在TUIKit/components/TUIChat/message-list/index.vue的末尾加上如下的代码:

1
2
3
4
5
6
7
8
9
watch(
() => scrollTop.value,
(val) => {
Taro.pageScrollTo({
scrollTop: val,
duration: 0,
});
}
);

给项目集成库

主要参考官方文档来一步步做:https://cloud.tencent.com/document/product/269/64506

项目的package.json里添加以下依赖:

1
2
3
4
5
6
7
8
9
10
11
{
"dependencies": {
"@tencentcloud/chat-uikit-engine": "latest",
"@tencentcloud/tui-core": "latest",
"@tencentcloud/universal-api": "latest",
"@tencentcloud/call-uikit-wechat": "latest",
"@tencentcloud/call-uikit-vue2.6": "latest",
"@tencentcloud/call-uikit-vue": "latest",
"@tencentcloud/tui-customer-service-plugin": "latest"
}
}

TUIKit目录整个复制到项目的src目录下。

node_modules里的@tencentcloud/tui-customer-service-plugintui-customer-service-plugin目录复制到TUIKit目录下。

tui-customer-service-plugin/componentsmessage-product-card中,做出以下修改:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<script lang="ts">
import Taro from "@tarojs/taro";
...

// eslint-disable-next-line
// declare var uni: any;

...

export default {
...
setup(props: Props) {
const jumpProductCard = () => {
if (window) {
window.open(props.payload.content.url, "_blank");
} else {
Taro.navigateTo({
url: `/TUIKit/pages/TUIChat-web-view/TUIChat-web-view?url=${props.payload.content.url}`,
});
}
};
...
},
};
</script>

app.ts中,进行im的登录操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
const tuiConfig = {
SDKAppID: 114514,
userID: "114514",
userSig: "114514",
};

const App = createApp({
onShow(options) {},
// 入口组件不需要实现 render 方法,即使实现了也会被 taro 所覆盖
onLaunch() {
TUILogin.login({
...tuiConfig,
useUploadPlugin: true, // If you need to send rich media messages, please set to true.
framework: `vue3`, // framework used vue2 / vue3
}).catch(() => {});
},
});

由于 Taro 的单个页面下只支持一个vue文件,我们不能直接用TUIKit/components里的组件作为页面。

新建一个TUIKit/pages目录,再将所有组件对应的目录、以及它下面的vue文件和ts配置也创建下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
mkdir src/TUIKit/pages
mkdir src/TUIKit/pages/TUIChat
mkdir src/TUIKit/pages/TUIContact
mkdir src/TUIKit/pages/TUIConversation
mkdir src/TUIKit/pages/TUIGroup
mkdir src/TUIKit/pages/TUISearch
mkdir src/TUIKit/pages/TUIChat-video-play
mkdir src/TUIKit/pages/TUIChat-web-view
touch src/TUIKit/pages/TUIChat/TUIChat.vue
touch src/TUIKit/pages/TUIChat/TUIChat.config.ts
touch src/TUIKit/pages/TUIContact/TUIContact.vue
touch src/TUIKit/pages/TUIContact/TUIContact.config.ts
touch src/TUIKit/pages/TUIConversation/TUIConversation.vue
touch src/TUIKit/pages/TUIConversation/TUIConversation.config.ts
touch src/TUIKit/pages/TUIGroup/TUIGroup.vue
touch src/TUIKit/pages/TUIGroup/TUIGroup.config.ts
touch src/TUIKit/pages/TUISearch/TUISearch.vue
touch src/TUIKit/pages/TUISearch/TUISearch.config.ts
touch src/TUIKit/pages/TUIChat-video-play/TUIChat-video-play.vue
touch src/TUIKit/pages/TUIChat-video-play/TUIChat-video-play.config.ts
touch src/TUIKit/pages/TUIChat-web-view/TUIChat-web-view.vue
touch src/TUIKit/pages/TUIChat-web-view/TUIChat-web-view.config.ts

所有的config.ts文件里添加统一的配置。

1
2
3
export default definePageConfig({
navigationBarTitleText: "消息",
});

每个vue文件里引入components目录下对应的组件(以 TUIChat.vue 为例)

1
2
3
4
5
6
7
8
9
<script lang="ts" setup>
import TUIChat from "@/TUIKit/components/TUIChat/index.vue";

...
</script>

<template>
<TUIChat></TUIChat>
</template>

最后在app.config.ts里添加对应的分包(要看项目,不一定加所有的页面)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
export default defineAppConfig({
subPackages: [
{
root: "TUIKit",
pages: [
"pages/TUIConversation/TUIConversation",
"pages/TUIChat/TUIChat",
"pages/TUIChat-video-play/TUIChat-video-play",
"pages/TUIChat-web-view/TUIChat-web-view",
"pages/TUIContact/TUIContact",
"pages/TUIGroup/TUIGroup",
"pages/TUISearch/TUISearch",
],
},
],
preloadRule: {
"pages/index/index": {
network: "all",
packages: ["TUIKit"],
},
},
});

优化主包体积过大问题

可以用webpack-bundle-analyzer插件来直观地查看依赖大小,以进一步地优化体积。

1
npm i webpack-bundle-analyzer -D

config/prod.ts中这么配置:

1
2
3
4
5
6
7
8
9
10
11
module.exports = {
...
mini: {
webpackChain(chain, webpack) {
chain
.plugin("analyzer")
.use(require("webpack-bundle-analyzer").BundleAnalyzerPlugin, []);
},
},
...
};

这样就能很直观地看到TUIKit相关的依赖足足占了 1 个多M,WTF!

如果你小程序主包的页面很多且体积很大,就把除了tab页以外的页面全放到分包里吧。

项目里有echarts这个大头的话,把它也放到分包里吧。

其他优化

可以根据项目需求来进一步地改进和优化TUIKit/components里的组件。

最后

费了一番功夫,总算迁移完成。

只能说 TX 你做得好啊,你做的好。

Author: alphardex
Link: https://alphardex.github.io/mygo/posts/36637/
Copyright Notice: All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.