1. 前置知识-当前JS主要的两种模块规范
1.1 CommonJS(CJS)
-
CommonJS(CJS)是早期Nodejs官方推荐的规范,代码中常见的关键字是
require和module.exports; -
不过现在Nodejs已原生支持esm语法,例如:
import { open } from 'node:fs/promises'; -
额外了解下,在Nodejs中,想要开启ESM语法主要有下面两种办法:
-
- 在根目录下的
package.json文件中新增{"type": "module"}的配置项(如果设置为{"type": "commonjs"},则会按照CJS规范进行解析)
- 在根目录下的
-
- js文件后缀名改为
.mjs(如果改为.cjs,则会按照CJS规范进行解析)
- js文件后缀名改为
1.2 ES Module(ESM)
-
ES Module(ESM)是ES6标准中引入的模块规范,代码中常见的关键字是
import、export和export default; -
vue-cli和create-react-app等脚手架工具,webpack默认情况下会把通过babel把CJS规范的代码转换为ESM规范的代码。所以开发者可以随心所欲的在代码中使用require和import,不报错。
2. vite+vue3下创建项目及导入echarts-for-weixin
- 创建项目
npx degit dcloudio/uni-preset-vue#vite my-vue3-project
- 导入echarts-for-weixin
- 此时项目目录结构
├── 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. 当前遇到的问题
- 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 };
echarts-for-weixin
-
echarts-for-weixin,
ec-canvas是echarts专门为微信小程序定制的组件,可以将其下载下来,放到wxcomponents文件夹下使用。 -
微信小程序中使用 ECharts文档中说明,如果希望减小包体积大小,可以使用在线自定义构建生成并替换 echarts.js,对于寸土寸金的微信小程序项目,主包限制只有2M大小,超过了就无法提交发版,这里当然选择自定义构建。(这里又扯到另一个问题,将echarts.js这个大文件打包到分包中,不影响主包体积,还有异步加载echarts.js等问题,这里不讨论)
-
在线自定义构建导出的文件模块规范是CJS的,这里用不了,前面已经说了,只能用ESM规范的代码。直接使用会导致报错,常见错误:
-
TypeError: (void 0) is not a function,使用 echarts-for-weixin 遇到错误:TypeError: (void 0) is not a function -
TypeError: echarts.registerPreprocessor is not a function, #插件讨论# 【 echarts - 陌上华年 】TypeError: echarts.registerPreprocessor is not a function
-
-
所以要下载一个ESM规范的echarts包,
https://github.com/apache/echarts/tree/master/dist,打开后选择esm模块规范的文件下载,然后导入项目
-
最终打包,微信小程序中最终使用的是
CJS模块,本地开发又是使用ESM模块。等于是要将ESM转为CJS,或者本地开发使用ESM模块,最终打包后使用CJS模块,真是头疼。 -
微信开发者工具es6转es5
原本想着微信开发者工具还自带有es6转es5,可以化险为夷,但是微信开发者工具转换文件大小上限为500kb.
-
微信开发者工具自带的es6转es5,最大文件大小为
500kb,但是下载的echarts文件超过了500kb,开发者工具默认为你已经处理了这个文件,不会再进行处理。对于最终打包,上传发版会有问题 -
上面自定义构建选择,勾选折线图+柱状图,轻松超过500kb,导致开发者工具处理不了echarts.js文件
- vite最终打包构建,没有将这个echarts.js这个文件处理成小程序需要的
CJS规范文件,开发者工具又因为这个文件超过了500kb就不处理
4. 解决思路
-
方法一:本地开发使用
ESM模块的echarts文件,最终打包后使用CJS模块的echarts文件 -
方法二:本地开发使用
ESM模块的echarts文件,最终打包通过vite把这个ESM模块的echarts文件转成CJS模块的echarts文件
4.1 方法一
准备两个文件,一个是echarts.esm.js,另一个是echarts.common.js,本地开发的时候,使用echarts.esm.js,最终打包的时候使用echarts.common.js,并将代码中的引入路径等都替换掉。
如下图:
- 新建
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();
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,不改就会有报错。这里有好的优化方法,可以在评论区告诉我。
- 修改导入
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";
- 根目录新建
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`, // 自定义输出文件名
},
}
});
- 复制处理好的文件到目标位置
#!/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'目录成功
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文件结束]");
}
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条有显著效果,其它的效果都不明显:
vite.config.js中build.minify不要设置为terser
build.minify默认就是esbuild,速度比terser快20-40倍,这个很有效。
{
build: {
minify: "esbuild",
}
}
vite.config.js中echarts.js大文件进行预构建
{
optimizeDeps: {
include: ["./src/wxcomponents/echarts-for-weixin-2.0.0/ec-canvas/echarts.esm.js"],
force: true
},
}
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'],
},
},
}
}
}
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"]
}
}
}
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模块规范的代码吗?有知道的评论区告诉我一声。