npm Packages

Installing the Modular @babylonjs Packages from npm

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:

main.js: a tree-shakable Babylon.js sceneJavaScript
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.

Bundle size of the same one-box scene with per-file imports, root imports and the legacy entryHTMLLive
<!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>