Packing and Shipping

Babylon.js
7 min readJan 14, 2021

Just bundle them together!

Packaging Babylon.js was always an interesting task for the team. In the early days of the framework the common way of consuming a JavaScript framework was to load it in a script tag in your HTML and populate a global namespace (usually on the window object). This is how all major frameworks used to work — and probably still do. The most known example was (and I guess still is?) jQuery, that was always available using the `$` variable(and the jQuery object, of course). So — load the script, have it do its magic in your browser, and get the set of tools with which you can work.

Babylon.js was no different. Written initially in JavaScript (and later in an early version of TypeScript), the first versions of the framework were populating the BABYLON namespace with Babylon.js classes. Who needs npm, imports, requires, bundlers? Just take the entire codebase, compile it, merge all files together, and voilà (literally) — you have a package ready to be used in your browser. Just upload it to github (to the ‘dist’ folder of course), and have your users download it and host it wherever they want.

This approach had a very clear upside — ease of use. Adding a script to your head (or later to the body) tag was a common practice, and websites were not (yet) measured by the amount of bandwidth they use. Mobile surfing (and 3D in particular) was still in its infancy. And flash was still a thing, so long waits and loading screens were common practice.

But if you miss the good ol’ days, don’t. This approach had many downsides. As much as it was easy to use, the package grew larger and larger over time. This influenced both development (the bigger the code base, the harder it is to maintain) and the users themselves. Internal dependency management was also harder to maintain and attempts to provide a way to minimize the package size were only partly successful. This way of providing the package also made it hard (thou not impossible) to include Babylon.js in build processes. It was also hard to use Babylon.js in any other environment other than the browser. Though Babylon.js is clearly a front end 3D tool, there are use cases for offline/server work (Server-based null-engine based on node.js, for example).

AMD, CommonJS, npm and everything in between.

The first version of Babylon.js to be included in npm was 1.9 as you can see on npmjs.com — https://www.npmjs.com/package/babylonjs?activeTab=versions . If you don’t know npm you should, and if you do, you know that you use it already. What we did was — deploy the bundle to npm as it is, to help developers include Babylon.js in their build tool. It wasn’t even fully minified! Simpler times…

In version 3 we introduced module support using CommonJS (which was/is used by node.js). It was a hybrid solution — we forced the namespace update on window and checked if module was defined. If it was, we returned the global namespace we created. That was common practice back then (to be honest, it was already a bit of an old practice, but one that was accepted).

In future versions we moved to UMD (Universal Module Definition). The main difference here was the introduction of AMD (i.e. RequireJS) to the equation. The nice thing about UMD is that it lets the developer decide which way they want to consume the package. On the one hand they can still use the “old school” global name-spacing method (which is the one they have been surely using for years), or consume the package using some form of module-management system. The latter allowed for much better code structure. You could, of course, import the entire namespace, but the better way to work and organize your code was to take only that what you needed.

The good thing about our UMD package is the support of one of Babylon.js’ cornerstones. Backwards compatibility could be maintained. Namespaces worked, packaged could still be included in a script tag. Everybody’s happy.

Then along comes ES6

ES5 had been here for a long time. ES6, its successor, was introduced back in 2015, but as always, adoption took and still takes a long time. One of the great things ES6 brought us is native module support (yes, also directly in the browser) and the `import` syntax which was a major improvement (IMO) over CommonJS’s `require` syntax. Better dependency management, better syntax, better code structure. And with it, one of its wonderful side-effects — take only what you need. No need to consume the entire Babylon.js package if you just want to show a sphere in a skybox. Welcome tree shaking!

Babylon.js’s codebase has gradually moved from an older namespace-based syntax to a modern import-based syntax. A main issue with that was backwards compatibility. A future-safe architecture is always a hard task and apparently the older versions of Babylon.js were not entirely that. In some cases, we had circular dependencies. In some cases, we had side-effect (code running when the script is loaded), which is discouraged when using (pure) es6 modules.

The decisions made during this development were:

1) We will keep our UMD package as is and continue building and serving it.

2) Our ES6 modules will not be pure modules and will contain side-effects.

3) We will have a new npm namespace for es6-packages only.

4) Some side-effect imports will be needed in order to achieve true tree shaking.

5) You will not be able to mix the two types of packages. You either use UMD or ES6.

The UMD packages (main one being babylonjs — npm (npmjs.com)) are documented here — npm Support | Babylon.js Documentation. The ES6 packages are documented in the es6 support page.

What should you use?

Well, as always, that depends. UMD is still faster for rapid prototyping. The Babylon.js playground, for example, still uses the UMD packages (and the global BABYLON namespace). On the other hand, if you start a new project and want to bring a bit of structure, better architecture and smaller package size, I always recommend ES6.

UMD “just works”, mainly because it is still using the most basic browser functionalities. It also guaranteed to work on any browser with WebGL support (yes IE11, I am looking at Safari). Don’t want to use npm? You don’t have to. ES6 on the other hand has a few hurdles that need to be jumped over just to get it to work. You will probably need a bundler. You will probably need a (custom) build process. You will probably need to compile it to ES5 for cross-browser support.

ES6 does not populate the global namespace, so no more BABYLON.* support. This might cause (your) legacy work to stop working, or might present some compatibility issues with our documentation website, which, for the sake of consistency, mostly use the BABYLON namespace in code examples. So if you want to just copy examples from the doc page without the need to (sometimes) change them, use UMD.

But whatever you do, choose one of them. Most of the time, when we have a forum question about build issues, it is because of mixing the two module flavors.

Example please!

My favorite stack (and I know this is a very personal) is:

  • Babylon.js ES6
  • Webpack — IMO the most flexible bundler at the moment.
  • TypeScript — such a wonderful frontend language!

To get you started I have created an example Babylon,js bootstrap project. This project uses the latest “everything” (as I just updated the dependencies :-)) from the stack I just mentioned. It uses the latest Babylon.js 5.0 alpha ES6 modules, uses the latest TypeScript version, and uses Webpack 5.0, which brought some wonderful improvements.

It is not entirely a “getting started” project, though you could use it as such. There are a few separated examples there to show that anything is possible if you configure your bundler correctly — loading digital assets (images and models), loading an external package (ammo.js), rendering simple scenes with a small file size, and using more than one Babylon.js package. If any example is missing or something is unclear, just raise an issue on github!

You might ask why you need a bundler like webpack. One main reason is browser support. Webpack can “lower” the syntax to ES5 so that your application will be supported by any browser. Webpack production build pipeline optimizes your code and is the one responsible for tree shaking. Using async loading (Lazy Loading | webpack) you can reduce the time it takes your page to start rendering, which will benefit you and your users on many levels. There are many bundlers out there. Rollup comes to mind, being es6-oriented bundler. Try different ones, see what suits your needs.

The future?

There is always room for improvements. The technology keeps on progressing, and we have to progress with it as well. And though Dolores Umbridge (and we always listen to Dolores) once said “Progress for the sake of progress must be discouraged”, sometimes you need to stay ahead, otherwise you will be left behind. If the framework cannot be consumed, we must fix it. If there is a better way of doing things, we can investigate and see if we can support it. Of course while keeping the framework backwards compatible and highly performant. Good luck with that!

And as always contact me or anyone else from the team if you have any questions!

My Twitter, my Github.com, my OnlyFans

--

--

Babylon.js

Babylon.js: Powerful, Beautiful, Simple, Open — Web-Based 3D At Its Best. https://www.babylonjs.com/