V7投稿 | 你不知道的 Cocos Creator 神技:用宏剔除不用的代码

背景

我们是不是习惯将一些常用的东西都归纳总结起来,做成一个框架,方便后续项目的复用,形成自己的“框架”,但是随着时间的迁移,这个框架会越来越“臃肿”,那我们有什么办法解决这个问题呢?

方案

一般针对这种情况,各种编辑器会有两种做法

直接剔除“无用”资源

无用的意思是编辑器检测到没有“引用”,比如CocosCreator(后面称CC)对非动态引用的资源剔除方案,比如AndroidStuiod(后面称AS)的shrinkResources配置。

[PS1: 因为Cocos打包后的资源都不是通过Android本身直接引用的,所以这个字段在Cocos的安卓工程中应该要配置为false)。]

值得注意的是,AS中的代码混淆(minifyEnabled),不但会混淆方法,而且也会将无用的代码剔除,所以如果是通过反射调用的代码,我们也要进行混淆剔除。

[PS2: 比如在CocosCreator 中用callStaticMethod方法通过反射方式js调用java代码,那么我们要么不启用混淆,要么将混淆的方法写到proguard-rules.pro文件中]

通过宏来剔除代码

这类比较常见在c++、oc语言中,比如iOS工程在XCode中,通过Preprocessor Macros设置宏。

因为CC已经支持资源剔除了,那么,代码上我们有什么办法剔除呢?从方案来讲,如果使用方案一,感觉更省力,但是考虑js的特性,很难做到这种效果,那么CC是怎么做的呢?

CocosCreator的方案

是的,CC选择的是宏的方式,这种方式与webpack其实是一致的,但是在CC3.0以前,只有官方内置的宏(主要是平台宏),其实是无法满足我们的需求的,但是在CC3.x,开放了自定义宏的方式,这种对我们就简单多了,下面介绍CC的使用方式

步骤一

从顶部项目菜单打开项目设置,选择宏配置,然后添加自定义宏,比如我定义一个宏为CUSTOM_TEST,如果要启用该宏,打钩即可。如下

image

步骤二

在代码中,用ifelse判断即可,编译时编辑器会自动剔除代码。如下

import { _decorator, Component, Node } from 'cc';
import { CUSTOM_TEST } from 'cc/userland/macro';
const { ccclass, property } = _decorator;

@ccclass('Test')
export class Test extends Component {
    start() {

        if (CUSTOM_TEST) {
            // TODO 宏开启要做的事
        } else {
            // TODO 宏关闭要做的事
        }
    }

}

这样就结束了~

[PS3: CC的宏配置保存在settings/v2/packages/engine.json文件的macroCustom字段中,可以写个插件控制该字段]

结尾

本次分享的功能极其简单,只是在相关技术群讨论时,感觉很多人还没用过甚至不知道,所以分享出来

[PS4: 3.8.2 以前可能存在问题, 3.8.2 已经修复]

[PS5: 感谢麒麟子大佬取的标题]

4赞

在早前我还建议过将「宏」的值,可以在不同的构建任务中修改,这样子就完美了

不同的构建任务中,我是通过插件修改的

1赞

onBeforeBuild 吗

是的123

懒.jpg 要是官方能支持更好,哈哈

学习了…

真棒…3.8.2亲测有效…
打包后整个if的代码块都被优化了…
38ff0155a5da86dd1b5f5a3fdd2805b

甚至用as改名也能跟踪到…
image
import { SDK_WX, SDK_WX2 as SDK_DY } from 'cc/userland/macro';
但是传达给其他函数就跟踪不到了…
8464c9df50feed310515821b5705e13

但是假如项目设置没有设置对应宏的话…ts层就会报错…
image
这时候预览还是可以运行的…变量会变成undefined…
image
但是打包就报错了…
dca4e8d15f24e78558b2250a7343ab8

总体来说为项目结构带来了一种可行性…肯定是好的…
但是宏的范围必须约束在if的作用域中…
导致很多不能写逻辑的内容…就不能随意切换了…
而且多个if作用域之间是无法跨越的…
灵活度还是无法和原生支持宏的语言匹配…

再做了一些细致的测试…
模拟一个实际的需求…

ts源码

import { SDK_WX, SDK_WX2 as SDK_DY } from 'cc/userland/macro';
const { ccclass, property } = _decorator;

interface ISDK {
    showAd(slot: string): void;
}
let SdkCtor: new (...args: any[]) => ISDK;

if (SDK_WX) {
    SdkCtor = class implements ISDK {
        constructor(public adId: string) { }
        showAd(slot: string) { console.log('show ad at wx', slot, 'id', this.adId); }
    }
}
else if (SDK_DY) {
    SdkCtor = class implements ISDK {
        constructor(public adId: string, public appKey: string) { }
        showAd(slot: string) { console.log('show ad at dy', slot, 'id', this.adId, this.appKey); }
    }
}
else {
    SdkCtor = class implements ISDK {
        constructor(public isTest: boolean) { }
        showAd(slot: string) { console.log('show ad with no sdk', slot, 'isTest', this.isTest); }
    }
}

const cfg = {
    adId: SDK_WX ? 'wx-ad-id' : SDK_DY ? 'dy-ad-id' : '',
    appKey: SDK_DY ? 'dy-app-key' : '',
    isTest: true,
}

function createSdk(): ISDK {
    if (SDK_WX) {
        return new SdkCtor(cfg.adId);
    }
    else if (SDK_DY) {
        return new SdkCtor(cfg.adId, cfg.appKey);
    }
    else {
        return new SdkCtor(cfg.isTest);
    }
}
const sdk = createSdk();
sdk.showAd('test');

打包后

var b = s.ccclass,
    w = s.property;
new(function () {
    function t(t, e) {
        this.adId = t, this.appKey = e
    }
    return t.prototype.showAd = function (t) {
        console.log("show ad at dy", t, "id", this.adId, this.appKey)
    }, t
}())("dy-ad-id", "dy-app-key").showAd("test");

优化结果还是比较欣慰的…
就是用到了...args有点不够优雅…
当然这是多个类写到一个文件…
比如独立文件还能这样…

ts源码

export default SDK_WX && class implements ISDK {
    constructor(public adId: string) { }
    showAd(slot: string) { console.log('show ad at wx', slot, 'id', this.adId); }
}

这个时候ts是有类型判断的…
image
(SDK_DY里多传参数就没有报错)
可以说是比较实用了…

打包后

System.register("chunks:///_virtual/WxSdk.ts", ["cc", "./macro"], (function (t) {
    var c, e;
    return {
        setters: [function (t) {
            c = t.cclegacy
        }, function (t) {
            e = t.SDK_WX
        }],
        execute: function () {
            c._RF.push({}, "180f4KG3elPaolQ9n0zqXWh", "WxSdk", void 0);
            t("default", e);
            c._RF.pop()
        }
    }
}));

但是总体来讲…还是要项目跟着代码去设置一大堆宏…
虽然不是不能接受…但是有点啰嗦…