uniapp下vite+vue3开发微信小程序使用echarts遇到的问题

2,129 阅读9分钟

WechatIMG515.jpeg

1. 前置知识-当前JS主要的两种模块规范

1.1 CommonJS(CJS)

  1. CommonJS(CJS)是早期Nodejs官方推荐的规范,代码中常见的关键字是requiremodule.exports

  2. 不过现在Nodejs已原生支持esm语法,例如:import { open } from 'node:fs/promises';

  3. 额外了解下,在Nodejs中,想要开启ESM语法主要有下面两种办法:

    1. 在根目录下的package.json文件中新增{"type": "module"}的配置项(如果设置为{"type": "commonjs"},则会按照CJS规范进行解析)
    1. js文件后缀名改为.mjs(如果改为.cjs,则会按照CJS规范进行解析)

1.2 ES Module(ESM)

  1. ES Module(ESM)是ES6标准中引入的模块规范,代码中常见的关键字是importexportexport default

  2. vue-clicreate-react-app等脚手架工具,webpack默认情况下会把通过babel把CJS规范的代码转换为ESM规范的代码。所以开发者可以随心所欲的在代码中使用require和import,不报错。

2. vite+vue3下创建项目及导入echarts-for-weixin

  1. 创建项目

文档:zh.uniapp.dcloud.io/quickstart-…

npx degit dcloudio/uni-preset-vue#vite my-vue3-project
  1. 导入echarts-for-weixin

文档:github.com/ecomfe/echa…

  1. 此时项目目录结构
├── README.md
├── echarts.md
├── index.html
├── jsconfig.json
├── package.json
├── patches
|  └── @climblee+uv-ui+1.1.20.patch
├── scripts
|  ├── copy.sh
|  ├── echarts_build_util.js
|  ├── miniprogram.js
|  └── tailwindcss.sh
├── shims-uni.d.ts
├── src
|  ├── App.vue
|  ├── api
|  ├── components
|  ├── hooks
|  |  ├── useLineCharts.js
|  |  ├── useMapGetter.js
|  |  └── useMapState.js
|  ├── lib
|  ├── main.js
|  ├── manifest.json
|  ├── mixins
|  ├── pages
|  ├── pages.json
|  ├── shime-uni.d.ts
|  ├── static
|  ├── store
|  ├── uni.scss
|  ├── utils
|  └── wxcomponents
|     ├── echarts-for-weixin-2.0.0
|     |  ├── README.md
|     |  └── ec-canvas
|     |     ├── ec-canvas.js
|     |     ├── ec-canvas.json
|     |     ├── ec-canvas.wxml
|     |     ├── ec-canvas.wxss
|     |     ├── echarts.esm.js
|     |     └── wx-canvas.js
|     └── wxml2canvas-2d
├── vite.config.cjs.js
├── vite.config.js
├── windi.config.js
└── yarn.lock

3. 当前遇到的问题

  1. uniapp文档中明确说明uniapp + vite + vue3下,只能使用ESM模块规范,不能使用CJS模块
  • 模块导入
// 之前 - Vue 2, 使用 commonJS
var utils = require("../../../common/util.js");

// 之后 - Vue 3, 只支持 ES6 模块
import utils from "../../../common/util.js";
  • 模块导出
// 之前 - Vue 2, 依赖如使用 commonJS 方式导出
module.exports.X = X;

// 之后 - Vue 3, 只支持 ES6 模块
export default { X };
  1. echarts-for-weixin
  1. 最终打包,微信小程序中最终使用的是CJS模块,本地开发又是使用ESM模块。等于是要将ESM转为CJS,或者本地开发使用ESM模块,最终打包后使用CJS模块,真是头疼。

  2. 微信开发者工具es6转es5

原本想着微信开发者工具还自带有es6转es5,可以化险为夷,但是微信开发者工具转换文件大小上限为500kb.

