uni-app(Vue2)自定义条件编译平台,动态配置修改小程序appid

uni-app(Vue2)自定义条件编译平台,动态配置修改小程序appid

  1. 小说前端 🧩
  2. 5 months ago
  3. 15 min read

小程序通过前几年的井喷式爆发,通过技术服务更新快和业务结合能力已经把快应用甩在了身后,包括不限于微信/抖音/支付宝/百度/QQ/快手/京东/360这些流量头部IP都在自己的App架构上搞起了小程序。虽说小程序已经比较泛滥,而且小程序的比重大多不如小游戏,但是小程序依然是大部分中小型企业快速验证自己在国内业务的最优选择之一。

uniapp在服务开发者方面,既能提供快速跨端能力,又能让开发者采用易入门的Vuejs语法,成为大部分小型开发团队的首选。

自定义条件编译

前段时间产品经理想要快速上线一个抖音小程序,就从兄弟公司借来了一套代码。后来又想要快速上线对应的微信小程序,幸好代码中通过package.json中的自定义条件编译已经预留了不同环境变量的配置环境配置,足以满足不同自定义平台环境下的需求。

项目环境

HBuilderX: 4.15

Vue版本: 2.x

已实现平台: 微信小程序/抖音小程序/快手小程序

uniapp文档中的实现

uniapp文档中关于实现自定义条件编译平台的一些事

简单说一下自定义条件编译的要点

{
    /**
     * package.json其它原有配置 
     * 拷贝代码后请去掉注释!
     */
    "uni-app": {// 扩展配置
        "scripts": {
            "mp-dingtalk": { // custom-platform,自定义编译平台配置,可通过cli方式调用
                "title":"钉钉小程序", // 自定义扩展名称,在HBuilderX中会显示在 运行/发行 菜单中
                "browser":"",  //运行到的目标浏览器,仅当UNI_PLATFORM为h5时有效
                "env": { //环境变量
                    "UNI_PLATFORM": "mp-alipay",  //基准平台
                    "MY_TEST": "", // ... 其他自定义环境变量
                 },
                "define": { //自定义条件编译
                    "MP-DINGTALK": true //自定义条件编译常量,建议为大写
                }
            }
        }    
    }
}

其实官方这段代码在注释中已经解释的比较清楚了,但是我在最初看到时确实还是有一点点不理解,比如define下的自定义条件编译常量有什么用呢?我们来看看。

先看看官方的注意事项:

  • UNI_PLATFORM仅支持填写uni-app默认支持的基准平台,目前仅限如下枚举值:h5mp-weixinmp-alipaymp-baidump-toutiaomp-qq
  • browser 仅在UNI_PLATFORMh5时有效,目前仅限如下枚举值:chromefirefoxieedgesafarihbuilderx
  • package.json文件中不允许出现注释,否则扩展配置无效
  • vue-cli需更新到最新版,HBuilderX需升级到2.1.6+版本

补充说明一下:

  • UNI_PLATFORM也就是支持有正妻名分的基准平台,比如mp-alipay,后来拓展出来的侧室像钉钉小程序的mp-dingtalk是不在此列的,但是你要想偷偷给钉钉小程序不一样的待遇,就需要新增自定义条件编译平台,但是UNI_PLATFORM这里要归在mp-alipay门下。
  • define下的编译常量才是你另眼相待钉钉小程序的地方,在这里设置{ "MP-DINGTALK": true }以后,你就可以在代码的任意地方使用钉钉小程序特有的条件编译了。

如同官方这个示例:

// #ifdef MP
小程序平台通用代码(含钉钉)
// #endif
// #ifdef MP-ALIPAY
支付宝平台通用代码(含钉钉)
// #endif
// #ifdef MP-DINGTALK
钉钉平台特有代码
// #endif

但是作为自定义编译常量,你是可以随意命名的,并非固定为MP-DINGTALK,你起名为亲爱的honey来代表钉钉小程序平台也行,只要你想,只要你愿意。就像下面这样:

// package.json
{
    "scripts": {
        "honey": {
            "title": "钉钉小程序",
            "env": {
                "UNI_PLATFORM": "mp-alipay",
                "NAME": "dingtalk"
            },
            "define": {
                "HONEY": true
            }
        }
    }
}

代码中进行条件编译判断的时候:

// #ifdef HONEY
钉钉平台特有代码
// #endif

