Babylon.js 89,603 ships two package families from the same code: tree-shakable ES modules under the @babylonjs scope (core, gui, loaders, inspector...), which the documentation recommends for applications, and single-file UMD packages (babylonjs, babylonjs-gui...) that define a global BABYLON (Script Tag). Install with npm 2,036 install @babylonjs/core@9.28.0 (MERN Stack Development covers npm and bundlers) and import each class from its own file:
import { Engine } from '@babylonjs/core/Engines/engine.js';
import { Scene } from '@babylonjs/core/scene.js';
import { ArcRotateCamera } from '@babylonjs/core/Cameras/arcRotateCamera.js';
import { HemisphericLight } from '@babylonjs/core/Lights/hemisphericLight.js';
import { Vector3 } from '@babylonjs/core/Maths/math.vector.js';
import { CreateBox } from '@babylonjs/core/Meshes/Builders/boxBuilder.js';
import '@babylonjs/core/Materials/standardMaterial.js'; // side effect: default material
const engine = new Engine(document.querySelector('canvas'), true);
const scene = new Scene(engine);
new ArcRotateCamera('camera', 1, 1.2, 4, Vector3.Zero(), scene);
new HemisphericLight('sky', new Vector3(0, 1, 0), scene);
CreateBox('book', { width: 1, height: 1.5, depth: 0.25 }, scene);
engine.runRenderLoop(() => scene.render());Some modules register features through side effects: without the last import, CreateBox finds no default material and the page throws "StandardMaterial needs to be imported before as it contains a side-effect required by your code". Bundled with esbuild 0.28.2 126 (--bundle --minify --format=esm), the scene rendered in Chrome 1 from a 1,516,036-byte file (351,593 bytes after gzip -9). The same scene importing from the package root came to 7,495,382 bytes (1,688,178 gzipped), barely less than the whole engine through @babylonjs/core/Legacy/legacy.js, 8,232,435 bytes (1,863,550): the root index re-exports every module, and their side effects keep the bundler from dropping them.
<!doctype html>
<style>
body { margin: 0; background: #f7f3ec; }
canvas { width: 100%; max-width: 600px; height: 380px; display: block; touch-action: none; }
</style>
<canvas id="view"></canvas>
<!--
In a project you would `npm install @babylonjs/core@9.28.0` and import each class from its
own file, e.g. import { CreateBox } from '@babylonjs/core/Meshes/Builders/boxBuilder.js',
then bundle. Loading those ES modules unbundled from a CDN means hundreds of requests, so this
preview uses the UMD package (global BABYLON) to chart what the bundler produced.
-->
<script src="https://cdn.jsdelivr.net/npm/babylonjs@9.28.0/babylon.js"></script>
<script src="https://cdn.jsdelivr.net/npm/babylonjs-gui@9.28.0/babylon.gui.min.js"></script>
<script>
const canvas = document.getElementById('view');
const engine = new BABYLON.Engine(canvas, true);
const scene = new BABYLON.Scene(engine);
scene.clearColor = BABYLON.Color4.FromHexString('#f7f3ecff');
const camera = new BABYLON.ArcRotateCamera('cam', -Math.PI / 2 + 0.25, 1.25, 12,
new BABYLON.Vector3(0, 2, 0), scene);
camera.attachControl(canvas, true);
new BABYLON.HemisphericLight('sky', new BABYLON.Vector3(0.4, 1, -0.7), scene);
const ui = BABYLON.GUI.AdvancedDynamicTexture.CreateFullscreenUI('ui');
// esbuild --bundle --minify --format=esm, bytes before and after gzip -9
const BUNDLES = [
['per-file imports', 1516036, 351593, '#3f7d3a'],
['@babylonjs/core root', 7495382, 1688178, '#e09a10'],
['Legacy/legacy.js', 8232435, 1863550, '#b5452f'],
];
const MB = 1e6, SCALE = 0.5; // half a metre per megabyte
const paint = (hex, alpha = 1) => {
const m = new BABYLON.StandardMaterial(hex + alpha, scene);
m.diffuseColor = BABYLON.Color3.FromHexString(hex);
m.alpha = alpha;
return m;
};
BUNDLES.forEach(([name, raw, gzip, hex], i) => {
const x = (i - 1) * 3.2;
const rawHeight = raw / MB * SCALE, gzHeight = gzip / MB * SCALE;
const rawBar = BABYLON.MeshBuilder.CreateBox(name, { width: 1.4, depth: 1.4, height: rawHeight }, scene);
rawBar.position.set(x, rawHeight / 2, 0);
rawBar.material = paint(hex, 0.35); // translucent: the minified size
const gzBar = BABYLON.MeshBuilder.CreateBox(name + 'gz', { width: 0.9, depth: 0.9, height: gzHeight }, scene);
gzBar.position.set(x, gzHeight / 2, 0);
gzBar.material = paint(hex); // solid: after gzip
tag(rawBar, `${name}\n${(raw / MB).toFixed(2)} MB · ${(gzip / MB).toFixed(2)} MB gz`, -rawHeight * 17 - 24);
});
const note = new BABYLON.GUI.TextBlock('note',
'Root imports re-export every module, and their side effects stop tree shaking.\n' +
"Side-effect import needed: '@babylonjs/core/Materials/standardMaterial.js'");
Object.assign(note, { fontSize: 12, color: '#444', height: '40px', top: '-6px',
verticalAlignment: BABYLON.GUI.Control.VERTICAL_ALIGNMENT_BOTTOM });
ui.addControl(note);
function tag(mesh, text, offsetY) {
const block = new BABYLON.GUI.TextBlock('', text);
Object.assign(block, { color: '#222', fontSize: 12, fontWeight: 'bold', resizeToFit: true,
outlineWidth: 3, outlineColor: '#f7f3ec' });
ui.addControl(block);
block.linkWithMesh(mesh);
block.linkOffsetY = offsetY;
}
engine.runRenderLoop(() => scene.render());
addEventListener('resize', () => engine.resize());
</script>