developers.weixin.qq.com/community/d…

  • 微信开发者工具自带的es6转es5,最大文件大小为500kb,但是下载的echarts文件超过了500kb,开发者工具默认为你已经处理了这个文件,不会再进行处理。对于最终打包,上传发版会有问题

  • 上面自定义构建选择,勾选折线图+柱状图,轻松超过500kb,导致开发者工具处理不了echarts.js文件

  1. vite最终打包构建,没有将这个echarts.js这个文件处理成小程序需要的CJS规范文件,开发者工具又因为这个文件超过了500kb就不处理

4. 解决思路

  1. 方法一:本地开发使用ESM模块的echarts文件,最终打包后使用CJS模块的echarts文件

  2. 方法二:本地开发使用ESM模块的echarts文件,最终打包通过vite把这个ESM模块的echarts文件转成CJS模块的echarts文件

4.1 方法一

准备两个文件,一个是echarts.esm.js,另一个是echarts.common.js,本地开发的时候,使用echarts.esm.js,最终打包的时候使用echarts.common.js,并将代码中的引入路径等都替换掉。

如下图:

useLineCharts.png ec-canvas.png
  1. 新建echarts_build_util.js处理代码中的字符串替换
const path = require("path");
const fs = require("fs");
const child_process =require("child_process");

// 微信小程序打包产出目录
const mpweixinPath = path.resolve(process.cwd(), "./dist/build/mp-weixin");

/**
 * 字符串替换
 * @param {String} filePath  目标文件路径
 * @param {String} targetStr 替换目标字符串
 * @param {String} distStr   替换字符串
 * @param {String} fileName  目标文件名
 */
async function handleReplace(filePath, targetStr, distStr, fileName) {
	return new Promise((resolve, reject) => {
		fs.readFile(
			filePath,
			{
				encoding: "utf8",
			},
			(err, data) => {
				if (err) {
					console.error(
						`[读取dist下${fileName}文件错误,请手动修改该文件中的es规范为commonjs规范"]`
					);
					console.error(err);
					reject(err);
				}

				if (data.includes(targetStr)) {
					const rData = data.replace(targetStr, distStr);

					fs.writeFile(filePath, rData, (err) => {
						if (err) {
							console.error(
								`[写入${fileName}文件错误,请手动修改该文件中的es规范为commonjs规范]`
							);
							console.error(err);
							reject(err);
						}

						console.log(`[修改${fileName}文件成功]`);
						resolve();
					});
				} else {
					console.log(`[${fileName}文件不存在esm规范代码,无需修改]`);
					resolve();
				}
			}
		);
	});
}

/**
 * 文件删除
 * @param {String} targetFilePath  目标文件路径
 * @param {String} fileName 文件名
 */
async function handleDeleteFile(targetFilePath, fileName) {
	return new Promise((resolve, reject) => {
		fs.stat(targetFilePath, (staterr, stats) => {
			if (staterr) {
				console.log(`[${fileName}已不存在,无需删除]`);
				resolve();
			}
			if (stats) {
				fs.unlink(targetFilePath, (err) => {
					if (err) {
						console.error(`[${fileName}文件删除失败,请手动删除]`);
						reject(err);
					}
					console.log(`[${fileName}文件删除成功]`);
					resolve();
				});
			} else {
				console.log(`[${fileName}已不存在,无需删除]`);
				resolve();
			}
		});
	});
}

使用示例:

// 将字符串`import * as echarts from "./echarts.esm"`替换成`const echarts = require("./echarts.common.min.js")`
const targetStr = `import * as echarts from "./echarts.esm"`;
const distStr = `const echarts = require("./echarts.common.min.js")`;
// 将useLineCharts.js文件中
// `import * as echarts from "../wxcomponents/echarts-for-weixin-2.0.0/ec-canvas/echarts.esm.js";`
// 替换成
// `const echarts = require("../wxcomponents/echarts-for-weixin-2.0.0/ec-canvas/echarts.esm.js");`
const targetStr2 = `wxcomponents/echarts-for-weixin-2.0.0/ec-canvas/echarts.esm.js`;
const distStr2 = `wxcomponents/echarts-for-weixin-2.0.0/ec-canvas/echarts.common.min.js`;
// 将代码中的`h.init$1`替换成`h.init`
const targetStr3 = `h.init$1`;
const distStr3 = `h.init`;

