Reducing Phaser’s Filesize: Custom Phaser Builds

Louigi Verona
Apr 26 · 9 min read

For those of us who are definitely not using all parts of Phaser, it might be prudent to reduce the file size of the production library. The current minified file size is around 880kb. This is quite a lot.

The good news is that there is a way to throw out modules you are not using in your game, and this tutorial is going to walk you through the process.

While Devlog 127 already has a good tutorial on how to do that, this tutorial is designed to be even more explicit, step by step instructions for people who generally know how to script games, but haven’t worked much with npm and/or webpack and just need a clear explanation of what to do, without the added baggage of learning a technology they would otherwise not need.

Carefully and patiently go through this tutorial once — and you will get a good understanding of custom builds, and at the same time learn a bit about Phaser’s source code, which is actually very organized and not scary at all. And be sure to bookmark this tutorial for future reference, as details get forgotten fairly quickly.

Final thing to note is that I am using a desktop Linux system. Everything should be identical for Windows and Mac OS users, but just in case it’s not, do reach out.

Prerequisites

You will need npm, which is a javascript pakage manager. No matter which system you are on, installing npm is not difficult.

You will also need to use the command line, so grab your favorite terminal emulator. Don’t worry, there is nothing advanced there at all, and I will walk you through all the necessary commands.

Let’s go!

Step 1. Clone or download a special repo

This is a repo that Photonstorm (Phaser’s author) has provided which is basically a template for building Phaser with webpack. You don’t need to know anything about webpack in order to follow this process.

https://github.com/photonstorm/phaser3-custom-build

Clone the repo, or if you don’t know how to use git, just grab the zip archive of the whole thing and unzip it anywhere on your computer.

Do not worry if by the time you get there, the repo looks dated. It is simply a config and does not require much updates. We will make sure to pull the latest version of Phaser using this setup.

Step 2. Initial setup

Here we will setup the build environment and make sure we are using the latest Phaser version. This is a very straightforward process which involves just 2–3 commands through the command line.

Once you unzip the archive, you will get a “phaser3-custom-build-master” folder. Navigate to this folder using the command line.

Now run this command:

npm install

Sometimes after running this you will get a dependency error. The message will actually explain that in order to fix it you need to run

npm audit fix

In my case this fixed the issue, and I could proceed. (Please note that npm audit fix is only available in npm 6.1.0+)

Now let’s check the Phaser version shipped with the repo:

npm list phaser

This gives you the version of Phaser included. If it is not the latest, we should update Phaser by running this command:

npm update phaser

That’s it. You are now ready to move forward to building Phaser!

Step 3. Building the full version of Phaser

What you now have is a system which takes Phaser’s source code and builds the final .js library.

The root directory has a bunch of .js files. We will only focus on files which start with “phaser”, like phaser-custom.js.

What these files are, are instructions which tell webpack which parts of Phaser to include in the build. Photonstorm has prepared several presets for us to build upon.

In order to build these various version of phaser, you would need to run a command which would look like:

npm run [ALIAS]

The [ALIAS] part depends on which version you would like to build. So, in order to build the full version of Phaser, with all the modules included, you would need to run

npm run buildfull

If you would like to build just Phaser Core, you would run

npm run buildcore

You can see the list of these aliases in a file called “package.json”.

What you want to do at this point is to build the full version of Phaser and check if your game works fine. So, do

npm run buildfull

This will put the newly built Phaser files into the “dist” folder. Do not worry about cleaning it out each time, the commands takes care of it automatically, by first deleting the whole “dist” folder and then replacing it with a new one.

Once you navigate to the “dist” folder, you will see three files: a full version, with comments and everything, a minified version, and a map file.

What you need to do is take the minified version and copy it to your game folder. Link to it, replacing the standard phaser library in your script tag, and see if your game works.

If not — you will have to investigate and probably hit the Discord channel for some help. But if your game is using the same version of Phaser that the npm list phaser has indicated, then the resulting library should be identical.

Step 4. Building a custom version of Phaser

Assuming the full version works, we will now move on to building a custom version.

The idea is this: we take the full version of Phaser, and start stripping off modules we might not need. We then copy the modified version of Phaser into our game and make sure the game works. If we have removed a module that is required by the game, the game will not load and throw an error in the Developer Tools console. In this case we just add the module back. The goal of this is to eventually reduce the library size.

