微信小程序插件开发指南

2,776 阅读13分钟

前言

如不了解本文在描述怎么样一个工程可以先行一步,或者移步插件|微信开放文档了解。 那么插件到底是个啥,官网有解释,在这里我们理解为可以在小程序中使用的小程序。

声明:与其说是开发指南,不如说是插件开发排坑指南,本文会在博主持续进行插件开发中进行持续更新。

正文

创建插件

0.开发插件的前提是你已经在小程序后台开通了小程序插件,本文主要针对插件开发做技术性排坑指南,如有对开发前提不懂的:请移步微信小程序插件开通教程,如果你的小程序无法开通,请移步微信小程序插件支持的开放范围及服务类目,如果你的后台没有小程序插件这个菜单,请确保你的小程序是企业级的小程序,个人小程序是无法拥有插件的,且一个小程序只能有一个插件。

1.打开微信开发者工具,创建新项目,输入你想要的项目名称,选择你的项目目录。

如上图所示,开发模式选用插件模式

这里会有一个宿主AppID和插件AppID,请将他们保持一致,这里的AppID就是开通小程序插件服务的那个小程序的AppID。

点击确定,一个插件就创建好了

项目目录

新建插件类型的项目后,项目中将包含三个目录:

  • doc 目录:用于放置插件开发文档。
  • miniprogram 目录:放置一个小程序,用于调试插件,之后我们称此小程序为示例小程序。
  • plugin 目录:插件代码目录。

接下来我会一一介绍这些目录的具体作用。doc 目录开发阶段暂时用不到,后期发布插件时再做详细介绍。

首先,打开和这几个目录同级的project.config.json

主要看上图圈起来的地方,对应的就是上面的目录,这里保证你是插件开发,在你上传项目时做为一个插件上传。如果你已经有一个小程序的源码示例,可以替换掉miniprogram 目录,如果你已经有一个插件源码示例,可以替换掉plugin 目录。一旦你有了更改或者替换,请不要忘记此处也需要修改。

代码调试

在plugin 目录下面进行插件的开发,此目录下面所有的代码都是黑盒,作为插件发布后源码是不可见的。

至文档编辑日期,我的插件目录是这样的,common存放一些公共资源,components存放组件,这里有一个知识点,组件会有内部组件和插件组件之分,内部组件就是你在插件开发中需要用到的组件,插件组件是你的插件要对外进行服务的插件。具体区别在plugin.json可以看出来。

在我的插件中,我只将hello-component对外服务。

在示例小程序中(即我们上方的miniprogram 目录的小程序),这样使用

先在app.json中注册,其中copark-plugin是我定义的插件名,在你的项目中,你也可以自己定义这个名字。

注:“plugin”和“pages”同级

在你想要使用的页面中,使用此插件组件"hello-list": "plugin://copark-plugin/hello-component"

hello-list:组件名(在你页面中使用的名字)

plugin:声明组件来自插件

copark-plugin:是你在app.json中注册的插件名称

hello-component:是插件暴露的组件(即插件组件),因为插件是个黑盒,所以这个具体的组件在发布插件的时候需要在doc文档中详细介绍给使用者。涉及到项目doc 目录,我们后面再详细讲解。

如上图,在宿主页面中,像调用普通组件一样调用插件组件。

如果你的组件不对外暴露出来,那么你就可以在自己的插件里面使用,使用方法和普通小程序的适用方法一样,这里不做过多介绍。

按照前文创建的示例项目,你大概已经了解如何跳转进入插件页面,如上图通过navigator标签,或者在js中通过wx.navigateTo()进行跳转。在开发中,我们一定会遇到一个问题,官方文档中给出我们如何跳转进入插件页面,但没有告诉我们在插件开发中如何跳转进入宿主页面,插件内部页面又该如何相互跳转。我们先把代码调试讲完,后面我们会详细讲解。

简单的介绍差不多完成了,我们在插件项目中已经可以进行开发调试了,那么我们怎么在其他现有的小程序中进行开发调试呢?

点击上传,输入版本号,建议根据推荐版本号输入,上传至插件后台。

上传成功后,会给我们一个引用ID,先把它复制下来。

然后切换到你的另一个小程序中,因为我使用的uni,所以用uni做一个示例,实际操作中,和原生的差别不大。

如图,打开你的uni项目的manifest.json,选择源码视图,在"mp-weixin"内注册组件,version填写dev-拼上你复制的引用ID。使用方法和示例小程序的使用方法一样。

示例小程序中我们演示了通过标签跳转,这里我们尝试通过方法的方式进行跳转。

TIP:这一切你都做好了,可能会发现页面无法显示,且会报错说你不能使用此插件,别担心,当你运行项目使用插件的时候,你没有使用插件的权限,系统会向插件开发者发送一条消息,插件后台的插件使用申请,只要通过申请,那么你的小程序就能使用这个插件了

打开你的小程序后台,点击小程序插件,点击申请管理,这里你会收到一个使用申请,通过后,再去调试一下,插件就应用成功了。

至此,从创建项目到启动插件,再到开发环境下调试插件已经全部完成。

具体开发

