Porting a Project from Concat to Webpack!
Last year, I started work on Aspect Star 4. I used this as an opportunity to switch from my older “engine” (which I tend to affectionately call “master.js” because most of the non-game-specific stuff lives there) to a newer system using TypeScript and the PIXI.js engine– and after making a basic, “jump around a level and change aspects”, I stopped for awhile. Now I’m picking it up again, but it’s structured a bit ugly– how can we make this a little easier to code for?
What’s wrong with it?
The program is overall, I’d say, well written. Everything is typed except for the “Worldfile” that contains the levels and the object dictionary, and TypeScript doesn’t throw any errors that need to be ignored. Everything is strict mode, and it’s much cleaner than say, Space Ava. A custom build step compiles the Terraformer-formatted images into something the browser can use, and tsc handles concatenating all the files together. The build system has no local dev server, so I have to deal with all the downsides of running JS off the file:///
protocol. (Easier in Firefox than Chrome, thankfully)
Another thing that changed since I wrote it is that I changed my day job from a Java coder to a frontend developer, working with what seems to become the standard “modern JS stack” of React and Webpack. It certainly isn’t the most lightweight stack, but it’s very easy to code with– and, most importantly, it features a full and proper module system. Files are self-contained, and everything is explicitly exported. Very nice! Why can’t I have that in Aspect Star 4?
Concatenation is the big problem here. Every file needs to be added manually, and there’s no way to use modules with flat JS; everything lives in one big, ugly global namespace. This is exactly how every “master.js” game worked too, but since they were pure js, that had the advantage of needing no build step. (The compilation of graphics assets was handled by the level editor in that case, which always felt like a bit of a hack and it wasn’t something I wanted to return to)
I also wanted to take advantage of lazy loading. The fact is, a Javascript game having a long loading time isn’t that shocking; music assets are going to be heavier than anything else. Nevertheless, it seemed like a useful capability, and there was no reason not to have it. (Plus I just wanted to play with it; we haven’t used it at work yet) But between Javascript modules and lazy loading, we’re nearing features whose browser implementations are experimental. It was finally time to give up my aversion to a packager.
Getting started
Aspect Star 4 was not like most Javascript projects that people seem to make these days. Like all prior Nicole Express games, it didn’t even use any package manager. Does your tutorial start by assuming package.json
exists? Then it’s not going to work here.
So I decided that the best idea was to start fresh. I moved ~/Projects/aspectstar4
to ~/Project/aspectstar4old/
, and initiated a fresh git repo and a new shiny yarn project. So modern! Integrating TypeScript into Webpack is also a solved problem, and meant I could finally say goodbye to having to manually add every file in the correct order into my tsconfig.json
. My existing index.html required very little tweaking to adapt to HtmlWebpackPlugin
(basically just removing all the manual script imports)
At that point, it was just a matter of slowly porting code over a file at a time. Static typing really shines here; it was a simple process of finding the errors, adding the imports to correct the errors, and continuing onwards. I also took advantage of the process to rearrange the code and break things up into multiple files.
Problems with PIXI
One problem I had was with PIXI.js. It doesn’t exactly play nice with the module system; instead, it expects to be a global variable. Even import PIXI from 'pixi-js'
doesn’t work like you might expect. Initially, I peppered every file that used it with import * as PIXI from 'pixi.js'
, but eventually I realized (in this case, because of the lack of Typescript errors) that it wasn’t necessary; just importing it once populated the global variable. So I cleaned that right up. One import in my index.ts
was all that was necessary here.
PIXI is a heavy package; initially I was concerned about the size of my JS bundle compared to my older tsc
-produced one… until I realized that my old bundle didn’t include the library, it was instead loaded by a separate script tag. But right now that seems like something I’ll just have to live with. WebGL doesn’t come for free!
The graphics
While I was porting the code over to modules, I just copied pre-compiled graphics into my static HTML folder. But I really prefer to use Terraformer, my own graphics editor, and had gotten used to having convenient integration between my code build and graphics build. But the problem here is that Terraformer is a thing of my creation; to my understanding, no one else even uses it. So in all of npm
, there was no way there was a package that would help.
But on the other hand, I had already integrated Terraformer into a build process. I had the Python script already written. And wouldn’t you know, WebpackShellPlugin
can run that script before it builds, thus getting all the benefits. Eventually, I’ll probably hook up a null module to *.terra
files so the autobuild can trigger when those files change, but right now this is already looking pretty good. (I’m often working on the code and graphics at the same time)
And there we have it!
I spent the better part of an afternoon on this, and to show for it all I have is a program that, objectively, does exactly the same thing as my old program did, and uses more JS to do it. But as a hobby game developer, I think the benefits of the code being easier to work with and understand will definitely be worth it. Let’s see how it goes!