In order to start doing that, open the “phaser-full.js” file. It is the config file for the full build we just did. Copy the contents of this file.

phaser-full.js:

require(‘polyfills’);var CONST = require(‘const’);
var Extend = require(‘utils/object/Extend’);/**
 * @namespace Phaser
 */var Phaser = {Actions: require(‘actions’),
 Animations: require(‘animations’),
 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/EventEmitter’),
 Game: require(‘core/Game’),
 GameObjects: require(‘gameobjects’),
 Geom: require(‘geom’),
 Input: require(‘input’),
 Loader: require(‘loader’),
 Math: require(‘math’),
 Physics: require(‘physics’),
 Plugins: require(‘plugins’),
 Renderer: require(‘renderer’),
 Scale: require(‘scale’),
 Scene: require(‘scene/Scene’),
 Scenes: require(‘scene’),
 Sound: require(‘sound’),
 Structs: require(‘structs’),
 Textures: require(‘textures’),
 Tilemaps: require(‘tilemaps’),
 Time: require(‘time’),
 Tweens: require(‘tweens’),
 Utils: require(‘utils’)};Phaser = Extend(false, Phaser, CONST);
module.exports = Phaser;
global.Phaser = Phaser;

Then open the file called “phaser-custom.js”. Delete all of its contents and paste the contents from “phaser-full.js”. This means that your custom build will at the moment be identical to the full Phaser library.

From now on your build will be using “phaser-custom.js” as its config file, and the command you will be using to build is:

npm run build

In this case “build” is an alias for the config you have in “phaser-custom.js”.

So now in your “phaser-custom.js” locate a var Phaser object. What it lists are modules. You delete a module by simply removing one of the elements.

For instance, let’s say your game is a 2D puzzle game which does not use any physics. That means you can safely remove the Physics: require(‘physics’) element from the Phaser object.

I would recommend removing objects one by one, so that it is easier to understand which object needs to be returned if your game complains. But it is ok to remove several obvious things in bulk. If your game does not draw shapes, doesn’t use physics, DOM and tweens, it is quite safe to remove DOM, Geom, Physics and Tweens.

Either way, at this point the process is very straightforward, albeit a tad tedious: remove the module from “phaser-custom.js”, then run npm run build, then navigate to the “dist” folder, copy the minified custom build to your game and test it.

Step 5. Cleaning up Loader and GameObjects

The two modules you will probably always need are Loader and GameObjects (otherwise, why use Phaser?)

However, frequently you won’t need all filetypes or even most filetypes, as well as not all games objects.

In both of these cases we should specify which things we want to keep and list them explicitly.

5.1 Let’s start with GameObjects.

How do we know which game objects exist in the first place? This is where the source code comes in.

Navigate to phaser3-custom-build-master/node_modules/phaser/src/gameobjects/

Each folder is a game object. Now you can see them all!

Typically, by knowing what your game uses it would be relatively straightforward to figure out which game objects you need.

The idea here is to expand the line GameObjects: require(‘gameobjects’) into it’s ingredients, so that we can only list objects which we need for our game.

So, if we want to add game objects Image, Sprite and Text, we should replace the line GameObjects: require(‘gameobjects’) with this:

GameObjects: {
 DisplayList: require(‘gameobjects/DisplayList’),
 UpdateList: require(‘gameobjects/UpdateList’),Image: require(‘gameobjects/image/Image’),
Sprite: require(‘gameobjects/sprite/Sprite’),
Text: require(‘gameobjects/text/static/Text’),Factories: {
Image: require(‘gameobjects/image/ImageFactory’),
Sprite: require(‘gameobjects/sprite/SpriteFactory’),
Text: require(‘gameobjects/text/static/TextFactory’)
},Creators: {
Image: require(‘gameobjects/image/ImageCreator’),
Sprite: require(‘gameobjects/sprite/SpriteCreator’),
Text: require(‘gameobjects/text/static/TextCreator’)
}}

This looks busy, but there is nothing complicated going on. You are really just linking to the game objects from the source code, that’s all.

So, if you want to add another game object, say, Group, just imitate what is done with other game objects in the above example: you link to the main .js file, say, group/Group, then link to its Factory, which is in the same folder, group/GroupFactory, and then to its creator, again, in the same folder in the source code, group/GroupCreator. That’s it.

