|  
 随着游戏往重度方向发展,游戏代码包越来越大,为了更好的加载体验,分包加载迫在眉睫。早在2018年微信小游戏就推出分包API了,但是白鹭引擎官方却迟迟没发布高效的分包解决方案,就目前能找到的分包方案,却有很多问题。
 我们先来分析一下目前公开的方案:方案1:白鹭官方推出的资源分包,只能针对素材分包,没无法针对js代码分包,这里查看文档
 方案2:在方案1的基础上,自己把原项目拆分成几个独立项目,一个分包对应一个项目,用类库的方式进行编译输出js。这种方式开发时不灵活,需要管理太多的项目,很不方便。
 方案3:白鹭在5.3.6引擎推出了webpack 编译模式,提供的WebpackBundlePlugin插件支持js拆分输出,但是该方式有问题:拆分输出的文件必须一次性加载才能运行,无法按需加载(分包加载),详情参考这里(截止到2021年8月官方还未修正)
 目前公开渠道能找到的这几种方法都解决不了实际需求,那就只能自己开发了。
 需求目标:开发的时候只有1个项目,根据文件夹将代码编译出多个独立的js,输出到对应的包里面按需分包加载。解决思路:定义一套输出规则,修改引擎的编译代码,使得项目代码输出成独立的多个js,最终实现分包加载。
 1、配置js输出规则:在config.wxgame.ts里面修改ConpilePlugin调用,配合编译代码实现js拆分输出
  new CompilePlugin({ libraryType: "release", defines: { DEBUG: false, RELEASE: true 
      , subpackages:[{name: "game_main", matcher:(filepath)=>{
              
              if(filepath.indexOf("miniGame/") >-1 ) return true; 
              return false;
          }
      }] 
  } }),
 2.1、在config.wxgame.ts里面导入SubPackagePlugin(代码见文末) import { SubPackagePlugin } from './wxgame/subpackage';
 2.2、在config.wxgame.ts里面ManifestPlugin代码处下一行加上分包文件路径配置 , new SubPackagePlugin({ output: 'manifest.js', subPackages: [
          { root: "stage_game",  "includes": ["js/game_main.min.js","js/xxxxx.js"]}
      ]
  })
 3、修改引擎编译代码:找到插件CompilePlugin的位置:C:\Users\用户名\AppData\Roaming\Egret\engine\5.2.33\tools\tasks\compile.js,修改tinyCompiler方法 function tinyCompiler(defines, pluginContext) {
    var os = require('os');
    var outfile = FileUtil.joinPath(os.tmpdir(), 'main.min.js');
    var options = egret.args;
    options.minify = true;
    options.publish = true;
    var configParsedResult = compiler.parseTsconfig(options.projectDir, options.publish);
    var compilerOptions = configParsedResult.options;
    var fileNames = configParsedResult.fileNames;
    var tsconfigError = configParsedResult.errors.map(function (d) { return d.messageText.toString(); });
    compilerOptions.outFile = outfile;
    compilerOptions.allowUnreachableCode = true;
    compilerOptions.emitReflection = true;
    compilerOptions.defines = defines;
	var pp = defines.subpackages; 
	if(pp){
		for(var j=0; j<pp.length; j++){
			var newList = [];
			for(var i=0; i<fileNames.length; i++){
				if( pp[j].matcher(fileNames[i]) ){
					newList.push(fileNames[i]);
					fileNames.splice(i, 1);
					i--;
				}
			}
			if(newList.length > 0){
				
				newList.push(options.projectDir + "libs/modules/egret/egret.d.ts");
				newList.push(options.projectDir + "libs/modules/game/game.d.ts");
				newList.push(options.projectDir + "libs/modules/assetsmanager/assetsmanager.d.ts");
				var compilerHost = compiler.compile(compilerOptions, newList);
				if (compilerHost.messages && compilerHost.messages.length > 0) {
					global.exitCode = 1;
				}
				var jscode = fs.readFileSync(outfile);
				pluginContext.createFile(pp[j].name + ".js", new Buffer(jscode));
			}
		}
	}
    var compilerHost = compiler.compile(compilerOptions, fileNames);
    if (compilerHost.messages && compilerHost.messages.length > 0) {
        global.exitCode = 1;
    }
    var jscode = fs.readFileSync(outfile);
    return jscode;
}
jscode = tinyCompiler(this.options.defines, pluginContext);
 4、执行发布命令:egret publish --target wxgame 以上,有效的解决了js文件拆分和分包的需求。需要强调的是,拆分了js,跨js调用的Class,需要在js里面设置 window.xxxx = xxxx对外暴露,这样才能正常调用。
 附:项目里面调用分包代码: public static openSubpackage(name, fun){
    if(DEBUG){
        return fun && fun();
    }
    if (window["wx"] && wx.loadSubpackage) {
        let task = wx.loadSubpackage({
            
            name: name, 
            success: function () {
                fun && fun();
            }
        });
        if(!task){
            console.warn("openSubpackage-fail:", name);
            return fun && fun();
        }
        task.onProgressUpdate(res => {
            
            console.warn("onProgressUpdate:", res);
        })
    }
    else {
        window["require"]("./" + name + "/game.js");
        fun && fun();
    }
}
Main.openSubpackage("stage_game", ()=>{
    
})
 附:SubPackagePlugin插件代码 import * as path from 'path';
