解决CocosCreator2.4.x的ABundle不能使用npm包的问题

前言

今天逛论坛时,我看论坛上有个小伙伴问cocoscreator能不能用npm包?

帖子:cocos creator 工程里面能装npm包吗?

我在想怎么可能不支持呢?

于是点击去看到了大佬的回答,也仔细看了大佬写的了一篇AssetBundle的讲解文章

Creator | Asset Bundle 全解析

这里说npm包都打进了main这个bundle了,分包里没法引用

这句话让我不禁想了一下,EasyGameFramework的所有库都是以npm包的方式安装使用的。

如果不能在分包引用,那不就很烦?

我用egf-ccc-full的项目做了一个测试,发现预览可以,构建出来真的不行,加载bundle就直接报错了。

后来我尝试挂载到全局,可以正常运行,但编码体验下降一百倍,过程繁琐。

我忍不了了

之前就发现一个跟npm包相关的问题:不支持引用项目文件夹外的npm包,编辑器编辑和构建会报错(但预览不会)
那个问题我已经解决了:CocosCreator2.4.2/2.4.3无法编译引用了项目文件夹外部的npm模块

所以我觉得这两个问题应该是差不多的。

今天我就让ABundle支持引用npm包!!!

@一下引擎组

问题重要吗?

我觉得很重要!

虽然说可以将npm包的东西挂载到全局,然后在分包里的脚本,去全局取用。

但这样不安全。控制台也可以访问到了。

而且最重要的是,很别扭,你知道吧?这编码体验很不爽了。

EasyGameFramework的出发点就是要让编码更加Easy,更加爽。

所以
今天我就让ABundle支持引用npm包!!!
今天我就让ABundle支持引用npm包!!!
让EasyGameFramework无缝支持分包使用

复现问题

flag已经立了,说明我已经解决了。哈哈哈哈哈哈~ 它不可能倒下的~

我们先把问题复现出来,看看是怎么回事。

只有构建之后才能看出问题,所以先构建一波,这里我使用egf-ccc-full来测试。

https://github.com/AILHC/EasyGameFrameworkOpen/tree/main/examples/egf-ccc-full

大家可以克隆下来看看

我简单讲一下我的测试逻辑

  1. 加载abtest这个包:test/display-ctrl/abtest,然后将这个包里的ABTestModule注入模块管理器
  2. 通过abtest模块的逻辑,显示abtest里的界面
  3. ABTestModule注入全局
  4. ABTestView继承@ailhc/dpctrl-ccc包的NodeCtrl

运行构建后的看看报错

找不到模块,这个报错的模块名有点奇怪啊,不是我的包名@ailhc/dpctrl-ccc,被切了

万能的断点调试

  1. 我们会发现,在分包的index.js找不到,只有两个包内的脚本模块对象

  2. 下一步。

    然后它把包名切割了!!!!就这样就切割了???!!!为什么?
    给不懂的科普一下npm包名
    · npm包名是没有./或者…/的,大多是只有一个名字 比如xstate
    · 有一些特殊的包名比如:@babel/core,比如@ailhc/egf-core

  3. 继续跟踪,它进了一个require函数,然后跳转过去,发现这个index.js是main bundle里的
    通过切割后的名字dpctrl-ccc到main包里也没找到

    其实main包里应该是有这个包的,只是key不对。
    我通过调试main index.js中对@ailhc/dpctrl-ccc的引用其实是可以找到的,只是它的key 转化为4

调试总结

通过这系列调试,我们可以知道

  • 主包main里的index.js有npm包模块,也可以引用
  • 分包里的index.js如果引用了包外的脚本模块,则会向上main包查找,查不到就报错
  • 而这个分包向主包查找模块的逻辑里,没考虑到npm包名的情况
  • 主包和分包里的index.js的开头那部分都很像,像是模板

总结完之后,我们就去根据我们调试的成果去尝试解决问题

如何来解决这个问题呢?

首先得找到模板index.js开头