Make sure to have correct paths and make sure the commas are all there, since a typo will lead to npm run build halting because of an error. If your changes do result in an error, re-check the file carefully. For instance, a typical mistake is to mislink the text object, since it has a unique path and instead of the usual objectname/Objectname, the path is actually text/static/Text. So make sure to verify all paths in the source code itself, at phaser3-custom-build-master/node_modules/phaser/src/gameobjects/

5.2 Loader

You can probably guess that with Loader it is pretty much similar. The only difference is that the syntax for the Loader object is slightly different. Here’s an example:

Loader: {
 FileTypes: {
 ImageFile: require(‘loader/filetypes/ImageFile’),
 AudioFile: require(‘loader/filetypes/AudioFile’),
 SpriteSheetFile: require(‘loader/filetypes/SpriteSheetFile’),
 ScriptFile: require(‘loader/filetypes/ScriptFile’)
 },
 LoaderPlugin: require(‘loader/LoaderPlugin’)
 }

Notice that you need to require the LoaderPlugin at the end of this structure. But the rest should be self-explanatory, and you can see the whole list of filetypes in the same way you did for game objects, by venturing to phaser3-custom-build-master/node_modules/phaser/src/loader/filetypes/

By the way, notice that you can open some of these .js files and look them through. They are well commented and you might be surprised as to how easy it is to follow what’s going on.

Example

Here is an example of how a final “phaser-custom.js” may look like:

require(‘polyfills’);var CONST = require(‘const’);
var Extend = require(‘utils/object/Extend’);/**
 * @namespace Phaser
 */var Phaser = {Actions: require(‘actions’),
 Animations: require(‘animations’),
 Cache: require(‘cache’),
 Cameras: require(‘cameras’),
 Core: require(‘core’),
 Class: require(‘utils/Class’),
 Create: require(‘create’),
 Display: require(‘display’),
 Events: require(‘events/EventEmitter’),
 Game: require(‘core/Game’),
 //GameObjects: require(‘gameobjects’),
 GameObjects: {
 DisplayList: require(‘gameobjects/DisplayList’),
 UpdateList: require(‘gameobjects/UpdateList’),Group: require(‘gameobjects/group/Group’),
 Image: require(‘gameobjects/image/Image’),
 Sprite: require(‘gameobjects/sprite/Sprite’),
 Text: require(‘gameobjects/text/static/Text’),Factories: {
 Group: require(‘gameobjects/group/GroupFactory’),
 Image: require(‘gameobjects/image/ImageFactory’),
 Sprite: require(‘gameobjects/sprite/SpriteFactory’),
 Text: require(‘gameobjects/text/static/TextFactory’)
 
 },Creators: {
 Group: require(‘gameobjects/group/GroupCreator’), 
 Image: require(‘gameobjects/image/ImageCreator’),
 Sprite: require(‘gameobjects/sprite/SpriteCreator’),
 Text: require(‘gameobjects/text/static/TextCreator’)
 
 }
 },
 Input: require(‘input’),
 Loader: {
 FileTypes: {
 ImageFile: require(‘loader/filetypes/ImageFile’),
 AudioFile: require(‘loader/filetypes/AudioFile’),
 SpriteSheetFile: require(‘loader/filetypes/SpriteSheetFile’),
 ScriptFile: require(‘loader/filetypes/ScriptFile’)
 },
 LoaderPlugin: require(‘loader/LoaderPlugin’)
 },
 Plugins: require(‘plugins’),
 Renderer: require(‘renderer’),
 Scale: require(‘scale’),
 Scene: require(‘scene/Scene’),
 Scenes: require(‘scene’),
 Sound: require(‘sound’)};Phaser = Extend(false, Phaser, CONST);
module.exports = Phaser;
global.Phaser = Phaser;

Conclusion

Once you’ve deleted all the modules that you could, this is it. You now have a much smaller Phaser library that you can use in your production setup. Depending on the type of game you are creating, the decrease in file size can be pretty dramatic.

For instance, for my game I was able to decrease the file size from the standard 884kb to 456kb. And as Apache and other web servers will typically gzip many of these files by default, the file is effectively reduced from 233kb (gzipped version of the full library) to 121kb (gzipped version of my custom build).

Louigi Verona

Written by

https://louigiverona.com/