然后,你通过HbuilderX运行或者发行/自定义发行菜单选择钉钉小程序进行编译时,你新增的自定义条件编译就会生效啦。

uniapp-manifest-dynamic-appid

需求的自我实现

研究完官方文档,我们就可以在package.json中给测试环境和正式环境配置不同的变量,足以满足真实的多个环境区分调用。

多说一点,我们也可以通过条件编译来加载不同的代码逻辑,比如在微信小程序中小说类目需要使用官方的阅读器插件,如果糅合在一个项目中,相关代码放在MP-WEIXIN编译判断中是比较合理的。

通常这个时候,产品经理想要的我们都能满足了。但是现在,产品经理还想要更多,要同一套代码上线不止一个的微信小程序。

条件编译动态配置appid

通常的uniapp项目是在manifest.json中配置小程序的appid的,但是一个项目常理上是对应一个微信小程序的,默认也就可以配置一个。

那我能怎么办呢?简单粗暴的方法,就是把manifest.json复制成manifest-plana.jsonmanifest-planb.json,编译哪个小程序的时候就使用哪个替换掉manifest.json

如果想要简单优雅点实现呢,我们就要研究一下如何动态修改manifest.json中的appid配置。搜索到uni-app自定义多环境配置,动态修改appid这个参考方案,但是作者是在uniapp+Vue3项目中使用vite.config.js来实现的。

由于我这个项目是Vue2版本,为了配置这个切换成Vue3可能会踩一些莫名的坑,时间紧迫不划算(也来不及测试),那就需要研究一下怎么在vue.config.js中实现配置逻辑。

多编译环境配置

配置多环境变量,是为了在vue.config.js中配合自定义编译平台修改appid,那我们就把一些代码中能用到的的配置参数从自定义条件编译中挪出去。

创建env配置文件

env文件的变量是需要和package.json中自定义编译条件进行联动的,因此请允许在这里再回顾一下自定义编译条件的示例配置:

{
    "uni-app": {
        "scripts": {
            "ceshi": {
                "title": "测试环境",
                "env": {
                    "UNI_PLATFORM": "mp-toutiao",
                    "NAME": "ceshi"
                }
            },
            "prod": {
                "title": "生产环境",
                "env": {
                    "UNI_PLATFORM": "mp-toutiao",
                    "NAME": "prod"
                }
            }
        }
    }
}

为了后续的调用一致性,package.json中自定义编译平台的变量名最好和其下的env.NAME保持一致。

我们原本在项目的helper目录放置了一些配置文件/工具函数,那就在这里创建一个env.js文件,放置需要配置的appid等自定义变量。示例代码如下:

const baseUrl = 'https://imwarn.com'
const platIds = {
    wechat: 1,
    toutiao: 2,
    kuaishou: 3
}
const ceshi = {
    baseUrl,
    appid: '',
    appName: '测试',
    packageName: 'com.imwarn.ceshi',
    platId: platIds.toutiao
}
const prod = {
    baseUrl,
    appid: '',
    appName: '爱情的纹理',
    packageName: 'com.imwarn.prod',
    platId: platIds.toutiao
}

module.exports = {
    ceshi,
    prod
}

我们在env.js中定义导出的ceshi等变量需要和package.json中自定义编译配置定义的NAME保持一致,以便于后续通过对象方式直接取值。

我们现在就可以在项目中通过引入env.js文件来使用对应的配置变量了,简单使用方式如下:

import ENV_CONFIG from '@/helper/env.js'
console.log(ENV_CONFIG[process.env.NAME].packageName)

此时通过【运行】菜单运行【测试环境】编译完成后,会在引用的页面打印出com.imwarn.ceshi

当然这种每使用一次就需要引用一次的方式不够友好,而且我们还要动态修改manifest.json中的appid变量,我们就需要用到vue.config.js配置文件了。

默认vue.config.js文件在uniapp项目中是不存在的,因此我们先在项目根目录新建这个js文件。

创建config文件

我们创建vue.config.js文件后,先把env.js导出的内容挂载到环境变量上,这样就等同于全局引用,后续在任意位置都可以直接使用了。相关代码如下:

// vue.config.js
const ENV_CONFIG = require('./helper/env.js');

module.exports = {
    chainWebpack: config => {
        config.plugin('define').tap(args => {
            args[0]['process.env'].config = JSON.stringify(ENV_CONFIG)
            return args
        })
    },
};

