Uniapp开发微信小程序插件的一些心得
一、uniapp 开发微信小程序框架搭建
1. 通过 vue-cli 创建 uni-ap
// nodejs使用18以上的版本
nvm use 18.14.1
// 安装vue-cli
npm install -g @vue/cli@4
// 选择默认模版
vue create -p dcloudio/uni-preset-vue plugindemo
// 运行 uniapp2wxpack-cli
npx uniapp2wxpack --create
2. 手动在项目根目录创建插件开发需要的 project.config.json
并且内容 miniprogramRoot 和 pluginRoot 属性按以下填写,并且在 appid 字段中自行填写真实的 appid(小程序的 appid)
// project.config.json
{
"miniprogramRoot": "miniprogram/",
"pluginRoot": "uniSubpackage/",
"compileType": "plugin",
"setting": {},
"appid": "xxxxxxxxx",
"projectname": "plugindemo",
"simulatorType": "wechat",
"simulatorPluginLibVersion": {},
"condition": {}
}
3. 在 src 目录下手动创建 plugin.json,
main 属性必须按以下内容填写,也就意味着插件的接口文件指向 src/main.js(因为打包后路径会变成 common/main.js)
{
"publicComponents": {
"hello-component": "components/test"
},
"pages": {
"yuwen-page": "pages/textbook/index"
},
"main": "common/main.js"
}
4. 运行命令编译
npm run dev:mp-weixin-pack-plugin (开发模式)
npm run build:mp-weixin-pack-plugin (生产模式)
此部分详细内容可参考uniapp + uniapp2wxpack
5 生成的完整目录如下



6 打包生成的目录