const ecCavasFile = path.resolve(mpweixinPath, "./wxcomponents/echarts-for-weixin-2.0.0/ec-canvas/ec-canvas.js");
const useLineChartsFile = path.resolve(mpweixinPath, "./hooks/useLineCharts.js");
const echartsEsmFile = path.resolve(mpweixinPath, "./wxcomponents/echarts-for-weixin-2.0.0/ec-canvas/echarts.esm.js");

async function run() {
	console.log("[🚀🚀🚀 处理echarts文件开始]");
  await handleReplace(ecCavasFile, targetStr, distStr, "ec-canvas.js");
	await handleReplace(useLineChartsFile, targetStr2, distStr2, "useLineCharts.js");
	await handleReplace(useLineChartsFile, targetStr3, distStr3, "useLineCharts.js");
  await handleDeleteFile(echartsEsmFile, "echarts.esm.js");
  console.log("[👏🏻👏🏻👏🏻 处理echarts文件结束]");
}

run();
  1. package.json中打包时增加处理命令
{
    "build:mp-weixin:dev": "uni build -p mp-weixin --minify -- --BUILD_ENV development && node ./scripts/echarts_build_util.js",
}

这样在执行打包npm run build:mp-weixin:dev时,最后将处理替换那些字符串

运行日志:

$ npm run build:mp-weixin:dev
> xxx@0.0.0 build:mp-weixin:dev
> uni build -p mp-weixin --minify -- --BUILD_ENV development && node ./scripts/echarts_build_util.js

Compiling...
mode==> production
isDev==> false
BUILD_ENV==> development
​Browserslist: caniuse-lite is outdated. Please run:
  npx update-browserslist-db@latest
  Why you should do it regularly: https://github.com/browserslist/update-db#readme​
​小程序端 style 暂不支持 p 标签选择器,推荐使用 class 选择器,详情参考:https://uniapp.dcloud.net.cn/tutorial/migration-to-vue3.html#style​
DONE  Build complete.
Run method: open Weixin Mini Program Devtools, import dist/build/mp-weixin run.
[ec-canvas文件位置:] /Users/xxx/pro/xxx/dist/build/mp-weixin/wxcomponents/echarts-for-weixin-2.0.0/ec-canvas/ec-canvas.js
[useLineCharts文件位置:] /Users/xxx/pro/xxx/dist/build/mp-weixin/hooks/useLineCharts.js
[echarts.esm.js文件位置:] /Users/xxx/pro/xxx/dist/build/mp-weixin/wxcomponents/echarts-for-weixin-2.0.0/ec-canvas/echarts.esm.js
[🚀🚀🚀 处理echarts文件开始]
[修改ec-canvas.js文件成功]
[修改useLineCharts.js文件成功]
[修改useLineCharts.js文件成功]
[echarts.esm.js文件删除成功]
[👏🏻👏🏻👏🏻 处理echarts文件结束]
[微信开发者工具子进程输出]: miniprogram argv==> [ 'build' ]
[微信开发者工具子进程输出]: 文件夹存在,直接打开

最后经过测试,本地开发和最终打包都没有问题。

4.2 方法二

不准备两种模块的文件,直接将ESM模块文件转为CJS模块文件。

echarts文件下载下来后,直接重命名为echarts.js,后面可以省去替换步骤。

美中不足的是,打包完后,useLineCharts.js代码中的h.init$1还是要改为h.init,不改就会有报错。这里有好的优化方法,可以在评论区告诉我。

  1. 修改导入
  • src/wxcomponents/echarts-for-weixin-2.0.0/ec-canvas/ec-canvas.js
- import * as echarts from "./echarts.esm";
+ import * as echarts from "./echarts";
  • src/hooks/useLineCharts.js
- import * as echarts from "@/wxcomponents/echarts-for-weixin-2.0.0/ec-canvas/echarts.esm";
+ import * as echarts from "@/wxcomponents/echarts-for-weixin-2.0.0/ec-canvas/echarts";
  1. 根目录新建vite.config.cjs.js文件

