uniapp 踩坑不完全记录

已经很久没有开发微信小程序了,这次因为个人需要,于是再次开发微信小程序,过程很坎坷。

以前都是原生微信小程序开发,这次想把安卓端一起做了,所以选择了 uniapp。其实如果不需要微信小程序,我更倾向于 capacitor、flutter 之类的解决方案,因为小程序的坑真的不想踩。

项目初始化

uniapp 开发使用的是 hbuilder x 这个编辑器,官网也介绍了如何选择项目模板开始新项目,但是,那个新项目连 package.json 文件都没有,目录结构也不习惯。官网也介绍了命令行创建项目,但是那个命令行创建的项目是 vue2 的……于是,我从网上找了个开发模板,直接 git clone 下来开发。模板:vue-uniapp-template。

然后,开发建议不用 hbuilder x 这个编辑器,还是用 vscode 之类的就好,官方编辑器只用来编译就行。

项目基础信息配置

hbuilder 的项目需要注册账号然后到 dcloud 后台配置项目,然后将 AppID 填入 manifest.json 里。

不确定这一步是否必须。

微信小程序配置

微信小程序的 appid 也是在 manifest.json 里面。

为了能够正常预览,还需要将微信开发者工具里面的服务端口打开。设置->安全->服务端口。

然后到 hbuilder x 的工具->设置->运行配置里面把微信开发者工具的路径填上。

安卓配置

安卓模拟器我是用的网易mumu。mumu 的网站有介绍怎么开启调试。这里简要写一下。

首先,开启网络桥接。

然后,将 mumu 的 adb 路径配置到 hbuilder 的运行配置。

我还将 mumu 下的 shell 文件夹配置到了系统环境变量,不确定是否必须。

接着,在 hbuilderx 的运行->运行到手机或模拟器子菜单里安装插件(没有安装插件会有此选项),不安装插件是无法启动模拟器的。

微信小程序运行 wasm

微信小程序支持 wasm,但是其运行环境是魔改过的,不支持远程下载,但我的 wasm 体积较大,远比小程序允许的 2M 分包尺寸大,并且不确定胶水 js 能否正常运行,所以我只能放弃,舍弃 wasm 相关的功能。

微信小程序解压缩支持

微信小程序官方提供了 unzip api,但有中文乱码问题,并且不支持压缩。但在小程序上我也不需要压缩,只要解压就可以。为了避免踩坑,我决定使用 jszip。其实采用 jszip 已经是后退一步的选项了,因为我的资源不仅是 zip 格式的,还有 tar.gz 格式,甚至可能还有 7z。我网站上用的是功能更强的 libarchive,但 libarchive 涉及 wasm 和封装的 js 包,所以……我只能放弃多种压缩格式,把资源重新制作成 zip 格式……

File 对象不存在

微信小程序不支持 File,也不支持 Blob。所以想从接口获取 Blob,或者判断 File 类型的操作都行不通,于是只能将请求压缩文件的 content-type 设置为:arrayBuffer,这样 jszip 就可以正常执行了,否则会报数据缺失。

uniapp/微信小程序使用 css 变量

这个……也不是很好的支持,所以我把相关配置写到了一个 config 文件:

import packageJson from "./manifest.json";

export const app = {
  name: packageJson.name,
  version: packageJson.versionName,
  description: packageJson.description,
  style: {
    primaryColor: "#1976d2",
  },
};

然后创建一个 store:

import { defineStore } from "pinia";
import { getAppCache } from "@/utils/cache";
import { app } from "@/config";

export const useAppStore = defineStore("dict", () => {
  const store = ref<typeof app>(Object.assign({ ...app }, getAppCache()));

  return {
    store,
  };
});

页面中这么使用:

<view
  class="header h-600rpx flex-center flex-col position-relative"
  :style="{ backgroundColor: store.style.primaryColor }"
>