建议没有接触过插件开发的小伙伴,先阅读上文,创建一个插件项目,并启动运行,发布开发版本,调试成功后再阅读本章,本章主要讲解如何将一个插件完善到可以真正的发布上线(插件必须具有完整的服务才能发布上线,一个demo是无法通过审核的),和一些开发中会遇到的问题,也会讲解一些浅显的小程序原生开发的知识。

1.插件组件和内部组件

我们来试着写一个内部组件,一个顶部导航

// plugin/components/t-navbar/t-navbar.js
Component({
    /**
     * 组件的属性列表
     */
    properties: {
        title: {
            type: String,
            value: '',
            observer(newVal, oldVal, changedPath) {
                this.setData({
                    title: newVal
                })
            }
        }
    },

    /**
     * 组件的初始数据
     */
    data: {
        nbFrontColor: '#000000',
        nbBackgroundColor: '#ffffff',
        statusBarHeight: 0,
        navBarHeight: 0,
        navStatusBarHeight: 0,
        title: ''
    },
  
    /**
    * 组件生命周期
    */
    lifetimes: {
        attached: function () {
            let systemInfo = wx.getSystemInfoSync();
            //状态栏高度
            let statusBarHeight = Number(systemInfo.statusBarHeight);
            let menu = wx.getMenuButtonBoundingClientRect()
            //导航栏高度
            let navBarHeight = menu.height + (menu.top - statusBarHeight) * 2
            //状态栏加导航栏高度
            let navStatusBarHeight = statusBarHeight + menu.height + (menu.top - statusBarHeight) * 2
            this.setData({
                statusBarHeight: statusBarHeight,
                navBarHeight: navBarHeight,
                navStatusBarHeight: navStatusBarHeight
            })
        }
    },

    /**
     * 组件的方法列表
     */
    methods: {

    }
})
{
  "component": true,
  "usingComponents": {}
}
<!--plugin/components/t-navbar/t-navbar.wxml-->
<view class="t-navbar" style="padding-top:{{statusBarHeight}}px;height: {{navBarHeight}}px;">
    <slot></slot>
    <view>{{title}}</view>
</view>
/* plugin/components/t-navbar/t-navbar.wxss */
.t-navbar{
  width: 100vw;
  display: flex;
  justify-content: center;
  align-items: center;
  background-color: rgba(0, 0, 0, 0);
}

附上源码,以便调试,这也是一个很实用的小组件。目录如下图

使用方法:

在页面json中注册组件

{
    "navigationStyle": "custom",
    "usingComponents": {
        "t-navbar": "../../components/t-navbar/t-navbar"
    }
}

"navigationStyle": "custom"意思是隐藏原生的导航。

引用的路径为相对路径。

如上图在页面中使用。

此组件我们没有在plugin.json中抛出,所以这种内部组件在宿主小程序中是无法使用的。

上图目录中的hello-component是我们在plugin.json中抛出的,所以能在宿主小程序中使用。

2.插件路由跳转

包含插件内部如何跳转自身,如何跳转宿主页面,宿主如何跳转插件。

A.前面已经说过了如何使用插件,也说到了如何从宿主小程序跳转进入插件的页面,我们这里主要说一下如何从插件跳转进入宿主小程序的页面。

在上面1. 中我们做了一个navbar小组件,组件的左上角有一个返回按钮,意思是路由返回

goback() {
  wx.navigateBack()
},

我们绑定此方法,调试发现可以使用,那么我们再测试

wx.navigateTo({
  url: 'pages/index/index',//小程序的路由
})

到这里我们发现不行,无法跳转。

查看官方文档,官方文档也告诉我们无法支持,仅限插件内部使用。

所有导航的api都仅限插件内部使用。

但实际中,调试工具和真机都可以使用wx.navigateBack(),具体原因无法知道,官方也没有给出明确指定。

由此,我们只要控制好路由返回,就能做到从插件返回到我们的宿主小程序。

此外,还有一种方法

wx.navigateToMiniProgram({
  appId:'wxf9d6xxxxxdc40a30',
  path:'pages/index/index',
  envVersion:'trial'
})

打开别的小程序,实际的真机调试中,确实是可以打开别的小程序,但不能再次打开宿主小程序,且会有提示弹窗,体验并不好。

以上,我们尝试了怎么从插件到宿主。

B.接下来我们尝试插件内部的页面进行路由跳转,关键词是相对路径

为了充分理解相对路径的路由跳转,如上图的page目录,最外层是hello页面,car目录里有index页面和list页面

从index页面进行跳转,那么

跳转list页面:

wx.navigateTo({
  url: 'list?id=1' //可拼接参数
})

跳转hello页面:

wx.navigateTo({
  url: '../hello'
})

就像我们加载静态资源一样,使用相对路径,进行插件内部页面跳转。

注:不要和plugin.json的页面路由声明混淆

此方法是目前为止最方便,最简单的跳转方式,跳转页面的生命周期也是完整的,不会有缺失。

如果你不想使用相对路径,还有一种方式进行,可以使用 plugin-private://APPID 访问插件的自定义组件、页面。

