Phaser 4 Development Report 1 + Video
Before I start I just wanted to say that I've not forgotten about the September Backers Pack! It will be out very shortly, keep an eye out for it soon :)
Hi all,
I just wanted to spend some time updating you on what's been happening with Phaser 4 development last week. Since making my (very long!) announcement post I had a lot of feedback from you. The overwhelming majority of feedback was positive. Most of you agreed it was right to update Phaser to use a modern web development approach and that needed to include refactoring the API. However, some of you raised legitimate concerns, too, so I'll do my best to address these before I start showing what I've been up to.
Could you rename Phaser to avoid confusion?
I can see where the idea is coming from. The move from v2 to v3 caused confusion in the way the API had changed. Some devs get confused about if a tutorial is meant for v3 or not. Also, from a product point of view (i.e. companies selling tutorial videos and courses) it would make their life easier.
This is what used to happen in the 'old days'. In my announcement post I talked about how new versions of software like Blitz Basic were renamed to Blitz 3D, and so on. The different names helped developers to instantly know what version they were using. However, it doesn't work like this in the open source and web world. A very good case in point is the PHP framework Laravel. Version 6 of Laravel was released just last week and guess what? It's not backward compatible. You don't have to change things much to get your old apps to run, but you do have to change them. And the older the version of Laravel you're using, the bigger the changes you have to make. They actually publish a roadmap, so you can see when the version you're using is going to be expired. First, they will stop fixing any bugs in that version, and then they'll stop adding security patches. You can clearly see when the version you're using is end-of-life.
Laravel is not unique in this approach. Webpack is another good example of a massively popular open-source project that has seismic shifts in the way it works from major version to major version. While I agree it's a bit easier to update your build tool configs from one version to another, than to recode an entire game, it's still a royal pain in the ass! Especially if you use a lot of plugins and have to wait for them to be updated too. What I'm trying to get at is that this is extremely common in the web world.
There are thousands upon thousands of online tutorials dedicated to Laravel and Webpack. Indeed, whole companies are built around offering learning courses for them, and they manage to cope with a change in version number. So, there's no reason to believe Phaser developers and tutorial authors are any less capable of doing so.
Will I have to start again from scratch?
I touched on this in my announcement post - that I believe Phaser became popular because it was so easy to use. Knowledge transfer from one version to another is an important benefit. No-one wants to have to reset themselves back to square one. From the initial development work I've done on Phaser 4, I don't believe they will have to, either. The move to lots of singular modules means it's entirely possible to assemble bundles that emulate the original API of Phaser 3, right down to how you explore the namespace. You'll see this demonstrated in the video below, where I'll discuss it in more depth.
It's all about the Workflow
Amazingly, the hardest part of starting out on Phaser 4 wasn't the coding, it was the build workflow. I knew from the start I wanted to use TypeScript, but I also knew that I needed to support a variety of different output formats. A UMD bundle for those who like script tags, an easy to consume singular ES6 module and also lots of focused packages for those wanting to pull in specific parts of the API for their own use.
I won't bore you with all the gory details, suffice to say it took me literally days and days to finally settle on a structure and suite of build tools that just works. I had to try so many different approaches but what I've picked I'm now very happy with.
I registered an organisation on npm, so I now have the @phaserjs scope for packages. Within here, I'm creating unique packages for each part of the API. For example @phaserjs/device contains all of the modules for the Device API. If you were to pull that one package into your project, you could do:
And it will give you context insight into the 4 main modules within the package. Picking one will expand that out further:
Of course, you can import just one specific module if you wish:
Note how VS Code is giving you full code-insight into what the isChrome() function returns? This screen shot doesn't come from a TypeScript project, it's just plain JavaScript, but VS Code (and, I'd hope, other IDEs too) is capable of working with the TypeScript definitions and Source Map that the Phaser 4 packages export to give you all of this information.
Insight like this really comes into its own with the JSDocs. Here I create a new Circle instance:
You can instantly see what the parameters are and what type they expect. This insight is free just from the fact I'm using TypeScript to author Phaser 4. However, if a class has JSDocs, like Circle does in the image above, you get to see those, too.
I've also been using Interfaces and Generics where it makes sense to do so. For example, the Area function will take a Circle object and return its area. So, we could pass it our hitArea circle like so:
And it will be perfectly happy with this. However, the function will accept any Circle-like object, too. As long as that object conforms to the expected interface then the Area function will happily work on it. In this case, the object needs to contain x, y, radius and diameter properties, allowing us to pass in an object like this:
It works because our fakeCircle adheres to the ICircle interface. Sure, it may have other properties in it too, but it has what the Area function needs, and that is what's important.
None of this is new-stuff. VS Code and TypeScript have been able to do it for a good while now. But it hasn't been possible with Phaser before. Even with the TypeScript defs that Phaser provides you just didn't have this granularity of information. With the large 'single library' structure of v3 you didn't have this level of package control, either. If I compile the above example it will tree-shake properly and export only exactly what I'm using. The final bundle is literally just this:
It's 148 bytes in size.
Granted, it's a slightly contrived example, but it doesn't change the fact that this was never possible before. With Phaser 3 I have to carefully (and sometimes, not so carefully!) consider every possible addition to see if it will have a significant impact on the library size, or not. Using this approach I no longer have to worry. What's more, each package is versioned. If I want to drop a new function into the Geometry Circle package, I can do that without worrying about it. If I want to drop in an entirely new package, say a brand new Game Object, I can also do that without worrying about the impact, and devs can start using it immediately without needing to wait for a new release first.
This doesn't mean you'll have to create your games like this, though.
I have already created a Phaser bundle, which pulls together all of the new @phaserjs scoped packages, that I have converted so far, and exposes them under a single Phaser object with the exact same name-space mapping as you've been used to in Phaser 2 and 3. This means that you could use the bundle and retain a 'traditional' way of working with Phaser. All namespaces and stuff intact. And, it will still tree-shake properly, too!
What is most important, however, is that you no longer need to use this approach either. You can suck in whatever little part of the library you need and use just that. You can use a proper modular approach in the way you build your project. Be it a game, or anything else. This benefit, alone, makes the update to Phaser 4 worth every moment I spend on it.
Perhaps it's better demonstrated via a video.
Here is a screen recording I made showing the two different approaches in action, and the resulting bundles:
https://www.dropbox.com/s/co89th1g3351qkh/Phaser%204%20IDE%20Test%201.mp4?dl=0
Go full-screen to see all the details!
Will the API change much?
This is a very valid question and the biggest voiced concern.
The answer is: Yes, where it needs to, but I'll keep the changes as logical as possible.
For example, in the Circle test I showed earlier I created a Circle class object. In Phaser 3 the Circle class has a method called 'contains'. This method takes a coordinate and tells you if it's within the circle or not. It works by calling an external function: Circle.Contains, which is also available separately if you wish to use it directly.
In Phaser 4, Circle.contains will no longer exist as a method on the Circle class. Instead, you'll have to use the function directly and pass your circle instance to it. The reason for this is so I can decouple the Contains function from the Circle class. Because what if you want to use Circles in your game, but you never once need to test if something is within a Circle or not? The method is useless to it, but it will still be packaged into your game, because no JavaScript module tree shaking in the world is yet capable of remove unused methods from within classes. Which is a real shame! But that's just how things work right now.
My guess is that deep-scope analysis isn't really standard for a couple of reasons: First, is that the way modules are meant to work is that they do one thing, and do it well. You're supposed, and encouraged, to keep dependencies to a minimum. Most tree-shaking systems work on this assumption, that it can safely prune out things on a module level, but never any deeper than this. I would hope that, in the future, tools like the TypeScript compiler, or Babel, are capable of performing proper dead-code-elimination, if used on your whole project end-to-end. As it stands today, that isn't the case.
It's not all bad, because we know that engines like V8 optimize massively for this already, so they'll effectively 'clean-up' your code for you, restructuring hot functions and so on. The fact this happens at runtime isn't a good enough reason for me to not change the API, though. Bundle size is massively important and I don't want anything in there that shouldn't be. So yes, the API will change in ways like this. You'll see less capabilities tacked onto classes and more need to pass your objects to other functions, but I will keep the naming the same and the general location of the functions logical, too, so it will never be much of a cognitive step to get to where you want to be.
Add to this another important fact: If you don't like this, it's now trivial to create your own bundles that re-create the structure you had before. You could easily create a class that extends Circle and adds in those 'missing' methods, because the functions will still all exist.
I believe it is this flexibility that makes the whole Phaser 4 endeavour worth while. It loops right back to my original vision for Phaser 3 / Lazer. Honestly, I should have carried on that path years ago, but hindsight is a wonderful thing. Let's fix that mistake now and come out the other end significantly stronger as a result.
Before I start I just wanted to say that I've not forgotten about the September Backers Pack! It will be out very shortly, keep an eye out for it soon :)
Hi all,
I just wanted to spend some time updating you on what's been happening with Phaser 4 development last week. Since making my (very long!) announcement post I had a lot of feedback from you. The overwhelming majority of feedback was positive. Most of you agreed it was right to update Phaser to use a modern web development approach and that needed to include refactoring the API. However, some of you raised legitimate concerns, too, so I'll do my best to address these before I start showing what I've been up to.
Could you rename Phaser to avoid confusion?
I can see where the idea is coming from. The move from v2 to v3 caused confusion in the way the API had changed. Some devs get confused about if a tutorial is meant for v3 or not. Also, from a product point of view (i.e. companies selling tutorial videos and courses) it would make their life easier.
This is what used to happen in the 'old days'. In my announcement post I talked about how new versions of software like Blitz Basic were renamed to Blitz 3D, and so on. The different names helped developers to instantly know what version they were using. However, it doesn't work like this in the open source and web world. A very good case in point is the PHP framework Laravel. Version 6 of Laravel was released just last week and guess what? It's not backward compatible. You don't have to change things much to get your old apps to run, but you do have to change them. And the older the version of Laravel you're using, the bigger the changes you have to make. They actually publish a roadmap, so you can see when the version you're using is going to be expired. First, they will stop fixing any bugs in that version, and then they'll stop adding security patches. You can clearly see when the version you're using is end-of-life.
Laravel is not unique in this approach. Webpack is another good example of a massively popular open-source project that has seismic shifts in the way it works from major version to major version. While I agree it's a bit easier to update your build tool configs from one version to another, than to recode an entire game, it's still a royal pain in the ass! Especially if you use a lot of plugins and have to wait for them to be updated too. What I'm trying to get at is that this is extremely common in the web world.
There are thousands upon thousands of online tutorials dedicated to Laravel and Webpack. Indeed, whole companies are built around offering learning courses for them, and they manage to cope with a change in version number. So, there's no reason to believe Phaser developers and tutorial authors are any less capable of doing so.
Will I have to start again from scratch?
I touched on this in my announcement post - that I believe Phaser became popular because it was so easy to use. Knowledge transfer from one version to another is an important benefit. No-one wants to have to reset themselves back to square one. From the initial development work I've done on Phaser 4, I don't believe they will have to, either. The move to lots of singular modules means it's entirely possible to assemble bundles that emulate the original API of Phaser 3, right down to how you explore the namespace. You'll see this demonstrated in the video below, where I'll discuss it in more depth.
It's all about the Workflow
Amazingly, the hardest part of starting out on Phaser 4 wasn't the coding, it was the build workflow. I knew from the start I wanted to use TypeScript, but I also knew that I needed to support a variety of different output formats. A UMD bundle for those who like script tags, an easy to consume singular ES6 module and also lots of focused packages for those wanting to pull in specific parts of the API for their own use.
I won't bore you with all the gory details, suffice to say it took me literally days and days to finally settle on a structure and suite of build tools that just works. I had to try so many different approaches but what I've picked I'm now very happy with.
I registered an organisation on npm, so I now have the @phaserjs scope for packages. Within here, I'm creating unique packages for each part of the API. For example @phaserjs/device contains all of the modules for the Device API. If you were to pull that one package into your project, you could do:
And it will give you context insight into the 4 main modules within the package. Picking one will expand that out further:
Of course, you can import just one specific module if you wish:
Note how VS Code is giving you full code-insight into what the isChrome() function returns? This screen shot doesn't come from a TypeScript project, it's just plain JavaScript, but VS Code (and, I'd hope, other IDEs too) is capable of working with the TypeScript definitions and Source Map that the Phaser 4 packages export to give you all of this information.
Insight like this really comes into its own with the JSDocs. Here I create a new Circle instance:
You can instantly see what the parameters are and what type they expect. This insight is free just from the fact I'm using TypeScript to author Phaser 4. However, if a class has JSDocs, like Circle does in the image above, you get to see those, too.
I've also been using Interfaces and Generics where it makes sense to do so. For example, the Area function will take a Circle object and return its area. So, we could pass it our hitArea circle like so:
And it will be perfectly happy with this. However, the function will accept any Circle-like object, too. As long as that object conforms to the expected interface then the Area function will happily work on it. In this case, the object needs to contain x, y, radius and diameter properties, allowing us to pass in an object like this:
It works because our fakeCircle adheres to the ICircle interface. Sure, it may have other properties in it too, but it has what the Area function needs, and that is what's important.
None of this is new-stuff. VS Code and TypeScript have been able to do it for a good while now. But it hasn't been possible with Phaser before. Even with the TypeScript defs that Phaser provides you just didn't have this granularity of information. With the large 'single library' structure of v3 you didn't have this level of package control, either. If I compile the above example it will tree-shake properly and export only exactly what I'm using. The final bundle is literally just this:
It's 148 bytes in size.
Granted, it's a slightly contrived example, but it doesn't change the fact that this was never possible before. With Phaser 3 I have to carefully (and sometimes, not so carefully!) consider every possible addition to see if it will have a significant impact on the library size, or not. Using this approach I no longer have to worry. What's more, each package is versioned. If I want to drop a new function into the Geometry Circle package, I can do that without worrying about it. If I want to drop in an entirely new package, say a brand new Game Object, I can also do that without worrying about the impact, and devs can start using it immediately without needing to wait for a new release first.
This doesn't mean you'll have to create your games like this, though.
I have already created a Phaser bundle, which pulls together all of the new @phaserjs scoped packages, that I have converted so far, and exposes them under a single Phaser object with the exact same name-space mapping as you've been used to in Phaser 2 and 3. This means that you could use the bundle and retain a 'traditional' way of working with Phaser. All namespaces and stuff intact. And, it will still tree-shake properly, too!
What is most important, however, is that you no longer need to use this approach either. You can suck in whatever little part of the library you need and use just that. You can use a proper modular approach in the way you build your project. Be it a game, or anything else. This benefit, alone, makes the update to Phaser 4 worth every moment I spend on it.
Perhaps it's better demonstrated via a video.
Here is a screen recording I made showing the two different approaches in action, and the resulting bundles:
https://www.dropbox.com/s/co89th1g3351qkh/Phaser%204%20IDE%20Test%201.mp4?dl=0
Go full-screen to see all the details!
Will the API change much?
This is a very valid question and the biggest voiced concern.
The answer is: Yes, where it needs to, but I'll keep the changes as logical as possible.
For example, in the Circle test I showed earlier I created a Circle class object. In Phaser 3 the Circle class has a method called 'contains'. This method takes a coordinate and tells you if it's within the circle or not. It works by calling an external function: Circle.Contains, which is also available separately if you wish to use it directly.
In Phaser 4, Circle.contains will no longer exist as a method on the Circle class. Instead, you'll have to use the function directly and pass your circle instance to it. The reason for this is so I can decouple the Contains function from the Circle class. Because what if you want to use Circles in your game, but you never once need to test if something is within a Circle or not? The method is useless to it, but it will still be packaged into your game, because no JavaScript module tree shaking in the world is yet capable of remove unused methods from within classes. Which is a real shame! But that's just how things work right now.
My guess is that deep-scope analysis isn't really standard for a couple of reasons: First, is that the way modules are meant to work is that they do one thing, and do it well. You're supposed, and encouraged, to keep dependencies to a minimum. Most tree-shaking systems work on this assumption, that it can safely prune out things on a module level, but never any deeper than this. I would hope that, in the future, tools like the TypeScript compiler, or Babel, are capable of performing proper dead-code-elimination, if used on your whole project end-to-end. As it stands today, that isn't the case.
It's not all bad, because we know that engines like V8 optimize massively for this already, so they'll effectively 'clean-up' your code for you, restructuring hot functions and so on. The fact this happens at runtime isn't a good enough reason for me to not change the API, though. Bundle size is massively important and I don't want anything in there that shouldn't be. So yes, the API will change in ways like this. You'll see less capabilities tacked onto classes and more need to pass your objects to other functions, but I will keep the naming the same and the general location of the functions logical, too, so it will never be much of a cognitive step to get to where you want to be.
Add to this another important fact: If you don't like this, it's now trivial to create your own bundles that re-create the structure you had before. You could easily create a class that extends Circle and adds in those 'missing' methods, because the functions will still all exist.
I believe it is this flexibility that makes the whole Phaser 4 endeavour worth while. It loops right back to my original vision for Phaser 3 / Lazer. Honestly, I should have carried on that path years ago, but hindsight is a wonderful thing. Let's fix that mistake now and come out the other end significantly stronger as a result.