通过进入编辑器目录,搜索文本__require找到了
CocosDashboard\resources.editors\Creator\2.4.2\resources\static_prelude.js

(function e(t, n, r) {
    function s(o, u) {
        if (!n[o]) {
            if (!t[o]) {
                var b = o.split('/');
                b = b[b.length - 1];
                if (!t[b]) {
                    var a = "function" == typeof __require && __require;
                    if (!u && a) return a(b, !0);
                    if (i) return i(b, !0);
                    throw new Error("Cannot find module '" + o + "'");
                }
                o = b;
            }
            var f = n[o] = {
                exports: {}
            };
            t[o][0].call(f.exports, function (e) {
                var n = t[o][1][e];
                return s(n || e);
            }, f, f.exports, e, t, n, r);
        }
        return n[o].exports;
    }
    var i = "function" == typeof __require && __require;
    for (var o = 0; o < r.length; o++) s(r[o]);
    return s;
})

很像啊哈

简直就是:roll_eyes:

然后我简单的加了一句log,发现重新构建之后输出出来了,说明改这个有效

然后呢,解决那个切掉我npm包名的逻辑

先判断路径是什么类型的,再决定切不切

var b = o;
if (o.includes("./")) {
//内部代码
b = o.split("/");
b = b[b.length - 1];
} else {
	//npm包代码
}

最后呢,因为主包中,npm包包名和模块对象的对应关系被转换了

所以呢

我们在第一次引用的时候,判断是不是npm包,如果是,就以包名为key,记录一下,下次好用包名查找

(function e(t, n, r) {
    function s(o, u, npmPkgName) {
        if (!n[o]) {
            if (!t[o]) {
                var b = o;
                if (o.includes("./")) {
                    //内部代码
                    b = o.split("/");
                    b = b[b.length - 1];
                } else {
                    //npm包代码
                }
                if (!t[b]) {
                    var a = "function" == typeof __require && __require;
                    if (!u && a) return a(b, !0);
                    if (i) return i(b, !0);
                    throw new Error("Cannot find module '" + o + "'");
                }
                o = b;
            }
            var f = n[o] = {
                exports: {}
            };
            t[o][0].call(f.exports, function (e) {
                var n = t[o][1][e];
                //判断是不是npm包,是就传包名
                return s(n || e, undefined, !e.includes("./") ? e : undefined);
            }, f, f.exports, e, t, n, r);
        }
        //判断是不是npm包,是就用包名作为key存一下模块引用
        if (npmPkgName && n[o] && !n[npmPkgName]) {
            n[npmPkgName] = n[o];
        }
        return n[o].exports;
    }
    var i = "function" == typeof __require && __require;
    for (var o = 0; o < r.length; o++) s(r[o]);
    return s;
})

重新构建,运行,没有报错~
搞定散花~

重要提示

虽然说搞定了,但它还是有局限性的

比如,你主包没有引用过npm包,那么你分包也引用不了

最后

欢迎关注我的公众号,更多内容持续更新

公众号搜索:玩转游戏开发

或扫码:img

QQ 群: 1103157878

博客主页: https://ailhc.github.io/

掘金: https://juejin.cn/user/3069492195769469

github: https://github.com/AILHC

这里还是得 @一下引擎组了 @EndEvil

6赞

感謝分享
我也分享一下我之前使用的解法

在主包 main 下新增一個 exporter.ts
以 mobx 為例

主包 main/exporter.ts

export * from 'mobx';

子包 sub/comp.ts

import { observable } from '../main/exporter';
// do something by observable 

親測可用,具體細節沒特別研究
不用改引擎,只需要將子 AB 包需要用 npm 包從主包 export 出去即可

4赞

感觉也不错,其实最好是官方搞定,这个应该很简单

1赞

想使用npm总是有方案的,但是得注意npm本身不是跨平台的,兼容性一定要注意,npm是面向V8开发的,而不是SpiderMonkey

这是哪跟哪?

我最近就是碰到这个问题。感谢分享 试试看

这个好使啊