如果需要使用 css var,可以将变量拼成 `–primary-color: xxx` 这种设置到页面顶层 view 上。然后该页面其他地方就可以使用 var(–primary-color) 了。大概是这样,我没试过。

chooseImage 对图片格式的限制

该方法并没有类似 accept 这样的参数,所以只能拿到文件路径后自行筛选。

const res = await uni.chooseImage({
  count: 1,
  sizeType: ["compressed"],
  sourceType: ["album", "camera"],
});
const ext = res.tempFilePaths[0].split(".").pop()?.toLowerCase();
if (!["jpg", "jpeg", "png", "gif", "tiff"].includes(ext ?? "")) {
  return uni.showToast({ title: "不支持的格式", icon: "none" });
}

没错,不支持 webp。

canvas 画布绘制尺寸支持

我没具体研究微信小程序自身的 canvas 是否支持 css 尺寸和画布尺寸不一致,但 uniapp 是不支持的。反正我没研究出来。

uniapp 提供了 uni.createCanvasContext(canvas-id) 这个 api,但这个 api 返回的绘图上下文不含 canvas 引用,无法通过 ctx.canvas 获取到元素。于是,canvas 的宽高只能在样式设定,并且在分别设置 width、height 属性和 css 的宽高属性时……好像出了问题,反正我没走通。总之结论就是 canvas 最好只设置 css 尺寸。

但是,我们的图片不太可能只有可视区大小,高清图该如何处理呢?

于是我创建了两个 canvas,一个显示,一个设置实际大小,然后定位到屏幕外面。为什么用定位,而不用隐藏呢?因为隐藏有 bug。隐藏的 canvas 宽高会变成 0,调用 uni.canvasPutImageData 会报错。是的,putImageData 是单独的 api,ctx 上没有。getImageData 也是单独的 api:uni.canvasGetImageData。

uni.canvasGetImageData 获取不到值

一开始我的代码是在 ctx.draw() 下面一行调用 uni.canvasGetImageData,但这时 canvas 还没绘制完成,所以获取不到图片数据。改成回调就好了。

ctx.draw(false, async () => {
  imgData = (await uni.canvasGetImageData({
    canvasId: "imageCanvas2",
    x: 0,
    y: 0,
    width: imgWidth.value,
    height: imgHeight.value,
  })) as unknown as ImageData;
});

ImageData 不存在

ImageData 这个对象在小程序环境并不存在……等等,这里有点难解释。微信文档里说支持,但我代码里 new ImageData 报错了。所以我只能自行创建同样结构的对象实现从 canvas 获取到的 ImageData 的拷贝操作。

将一个 canvas 绘制到另一个 canvas

uniapp 不能直接将一个 canvas 绘制到另一个 canvas。解决方案是:保存一个 canvas 图片到文件系统,然后将图片绘制到另一个 canvas。

很……蛋疼,所以我没这么做。我是写了一个对 ImageData 缩放的方法,然后将处理后的 ImageData 绘制到另一个 canvas 上。

微信小程序存储限制

微信小程序的存储限制为 10M,这对于存储用户信息或者少量跨页数据很有用,但如果想缓存大文件就没戏了,根本不够用。web 平台的 indexeddb 就很好,配合 localforage 这样的库来缓存文件,避免重复下载很方便。于是,为了兼容微信小程序的体验,我只能放弃上传体积超过 1M 的资源。同时,微信小程序平台也放弃了用户自行缓存资源的操作。砍,什么功能都能砍。


目前就记录这些吧,其他的也想不起来了。总之,很蛋疼。过了这些年,微信小程序的开发体验还是非常糟糕,完全不像一个大厂应有的水平。那个微信开发者工具越来越抽风了,审查元素基本不可用,文件缓存问题也没解决。

然后 uniapp……又有一堆问题。

太糟糕了,非常糟糕。

微信小程序只适合开发表单、文本展示这一类的,其他操作要么放弃开发,要么上传到服务器处理。

祝 PWA 早日一统江湖。