单独处理esm转cjs

// vite.config.cjs.js
import { defineConfig } from 'vite';

export default defineConfig({
  build: {
    // 指定打包后的目录
    outDir: 'dist/build/echarts',
    // 覆盖 Rollup 的配置
    rollupOptions: {
      // 设置全局的 output 配置
      output: {
        // 强制所有输出文件都为 CommonJS 格式
        format: 'cjs',
        // 如果你需要更细粒度的控制,比如入口文件,可以在这里指定
        // 但通常 Vite 会根据入口文件自动处理
        // entryFileNames: '[name].cjs',
        // chunkFileNames: '[name]-[hash].cjs',
        // 你可以添加 external 或 globals 来排除或映射外部依赖
      },
    },
    // 指定入口文件(这里假设你有一个特定的入口文件用于 CJS 构建)
    // 注意:这通常是一个库或模块的入口,而不是整个应用的入口
    lib: {
      entry: 'src/wxcomponents/echarts-for-weixin-2.0.0/ec-canvas/echarts.js',
      name: 'echarts', // 库的全局变量名(可选)
      fileName: () => `echarts.common.min.js`, // 自定义输出文件名
    },
  }
});

  1. 复制处理好的文件到目标位置
#!/bin/bash

cp dist/build/echarts/echarts.js dist/build/mp-weixin/wxcomponents/echarts-for-weixin-2.0.0/ec-canvas
echo 复制'echarts.js'文件成功

rm -rf "./dist/build/echarts"
echo 删除'dist/build/echarts'目录成功
  1. echarts_build_util2.js

现在只需把h.init$1改回h.init

// ...

async function run() {
	console.log("[🚀🚀🚀 处理echarts文件开始]");
	await handleReplace(useLineChartsFile, targetStr3, distStr3, "useLineCharts.js");
  console.log("[👏🏻👏🏻👏🏻 处理echarts文件结束]");
}
  1. package.json新增scripts脚本命令
{
  "scripts": {
    "build:mp-weixin:dev": "uni build -p mp-weixin --minify -- --BUILD_ENV development && npm run build:cjs",
    "build:cjs": "vite build --config vite.config.cjs.js && sh scripts/copy.sh && node ./scripts/echarts_build_util2.js"
  }
}

运行日志:

$ npm run build:mp-weixin:dev

> xxx@0.0.0 build:mp-weixin:dev
> uni build -p mp-weixin --minify -- --BUILD_ENV development && npm run build:cjs

Compiling...
mode==> production
isDev==> false
BUILD_ENV==> development

uni-app 有新版本发布,请执行 `npx @dcloudio/uvm@latest` 更新,更新日志详见:https://download1.dcloud.net.cn/hbuilderx/changelog/4.24.2024072208.html
​Browserslist: caniuse-lite is outdated. Please run:
  npx update-browserslist-db@latest
  Why you should do it regularly: https://github.com/browserslist/update-db#readme​
​小程序端 style 暂不支持 b 标签选择器,推荐使用 class 选择器,详情参考:https://uniapp.dcloud.net.cn/tutorial/migration-to-vue3.html#style​
DONE  Build complete.
Run method: open Weixin Mini Program Devtools, import dist/build/mp-weixin run.

> xxx@0.0.0 build:cjs
> vite build --config vite.config.cjs.js && sh scripts/copy.sh && node ./scripts/echarts_build_util2.js

vite v4.0.3 building for production...
✓ 1 modules transformed.
dist/build/echarts/echarts.js  1,536.81 kB │ gzip: 422.57 kB
dist/build/echarts/echarts.js  1,056.82 kB │ gzip: 352.35 kB
复制echarts.js文件成功
删除dist/build/echarts目录成功
[useLineCharts文件位置:] /Users/xxx/pro/hsyd_doctor/dist/build/mp-weixin/hooks/useLineCharts.js
[🚀🚀🚀 处理echarts文件开始]
[修改useLineCharts.js文件成功]
[👏🏻👏🏻👏🏻 处理echarts文件结束]
[微信开发者工具子进程输出]: platform==> darwin
[微信开发者工具子进程输出]: miniprogram argv==> [ 'build' ]
[微信开发者工具子进程输出]: 文件夹存在,直接打开