const manifest = {
    initial: [],
    game: []
}
type SubPackagePluginOptions = {
    output: string,
    subPackages: {
        root: string,
        includes: string[]
    }[],
    verbose?: boolean
}
export class SubPackagePlugin {
    private verboseInfo: { filename: string, new_file_path: string }[] = [];
    private includes: {} = {};
    constructor(private options: SubPackagePluginOptions) {
        console.warn("options",options);
        if (this.options.subPackages) {
            this.options.subPackages.forEach((sub_package) => {
                sub_package.includes.forEach((file_name) => {
                    this.includes[file_name] = sub_package.root;
                })
            })
        }
    }
    async onFile(file: plugins.File) {
        const filename = file.relative;
        if(filename == "manifest.js") return;
        const extname = path.extname(filename);
        
        if (extname == ".js") {
            const basename = path.basename(filename);
            let trans_filename = filename.split("\\").join('/');
            
            let new_file_path = trans_filename;
            let isSubPackage = false;
            if (this.options.subPackages) {
                
                if (this.includes[trans_filename]) {
                    
                    new_file_path = this.includes[trans_filename] + "/" + trans_filename;
                    isSubPackage = true;
                }
            }
            
            if (this.options.verbose) {
                console.log(`SubPackagePlugin: ${filename} isSubPackage: ${isSubPackage}`);
            }
            file.path = path.join(file.base, new_file_path);
            if (!isSubPackage) {
                const relative = file.relative.split("\\").join('/');
                if (file.origin.indexOf('libs/') >= 0) {
                    manifest.initial.push(relative);
                }
                else {
                    manifest.game.push(relative);
                }
            }
            if (this.options.verbose) {
                this.verboseInfo.push({ filename, new_file_path })
            }
        }
        return file;
    }
    async onFinish(pluginContext: plugins.CommandContext) {
        const output = this.options.output;
        const extname = path.extname(output);
        let contents = '';
        switch (extname) {
            case ".json":
                contents = JSON.stringify(manifest, null, '\t');
                break;
            case ".js":
                contents = manifest.initial.concat(manifest.game).map((fileName) => {
                    let isEgret = fileName.indexOf("egret-library") >-1;
                    return (isEgret ? 'requirePlugin':'require') + `("${fileName}")`;
                }).join("\n")
                break;
        }
        pluginContext.createFile(this.options.output, new Buffer(contents));
        if (this.options.subPackages) {
            const self = this;
            this.options.subPackages.forEach(function (item) {
                let gameJS = "";
                
                
                gameJS = item.includes.map((file) => {
                    const extname = path.extname(file);
                    console.log('extname', extname);
                    let return_location = ''
                    switch (extname) {
                        case '.js':
                            return_location = `require("js/${path.basename(file)}")`
                            break;
                    }
                    return return_location
                }).join("\n");
                console.log('output', gameJS);
                pluginContext.createFile(path.join(item.root, "game.js"), new Buffer(gameJS));
            });
        }
        if (this.options.verbose) {
            this.verboseInfo.forEach((item) => {
                console.log(`SubPackagePlugin: ${item.filename} => ${item.new_file_path}`);
            });
        }
    }
}
  
 本文原创,未经作者同意禁止转载 |