在我们通过自定义编译条件平台运行编译项目时,定义在自定义条件中env下的变量都会被自动合并挂载到process.env变量中,因此我们可以在这种情况下使用process.env.NAME获取定义的值为ceshi

此时我们在process.env下定义了一个全局变量config,通过全局变量process.env.config可以获取到env.js默认导出的所有变量,那我们要拿到当前自定义编译平台对应的变量就是水到渠成的事情了。

// 获取ceshi这个对象变量
const ceshi = process.env.config[process.env.NAME]

// 获取其中定义的API域名URI
const apiBaseURL = ceshi.baseUrl

动态修改appid

Vue项目进行编译构建时,我们可以通过node的一些API来对文件进行操作。我们在此处也可以借用node对文件的操作API,根据当前运行/发行的自定义平台获取到appid,再对manifest.json的文件内容进行读取、遍历、匹配修改、写入保存,就实现了我们的小目标(不是一个亿的那种)。

uniapp有提供发布时动态修改manifest.json的示例代码片段,再结合参考方案,我们这部分的代码实现如下:

const fs = require('fs');
const path = require('path');

// 读取 manifest.json ,修改后重新写入
const manifestPath = path.resolve(__dirname, 'manifest.json');
let Manifest = fs.readFileSync(manifestPath, { encoding: 'utf-8' });

function replaceManifest(path, value) {
    const arr = path.split('.');
    const len = arr.length;
    const lastItem = arr[len - 1];    
    let i = 0;
    let ManifestArr = Manifest.split(/\n/);

    for (let index = 0; index < ManifestArr.length; index++) {
        const item = ManifestArr[index];
        if (new RegExp(`"${arr[i]}"`).test(item)) ++i;
        if (i === len) {
            const hasComma = /,/.test(item);
            ManifestArr[index] = item.replace(
                new RegExp(`"${lastItem}"[\\s\\S]*:[\\s\\S]*`),
                `"${lastItem}": ${typeof value === 'string'? '"'+value+'"' : value}${hasComma ? ',' : ''}`
            );
            break;
        }
    }
    Manifest = ManifestArr.join('\n');
}

// 具体使用,找到对应key值替换为新的值
// console.log(process.env)
const appid = ENV_CONFIG[process.env.UNI_SCRIPT].appid;
console.log('appid:', appid, process.env.VUE_APP_PLATFORM)
const replacePath = `${process.env.VUE_APP_PLATFORM}.appid`
console.log(replacePath)
replaceManifest(replacePath, appid);

fs.writeFileSync(manifestPath, Manifest, { flag: 'w' });

在这部分代码中,我们获取了两个参数:

  • process.env.VUE_APP_PLATFORM获取到当前编译所属的小程序平台,判断是微信小程序还是抖音小程序这类
  • ENV_CONFIG[process.env.UNI_SCRIPT].appid获取到自定义编译环境对应的appid变量,也就是我们想动态配置的主要参数

注意:参考方案中是在process.env.UNI_CUSTOM_DEFINE下面找到package.json中定义的NAME变量的,但是在实际使用中打印process.env并没有看到这个值,而是通过process.env.UNI_SCRIPT获取到了自定义编译平台的变量名,也就是package.jsonuni-app.scripts.ceshi的这个ceshi,当然这样就够了,我们就能从配置文件中拿到appid啦。

不同开发版本中全局变量可能会有变化,因此大家使用的时候一定要先打印process.env来判断我们能使用哪个变量。

使用方式

1、本地调试时,通过HBuilderX菜单栏的运行菜单选择底部的自定义环境测试环境或其他环境来运行编译。通过cli命令运行示例:

npm run dev:custom ceshi

2、发布上线时,稍微有点区别,通过HBuilderX菜单栏的发行菜单底部自定义发行生产环境等自定义环境来编译发布。通过cli命令运行示例:

npm run build:custom prod

在发行时HbuilderX会弹出【发行】弹窗,里面会展示原有manifest.json文件的appid,这点没影响的哈。因为我们只有在确认发行之后,项目才进入编译过程,进行appid变量的动态替换,然后才会进入编译构建流程。

uniapp-manifest-dynamic-appid

3、在业务代码中,可以通过process.env.config[process.env.NAME]来获取env.js配置的环境变量。

写在最后

参考方案: uni-app自定义多环境配置,动态修改appid

参考文档: uni-app发布时动态修改manifest.json

参考文档: uni-app实现自定义条件编译平台

uniapp Vue2 小程序 前端