最后经过测试,本地开发和最终打包都没有问题。

5. 遗留的问题

5.1 本地开发,首次启动,微信开发者工具显示有报错

报错内容如下:

SyntaxError: Unexpected token 'export'
    at loadScriptSync (VM89 getmainpackage.js:339)
    at VM89 getmainpackage.js:342
    at VM89 getmainpackage.js:400
    at <anonymous>:411:29(env: macOS,mp,1.06.2401020; lib: 3.1.3)

查看echarts相关页面,图表也不会有正常显示,同样会有报错:

Error: module 'wxcomponents/echarts-for-weixin-2.0.0/ec-canvas/echarts.esm.js' is not defined, require args is '../wxcomponents/echarts-for-weixin-2.0.0/ec-canvas/echarts.esm.js'

解决:

找到src/hooks/useLineCharts.js文件,打开后,执行ctrl+s,让vite重新编译,微信开发者工具就不会有报错。

但是每次首次启动,都需要这样手动执行保存,才不会有报错,有知道如何优化的,评论区告诉我。

5.2 本地开发,热更新慢

每次修改代码,执行保存后,vite编译慢,vite编译完后,微信开发者工具那边才能监测到文件改动,执行热更新,导致整体变得慢。

解决:

一些优化手段,除了第1条有显著效果,其它的效果都不明显:

  1. vite.config.jsbuild.minify不要设置为terser

build.minify默认就是esbuild,速度比terser快20-40倍,这个很有效。

{
  build: {
    minify: "esbuild",
  }
}
  1. vite.config.js中echarts.js大文件进行预构建
{
  optimizeDeps: {
    include: ["./src/wxcomponents/echarts-for-weixin-2.0.0/ec-canvas/echarts.esm.js"],
    force: true
  },
}
  1. vite.config.js中对echarts.js进行单独构建
{
  build: {
    rollupOptions: {
      input: './src/wxcomponents/echarts-for-weixin-2.0.0/ec-canvas/echarts.esm.js',
      output: {
        manualChunks: {
          'echarts': ['./src/wxcomponents/echarts-for-weixin-2.0.0/ec-canvas/echarts.esm.js'],
        },
      },
    }
  }
}
  1. vite.config.js中对echarts.js提前转换和缓存文件以进行预热
{
  server: {
    // https://cn.vitejs.dev/config/server-options.html#server-warmup
    warmup: {
      clientFiles: ["./src/wxcomponents/echarts-for-weixin-2.0.0/ec-canvas/echarts.esm.js"]
    }
  }
}
  1. src/manifest.json中微信小程序增加babelSetting设置,忽略对echarts-for-weixin-2.0.0这个文件夹的babel处理
{
  "mp-weixin" : {
      "appid": "xxx",
      "setting": {
        "urlCheck": false,
        "minified": true,
        "bigPackageSizeSupport": true,
        "enhance": true,
        "babelSetting": {
          "ignore": ["./wxcomponents/echarts-for-weixin-2.0.0/**/*.js"]
        }
      },
      "usingComponents": true,
      "optimization": {
        "subPackages": true
      },
      "lazyCodeLoading": "requiredComponents",
      "libVersion": "3.1.3"
  }
}

5.3 上面第2种方法中存在的疑惑

打包时,并没有把src/wxcomponents/echarts-for-weixin-2.0.0/ec-canvas/ec-canvas.js中的import * as echarts from "./echarts"替换为const echarts = require("./echarts"),但是运行也灭有报错,图表显示也正常,这是为什么?不是说最终需要的都是CJS模块规范的代码吗?有知道的评论区告诉我一声。

6. 参考资料

  1. Nodejs 模块规范 CJS 与 ESM 及其衍生后缀 .cjs 和 .mjs 文件

  2. echarts-for-weixin文档

  3. vite文档

  4. esbuild文档

  5. Rollup文档

  6. uniapp文档