wx.navigateTo({
  url: 'plugin-private://插件ID/pages/hello',
})

当然,此方式也可以访问其他插件的组件、页面,前提是宿主小程序申请了你要调用的插件的使用权。

C.如果你实在想要跳转宿主小程序的页面,这里还有一种方式,你可以将你的插件页面改为插件组件,让宿主小程序和插件组件进行通信,告诉宿主你的服务完成了,可以跳转页面了。组件通信参考官方文档自定义组件

3.本地存储

包含storage的作用域,何时何地可以存储在插件作用域

我们已wx.setStorageSync(Object object)为例,进行说明。凡是在插件端进行的缓存行为,都将做为插件的缓存,不与宿主小程序共享。就是在我们plugin 目录里面运行的缓存行为,都会做为插件的的缓存。

组件和页面的形式我们略过,这里我们对插件方法做一点了解。

在我们plugin 目录下,有一个index.js。里面可以暴露一些方法,属性,数据给宿主小程序,就是说这里的方法属性数据等也将是我们的插件提供的服务。

这里作为示例,写了一个setSys方法,宿主小程序可以传入参数,我们的插件进行缓存,我们来看看具体表现。

如图,还是我们之前那个uni项目,先声明插件,再在下面使用此方法。

企业微信截图_16613112989893.png

调试器中看到小程序的缓存和插件的缓存已经被区别开了。

4.request请求

插件token设置,请求域名设置,X-WECHAT-HOSTSIGN请求验签

当我们的插件在使用 wx.request 等 API 发送网络请求时,将会额外携带一个签名 HostSign ,用于验证请求来源于小程序插件。

其中, NONCESTR 是一个随机字符串, TIMESTAMP 是生成这个随机字符串和 SIGNATURE 的 UNIX 时间戳。它们是用于计算签名 SIGNATRUE 的参数,签名算法为:

SIGNATURE = sha1([APPID, NONCESTR, TIMESTAMP, TOKEN].sort().join('')) 

其中,APPID 是 所在小程序 的 AppId (可以从请求头的 referrer 中获得);TOKEN 是插件 Token,可以在小程序插件基本设置中找到。

登录小程序后台➺小程序插件菜单➺基本设置TAB➺下拉到最后➺TOKEN设置

网络请求的 referer 格式固定为 servicewechat.com/{appid}/{ve… {appid} 为小程序的 appid,{version} 为小程序的版本号,版本号为 0 表示为开发版、体验版以及审核版本,版本号为 devtools 表示为开发者工具,其余为正式版本。

插件开发者可以在服务器上按以下步骤校验签名:

  1. sort 对 APPID NONCESTR TIMESTAMP TOKEN 四个值表示成字符串形式,按照字典序排序(同 JavaScript 数组的 sort 方法);
  2. join 将排好序的四个字符串直接连接在一起;
  3. 对连接结果使用 sha1 算法,其结果即 SIGNATURE 。

自基础库版本 2.0.7 开始,在小程序运行期间,若网络状况正常, NONCESTR 和 TIMESTAMP 会每 10 分钟变更一次。如有必要,可以通过判断 TIMESTAMP 来确定当前签名是否依旧有效。

简单来说,就是你告诉服务端你在后台设置的TOKEN,服务端在接收到你的请求时,可以根据请求头携带的信息和你告诉服务端的TOKEN验明这次请求是否合法(宿主小程序合法,TOKEN合法)

5.插件页面分享(转发给朋友)

插件页面也可以使用onShareAppMessage进行页面转发。

让人疑惑的是转发的path应该怎么写才能跳转到插件页面。

Page({
  onShareAppMessage() {
    const promise = new Promise(resolve => {
      setTimeout(() => {
        resolve({
          title: '自定义转发标题'
        })
      }, 2000)
    })
    return {
      title: '自定义转发标题',
      path: '/page/user?id=123',
      promise 
    }
  }
})

官方文档告诉我们,小程序页面可以这样进行分享,path必须是以 / 开头的完整路径。

但是插件的路由一般在宿主小程序中是类似这样的:'plugin://myPlugin/index'

在插件内部跳转的路由是这样的: 'plugin-private://wxa9bxxxx8cada2b21/pages/index'或者'../index'

此处省略博主试错的N个过程,先上结果。

为什么下面的方式可以分享,不要问,问就是此处省略一万字。

onShareAppMessage() {
  const promise = new Promise(resolve => {
    setTimeout(() => {
      resolve({
        title: '自定义转发标题',
        path:'__plugin__/wxa9xxxxxxada2b21/pages/user'
      })
    }, 2000)
  })
  return {
    title: '自定义转发标题',
    path:'__plugin__/wxa9xxxxxxada2b21/pages/user',
    promise
  }
}

前提是插件已被小程序注册并使用。

在目前开发阶段和体验版环境的本地与第三方小程序调试下来,这种方法是可以实现在本页面分享其他页面链接的,并可以携带参数。具体线上表现暂不清楚,等后续亲自以身试毒。

吐槽:真的求求官方给个插件开发文档吧。插件服务源码不可见可以理解,但作为插件开发者,开发插件也和开盲盒一样的体验真的不好啊。