7 增加插件文档支持
- 在项目根目录增加doc文件夹,新建README.md文件,作为插件的文档
- 项目打包后,手动将doc文件夹拷贝到 dist/dev/mp-weixin-pack-plugin目录
- 微信开发者工具单击右键doc文件夹,上传插件文档
- 在微信公众平台,插件基本信息,更新插件文档
二、uniapp 开发微信小程序插件遇到的问题及解决
1 当前 uniapp 开发插件,插件内部跳转报错 `navigateTo:fail rejected due to no permission currently`
经过定位问题,发现属于uniapp的框架问题,uniapp封装了wx.和uni., 导致uni.navigateTo、uni.request 的权限都是主小程序的,而不是插件内的,在编译生成的代码里把对应的硬改成 wx. 的方法,程序不回报错,但是这样是治标不治本,只能说定位到问题出现在这块儿。
解决办法:
- 将`wx.js` 替换 `/node_modules/@dcloudio/uni-mp-weixin/dist/wx.js`
- 将`index.js ` 替换 `/node_modules/@dcloudio/vue-cli-plugin-uni/lib/mp/index.js`
- index.js下载地址 和 wx.js下载地址
- uniapp官方原贴
2 插件总线通信问题
按照微信官方文档,在plugin.json可以设置main,提供一个index.js给到小程序调用(通过requirePlugin()方法获得),然后本帖的框架main.js是项目的入口程序,不能通过module.export导出。
解决方案:
uniapp预置了一个__uniPluginExports接口,可以在上面写index.js需要暴露的方法和功能。
// src/main.js
...
const app = new Vue({
...
})
// 用来给引用的小程序使用的,相当于原生小程序插件写法的index.js
__uniPluginExports = {
eventBus: {},
...,
其他方法
}
三、插件-小程序通过事件总线通信
1 插件可以预置一个eventBus事件总线,供插件和小程序通信使用。
对应__uniPluginExports的eventBus,小程序通过requirePlugin()获取并使用。
// 用 JS 实现简易版的事件总线
class EventBus {
constructor () {
this._events = new Map()
}
// next 触发某个行为
next (type, ...args) {
let handler = this._events.get(type)
if (Array.isArray(handler)) {
// 如果 handler 是数组,说明有多个监听者,需要依次触发里边的函数
for (let i = 0; i < handler.length; ++i) {
if(handler[i]){
if (args.length > 0) {
handler[i].apply(this, args)
} else {
handler[i].call(this)
}
}
}
} else {
if (handler) {
// 单个函数的情况直接触发即可
if (args.length > 0) {
handler.apply(this, args)
} else {
handler.call(this)
}
}
}
return true
}
// subscribe 订阅/监听某个行为
subscribe (type, fn) {
const handler = this._events.get(type) // 获取对应事件名称的函数清单
if (!handler) {
this._events.set(type, fn)
} else if (handler && typeof handler === 'function') {
// 如果 handler 是函数,说明当前只有一个监听者
// 再次添加监听者,需要改用数组储存
this._events.set(type, [handler, fn])
} else {
// 已有多个监听者,直接往数组里 push 函数即可
handler.push(fn)
}
}
// destroy 销毁事件
destroy (type, fn) {
const handler = this._events.get(type) // 获取对应事件名称的函数清单
// 如果是函数,说明只有一个监听者,直接删除
if (handler && typeof handler === 'function') {
this._events.delete(type)
} else {
handler.splice(
handler.findIndex(e => e === fn),
1
)
}
}
}
export const eventBus = new EventBus()
2 插件可以通过requireMiniProgram()获取小程序app.json暴露出的index.js,
并可以根据配置来初始化插件的一些信息。
// 宿主小程序的app.json
{
"pages": [],
"plugins": {
"plugin":{
"version": "",
"provider": "插件appId",
"export": "index.js",
"genericsImplementation": {
"plugin-page": {
"mp-view": "这里是宿主小程序自定义组件的路径"
}
}
}
}
}
// 宿主小程序index.js, 暴露给插件的一些配置项
module.exports = {
....
}
// 插件获取小程序暴露的index.js
const miniProgramExports = requireMiniProgram(); // 接收小程序通过index.js传过来的数据
3 通过事件总线实现小程序插件通信
插件给小程序发消息
import { eventBus} from '@/utils/eventBus';
eventBus.next('方法名', {
... // 传参
});
// 在页面|组件销毁前,记得执行destory()方法注销事件订阅
eventBus.destory('方法名')
小程序订阅插件消息
const plugin = requirePlugin('插件名');
plugin.eventBus.subscribe('事件名', () => {
// 回调函数
})
4 引用小程序的自定义组件
插件开发官方原贴
4.1 page.json 配置componentGenerics
// 插件目录 src/pages.json
{
"pages": [
"path": "pages/index/index",
"style": {
"navigationBarTextStyle": "black",
"navigationBarBackgroundColor": "#FFFFFF",
"backgroundColor": "#F8F8F8",
"navigationBarTitleText": "课本朗读",
// 配置componentGenerics
"componentGenerics": {
"mp-view": true
}
}
]
}
4.2 plugin.json配置页面标签
// 插件 src/plugin.json
{
// 配置公共组件
"publicComponents": {},
"pages": {
"plugin-page": "pages/index/index"
},
"main": "common/main.js"
}
4.3 插件pages/index/index.vue页面使用mp-view
<template>
<view>
...
<mp-view></mp-view>
</view>
</template>
<script>
...
</script>
<style lang="scss" scoped>
...
</style>
4.4 宿主小程序app.json配置genericsImplementation, 声明自定义组件
// 宿主小程序的app.json
{
"pages": [],
"plugins": {
"plugin":{
"version": "",
"provider": "插件appId",
"export": "index.js",
"genericsImplementation": {
"plugin-page": {
"mp-view": "这里是宿主小程序自定义组件的路径"
}
}
}
}
}
四、插件预置方法
1 微信不支持宿主小程序直接从插件页面跳转宿主小程序的页面。所以可以通过插件预置的方法实现
// 插件src/main.js
import { eventBus } from '@/utils/eventBus ';
...
// 用来给引用的小程序使用的
__uniPluginExports = {
eventBus: eventBus,
/**
* 在插件内跳转到小程序页面
* @param url{string} 页面 URL
* @returns {Promise<unknown>}
*/
jumpPage: function (url) {
return new Promise((resolve, reject) => {
uni.navigateTo({
url: url,
success(e) {
resolve(e);
},
fail(e) {
reject(e);
},
});
});
},
jumpPageBack() {
uni.navigateBack({
fail(e) {
console.log('uni.navigateBack', e);
},
});
},
/**
* 在插件内跳转到小程序页面,并关闭当前页面
* @param url{string} 页面 URL
* @returns {Promise<unknown>}
*/
jumpPageRedirect: function (url) {
return new Promise((resolve, reject) => {
uni.redirectTo({
url: url,
success(e) {
resolve(e);
},
fail(e) {
reject(e);
},
});
});
},
/**
* 在插件内跳转到小程序页面
* @param url{string} 页面 URL
* @returns {Promise<unknown>}
*/
reLaunch(url) {
return new Promise((resolve, reject) => {
uni.reLaunch({
url: url,
success(e) {
resolve(e);
},
fail(e) {
reject(e);
},
});
});
}
}
宿主小程序调用插件跳转路由方法
const plugin = requirePlugin('插件名');
plugin.jumpPage('这里传入小程序的url')
2 某些场景,插件需要跳转h5页面,微信小程序插件不支持webview,所以需要使用宿主小程序跳转webview页面
具体步骤如下:
- 插件通过事件总线触发一个自定义的webview事件通知宿主小程序,并传入h5的url
- 宿主小程序订阅webview的事件,得到h5的url地址
- 宿主小程序调用插件暴露出的jumpPage方法,跳转自身webview页面,并保存h5地址
- 宿主小程序在webview页面拿到h5地址并渲染页面