phaser游戏引擎的自定义构建体积优化实践指南

705 阅读4分钟

背景

无论国内外,phaser相关的文章少之又少,custom build也只有一篇官方文章,即使跟着文章做也有许多问题,所以在这分享一下自己的实践操作,有不对的地方欢迎大家指正~

Phaser引擎不受Tree Shaking影响,无论全量导入,还是按需导入,Phaser都会全量地导入在你的项目中。因此,Phaser官方提供自定义构建的方式来优化Phaser的体积。

官方repo:

github.com/phaserjs/cu…

Custom build步骤

废话不多说,进入正题

  1. clone下官方repo的地址
  2. 修改package.json中使用的phaser版本为自己项目中的版本,保证版本的一致性
  3. (如果是新开的项目直接用最新的即可,跳过 2.)
  4. 首先拿到buildfull的体积作为对比。

此处说明我用的是比较老的版本,所以fullbuild可能会比大家小

shake Phaser模块

以下是本次优化手动"shake"的配置,注释掉的模块没有被打包进来

Events,Game,GameObjects,Geom,Input,Loader,Plugins,Renderer,Scene,Scenes,Tweens

以上所有是目前我需要用到的模块,

其中,GameObjects和Loader是必选项, 但是可以更细粒度地拆分这两个模块,

在node_modules/phaser/src/gameobjects/目录中,每个文件夹即是一个模块,Loader同理

image.png

在以下示例中,GameObject只保留了Sprite和Group,Loader只保留了SpriteSheetFile。 因为我只用到了这些,大家可以根据自己的项目来做更细粒度的拆分。此处的效果比较明显

require("polyfills");

var CONST = require("const");
var Extend = require("utils/object/Extend");

/**
 * @namespace Phaser
 */

var Phaser = {
    // Actions: require('actions'),
    // Animations: require('animations'),
    // BlendModes: require('renderer/BlendModes'),
    // Cache: require('cache'),
    Cameras: require("cameras"),
    // Core: require('core'),
    // Class: require('utils/Class'),
    // Create: require('create'),
    // Curves: require('curves'),
    // Data: require('data'),
    // Display: require('display'),
    // DOM: require('dom'),
    Events: require("events/index"),
    // FX: require('fx'),
    Game: require("core/Game"),
    // GameObjects: require('gameobjects'),
    GameObjects: {
        DisplayList: require("gameobjects/DisplayList"),
        UpdateList: require("gameobjects/UpdateList"),
        Sprite: require("gameobjects/sprite/Sprite"),
        Group: require("gameobjects/group/Group"),
        Factories: {
            Sprite: require("gameobjects/sprite/SpriteFactory"),
            Group: require("gameobjects/group/GroupFactory"),
        },
        Creators: {
            Sprite: require("gameobjects/sprite/SpriteCreator"),
            Group: require("gameobjects/group/GroupCreator"),
        },
    },
    Geom: require("geom"),
    Input: require("input"),
    // Loader: require("loader"),
    Loader: {
        FileTypes: {
            SpriteSheetFile: require("loader/filetypes/SpriteSheetFile"),
        },
        LoaderPlugin: require("loader/LoaderPlugin"),
    },
    // Math: require('math'),
    // Physics: require('physics'),
    Plugins: require("plugins"),
    Renderer: require("renderer"),
    // Scale: require('scale'),
    // ScaleModes: require('renderer/ScaleModes'),
    Scene: require("scene/Scene"),
    Scenes: require("scene"),
    // Structs: require('structs'),
    // Textures: require('textures'),
    // Tilemaps: require('tilemaps'),
    // Time: require('time'),
    Tweens: require("tweens"),
    // Utils: require('utils')
};

//  Merge in the optional plugins and WebGL only features

if (typeof FEATURE_SOUND) {
    Phaser.Sound = require("sound");
}

//   Merge in the consts

Phaser = Extend(false, Phaser, CONST);

/**
 * The root types namespace.
 *
 * @namespace Phaser.Types
 * @since 3.17.0
 */

//  Export it

module.exports = Phaser;

global.Phaser = Phaser;

Webpack配置

在phaser的custom build webpack配置中,已经使用了terser做代码压缩,所以压缩方面无需更多操作

在插件注入方面,仍然有体积优化可以操作。在楼主的项目中,我们仅使用Canvas方式渲染phaser游戏,所以取消全部其他插件。

tips: 如果你做的是FbInstantGame,保留FbInstant插件。

    plugins: [
        new CleanWebpackPlugin(),
        new webpack.DefinePlugin({
            "typeof CANVAS_RENDERER": JSON.stringify(true),
            "typeof WEBGL_RENDERER": JSON.stringify(false), // 去掉webgl
            "typeof WEBGL_DEBUG": JSON.stringify(false),
            "typeof EXPERIMENTAL": JSON.stringify(false),
            "typeof PLUGIN_3D": JSON.stringify(false),
            "typeof PLUGIN_CAMERA3D": JSON.stringify(false), // 去掉3D
            "typeof PLUGIN_FBINSTANT": JSON.stringify(true), // 保留FB plugin
            "typeof FEATURE_SOUND": JSON.stringify(false),
        }),
    ],

结果

节约了大概400K-(gzip前)的体积(~45%)

当然,越简单的项目可去掉的模块越多,效果越明显。

此处值得一提的是我用的是非常老的版本,3.16.1,在官方教程中使用更新的版本,效果更好。

踩坑

踩坑点:Phaser的自定义构建产物不支持各种形式的导入

import Phaser from './phaser-custom.js'; // ❌
import * as Phaser from './phaser-custom.js' // ❌
// vite 配置别名错误
  resolve: {
    alias: {
      phaser: path.resolve(__dirname, '../lib/phaser-custom.js'),
    },
  },

原因是 Phaser 没有默认的导出 export default,也不支持使用 import * as 导入所有命名导出

仅支持以下方式

  // it works
  <script src="./phaser-custom.js"></script>
  <script>
    console.log(Phaser)
  </script>

原理:在 HTML 文件中直接通过 文件时,UMD 文件通常会将库暴露在全局对象上,这样可以直接访问库

代码改造

由于Phaser引入方式的改变,import语句也失去了效果

import phaser,{Module1,Module2...} from "Phaser" 
// 全部修改成 const Phaser = window.Phaser? ❌

所以我们做一个包装,并且利用vite/webpack的alias代替phaser。这样无需修改Phaser相关项目代码即可正常跑通

//phaserWarp.js
const Phaser = window.Phaser
export const { Game, CANVAS, Events, Scene, GameObjects, Geom } = Phaser
//vite config
// 修改phaser别名为phaserWrap的路径,这样你的import phaser语句都是从phaserWarp.js中引入
  resolve: {
    alias: {
      phaser: path.resolve(__dirname, '../src/phaserWrap.js'),
    },
  },

至此,大功告成,项目正常运行