How to Vite with Babylon.js (Game Tutorial Series)

NOTE: A more general-purpose (and beginner-friendly) version of this guide is now available on the Babylon Docs!

Hi! Are you allergic to Webpack like I am? Do you hate dealing with its clunky config files? Thankfully the 2020s have given us Vite, which is simpler to use. But if you’re following the Create a Game Tutorial Series like I am, you’re probably wondering how to use it with Babylon.

This guide is intended to replace the Getting Set Up chapter in the tutorial series, but you should read Getting Started first if you haven’t used Babylon.js before.

  1. Create a folder for your code project(s) and open it with a code editor like Visual Studio Code (or VSCodium.)

  2. Install npm (either through here or your package manager if you’re running Linux).

  3. Close and reopen your code editor to the same folder and then run each of these commands in the Terminal at the bottom after you do that:
    npm i vite (i is short for install)
    npm i -D @babylonjs/core (-D is short for --save-dev)
    npm i -D @babylonjs/inspector
    npm i -D @babylonjs/gui

  4. Now run npm init vite and give your project any name you want, select “Vanilla,” and then select “TypeScript.” A project folder with the name you chose should now appear on the left-hand side. Feel free to rename this folder if you’d like.

(NOTE: The reason why we didn’t install the babylonjs packages inside the actual project folder that Vite just created is because it saves on space. Why install another copy of babylonjs in every project folder, when you can install it in the root folder above all your future game projects? You can use npm packages installed in folders ABOVE your working directory, not just in the same folder as your code.)

  1. Right-click this folder and click “Open in Integrated Terminal.” You should now have a second terminal window open that lists the name of your new generated project folder. Run npm i and then npm run dev. If you ctrl+click the localhost: link in the terminal, you should be taken to a functioning test page within your browser:

  1. Go back to your code editor and press Ctrl+C in the Terminal to stop Vite from running. Using the left-hand side bar, do the following:
  • Delete everything inside the src and public folders
  • Inside the src folder, create a file called app.ts
  • Double-click index.html and tsconfig.json to open them

image
*ignore the dist folder

  1. Open index.html, paste in the following, and save:
<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title>Title of Your Project</title>
    </head>
    <body>
       <script type="module" src="./src/app.ts"></script>
    </body>
</html>

(This is the same code as the lesson but with the addition of a script tag to indicate our primary TS file.)

  1. Open app.ts, paste in the following, and save:
import "@babylonjs/core/Debug/debugLayer";
import "@babylonjs/inspector";
import "@babylonjs/loaders/glTF";
import { Engine, Scene, ArcRotateCamera, Vector3, HemisphericLight, Mesh, MeshBuilder } from "@babylonjs/core";

class App {
    constructor() {
        // create the canvas html element and attach it to the webpage
        var canvas = document.createElement("canvas");
        canvas.style.width = "100%";
        canvas.style.height = "100%";
        canvas.id = "gameCanvas";
        document.body.appendChild(canvas);

        // initialize babylon scene and engine
        var engine = new Engine(canvas, true);
        var scene = new Scene(engine);

        var camera: ArcRotateCamera = new ArcRotateCamera("Camera", Math.PI / 2, Math.PI / 2, 2, Vector3.Zero(), scene);
        camera.attachControl(canvas, true);
        var light1: HemisphericLight = new HemisphericLight("light1", new Vector3(1, 1, 0), scene);
        var sphere: Mesh = MeshBuilder.CreateSphere("sphere", { diameter: 1 }, scene);

        // hide/show the Inspector
        window.addEventListener("keydown", (ev) => {
            // Shift+Ctrl+Alt+I
            if (ev.shiftKey && ev.ctrlKey && ev.altKey && ev.keyCode === 73) {
                if (scene.debugLayer.isVisible()) {
                    scene.debugLayer.hide();
                } else {
                    scene.debugLayer.show();
                }
            }
        });

        // run the main render loop
        engine.runRenderLoop(() => {
            scene.render();
        });
    }
}
new App();

(This code is unaltered from the original lesson. Take note of how the import statements allow the rest of the code to work, as well as how this structure should be divided into functions and class variables as you progress.)

  1. Open tsconfig.json, paste in the following, and save:
{
  "compilerOptions": {
    "target": "es6",
    "lib": [
      "dom",
      "es6"
    ],
    "useDefineForClassFields": true,
    "module": "ESNext",
    "rootDir": "src",
    "moduleResolution": "node",
    "resolveJsonModule": true,
    "noResolve": false,
    "sourceMap": true,
    "noEmit": true,
    "preserveConstEnums": true,
    "isolatedModules": true,
    "esModuleInterop": true,
    "noImplicitAny": false,
    "noUnusedLocals": false,
    "noUnusedParameters": true,
    "noImplicitReturns": true,
    "skipLibCheck": true
  },
  "include": [
    "src"
  ]
}

(This tsconfig.json file is a mix between the one provided in the tutorial and the one generated by Vite. I wrote a more efficient version of this file for the Babylon Docs version of this tutorial, but this works fine too. You can read more about how this file works in the TypeScript docs.)

  1. Run npm run dev in that second terminal and ctrl+click the localhost: link again to display it in your browser… and be amazed!

And just like before, you can press Ctrl+C in the terminal window to stop Vite from running.

Isn’t that so much cleaner to handle? Now you can turn your project into a Git repository if you want, and then move on to the next chapter. UPDATE: As you complete the rest of this tutorial, please keep the following in mind…

  • The lessons are a little unfinished- this series is more of an explanation of this game’s code rather than a step-by-step tutorial to make it. There’s also missing links to assets that you’ll need to grab yourself from the GitHub repo, such as textures or sound files. I was not able to run any code until I got to the Game GUI chapter that introduces ui.ts, and it was still crashing while looking for un-introduced sound files…

  • Static assets and their respective folders (such as images/models/sounds/etc.) still go in the public folder so that they are included in any compiled bundles you make. If you get assets that don’t load in for some reason, try adding ?url to the end of your code’s filepaths. Some uncommon file types (such as .glsl shader code) may need ?raw at the end to tell Vite to import the file as a string. This means /shaders/example.glsl would become /shaders/example.glsl?raw.

  • OPTIONAL NOTE: This technically goes against the Vite docs’ recommendation of making every static asset’s path into an import statement, but you’ll have to create a .d.ts file in your src folder and figure out this code snippet if you want to do it this way. There really isn’t a need to do this unless you want to obscure the filenames of your assets (which doesn’t prevent players from ripping assets from your game) and also have the browser cache these large files locally. Caching your assets may irritate some players of your game since it’ll take up extra space on their computers, but doing this could theoretically allow them to play an offline copy of your game. I haven’t tested this though.

  • ALSO OPTIONAL: If you really want to optimize your code and shrink your compiled bundle sizes, you can turn your import statements into dynamic ones. I haven’t tested if this works with Babylon.js yet either, but this video can help you get started if you want to explore that.

BUILDING AND SHARING ONLINE:

  • Once you reach the end of the tutorial series and address the extra notes above, you can build the final deployment of the game with npm run build and then test it with npm run preview. If everything works as intended, you can find the exported production code in the dist folder. BEFORE you share it online, be sure to open the index.html file and edit the "/assets/index.(...).js" string to add a period before the first slash. (Explanation for why this happens is in the next bullet point.) Now you can host online wherever you’d like!

  • Once you build and host this code online, you may have a weird glitch where lit lanterns have weird red glowing patterns instead of their usual yellow appearance. This is caused by a typo left by the original developer. To fix this, go to environment.ts, locate the "/textures/litLantern.png" string, and add a period in front of the first slash. (As you may have noticed, browsers can’t find folders in your root structure unless you have no preceding slash "like/this", or one with a period "./like/this", because "/doing/this" confuses the browser.)

  • If you’re unable to compile your code at all because of a leftover error, it’s probably because of res in app.ts, which can be resolved by adding a leading underscore to it like this: _res. I don’t know why TypeScript wants us to annotate the variable like this, but it works.

And that’s how you use Vite with Babylon. Enjoy!

5 Likes

This is amazing, wonder how we could surface those info in the doc @PirateJC ???

2 Likes

Whoa @wavetro this is awesome!

Any interest in writing this up as a more official doc for the Babylon.js Docs pages? Perhaps under “Guided Learning?”

2 Likes

I’d be willing to! Is there a GitHub repo for the docs that I should fork to rewrite this?

2 Likes

Yup! Here ya go!

1 Like

Thanks! I’ll write and submit a pull request for this once I complete the rest of the Create a Game Tutorial Series so I know for sure that Vite is compatible

Hi @PirateJC, I finished the tutorial series, but in order to compile the code for production, it looks like I have to store every asset’s filepath in a variable via an import statement. These import statements are how Vite locates and bundles the assets needed for the final compiled export.

This would mean someFunctionHere('./path/to/file.ext') would have to become

import exampleFile as './path/to/file.ext';

// and then later...

someFunctionHere(exampleFile);

Is it alright if I still do a write-up of this Vite workflow, but without referencing/connecting back to the Create a Game Tutorial Series? It’s easier to tell users to get into the habit of doing import varName as './path/to/file.ext' with whichever project they’re tackling next, instead of making them edit the existing Summer Festival game code to turn every asset filepath into an import statement.

2 Likes

Ah yes! I see. Yeah no problem at all. I still think it’d be great to have the content, even if it’s more general in nature and doesn’t point specifically to the Create A Game Tutorial Series!

Thanks for doing this @wavetro ! We LOVE to see people contribute to the greater community!

Cheers!

1 Like

Can you put the assets in the /public folder that you deleted and they’ll be served? Importing assets is going to increase bundle size and affect caching.

1 Like

I could try that, but isn’t it safer to bundle the assets so the filenames are hashed? Or does the extra security not matter

EDIT: Nevermind you’re right, any end user would be able to find and download the assets anyway. I’ll update the tutorial soon

1 Like

Thank you everyone! A more thorough version of this tutorial is now up on the Babylon Docs.

2 Likes

“But how do I do this with JavaScript instead of TypeScript?”

Whoops! I wrote my original guide thinking that only TypeScript would be the language of interest. Here’s how to do the same tutorial but with vanilla JS.

Open the same guide on the Babylon Docs and follow the instructions until you hit these specific steps:

  • For STEP 5, after you choose “Vanilla,” choose “JavaScript” instead of “TypeScript.”

  • For STEP 8, your screen should look like this:

  • For STEP 9, after pressing Ctrl + C in the terminal, use the left-hand sidebar (with some right-clicking) to do the following INSTEAD:
- Delete all the files that end in .js and .svg (including inside the `public` folder)
- Create a new file inside your project folder called `app.js`
- Double-click `index.html` so that it's open next to your `app.js` file
  • For STEP 10, copy in the following code for your new app.js file. The only real difference between this and the TypeScript version is the lack of type declarations on the camera, light, and sphere lines. Everything is otherwise the same, including the import statements that allow the rest of the code to work. You also don’t have to worry about tsconfig.json for vanilla JS.
import "@babylonjs/core/Debug/debugLayer";
import "@babylonjs/inspector";
import { Engine, Scene, ArcRotateCamera, Vector3, HemisphericLight, MeshBuilder } from "@babylonjs/core";

class App {
    constructor() {
        // create the canvas html element and attach it to the webpage
        const canvas = document.createElement("canvas");
        canvas.style.width = "100%";
        canvas.style.height = "100%";
        canvas.id = "gameCanvas";
        document.body.appendChild(canvas);

        // initialize babylon scene and engine
        const engine = new Engine(canvas, true);
        const scene = new Scene(engine);

        const camera = new ArcRotateCamera("Camera", Math.PI / 2, Math.PI / 2, 2, Vector3.Zero(), scene);
        camera.attachControl(canvas, true);
        const light1 = new HemisphericLight("light1", new Vector3(1, 1, 0), scene);
        const sphere = MeshBuilder.CreateSphere("sphere", { diameter: 1 }, scene);

        // hide/show the Inspector
        window.addEventListener("keydown", (ev) => {
            // Shift+Ctrl+Alt+I
            if (ev.shiftKey && ev.ctrlKey && ev.altKey && ev.keyCode === 73) {
                if (scene.debugLayer.isVisible()) {
                    scene.debugLayer.hide();
                } else {
                    scene.debugLayer.show();
                }
            }
        });

        // run the main render loop
        engine.runRenderLoop(() => {
            scene.render();
        });
    }
}
new App();
  • Also for STEP 10, copy in the following code for your index.html file.
<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title>Title of Your Project</title>
    </head>
    <body>
       <script type="module" src="./app.js"></script>
    </body>
</html>
  • STEP 11 should work flawlessly now! :tada: :tada: :tada: But PLEASE remember to read the rest of the guide under step 11. There are still important things to know about updating your npm and building your final code bundle that you should know how to do.

Let me know if you have any further questions!

(this JS version of the guide was privately requested by a forum user)

3 Likes

Thankyou!

1 Like

Thanks for the great tutorial @wavetro !
You mention

I followed all the steps and everything works great, but the node_modules folders in the root folder and the working directory have tons of duplicates, please see the screenshot below (left is root, right is working directory). How can I get rid of the duplicates?

Ah yeah, a majority of those duplicated folders in the working directory are all dependencies for a duplicate installation of vite. As far as I know, this is intended behavior by the Vite developers, since the installation in the root folder initializes the installation in the working directory, and the extra module folders in the working directory is what will be doing the heavy lifting when you build your Babylon app.

I think you could get around this by uninstalling Vite and its dependencies from your root folder by running npm uninstall vite in it after creating your project, but you might need it again the next time you want to initialize another Vite + Babylon project. I personally just let the duplicate Vite installs exist, since it’s the only npm package that’s doing this duplicate modules behavior. Someone can correct me if I’m wrong, and sorry if this all sounds inconvenient!

Thanks for the detailed explanation!
If I find the time I might approach the Vite developers to see if there is a possibility in the future to allow for a more memory-friendly setup with a root and several different project directories.

Thank you for the tutorial, @wavetro

you can automate all this by using CLI.
yarn create vite assuming you have yarn globally installed via NPM
then simply add these dependencies :slight_smile:
yarn add @babylonjs/core

additionally if you want more goodies including DebugLayer →
yarn add @babylonjs/core @babylonjs/gui @babylonjs/gui-editor @babylonjs/inspector @babylonjs/loaders @babylonjs/materials @babylonjs/serializers

And finally inside your code:

window.addEventListener("DOMContentLoaded", () => {
  // rome-ignore lint/style/noNonNullAssertion: <explanation>
  new Game(document.querySelector<HTMLCanvasElement>("#renderCanvas")!)
})

if you want to use scene.debugLayer.show()

import these at the top:

import "@babylonjs/core/Debug/debugLayer";
import "@babylonjs/inspector";
import "@babylonjs/loaders/glTF";

Thats it. have fun.

DEMO: GitHub - steelx/bjs-game01: Babylon js game using vite and Typescript

3 Likes

Sry I’m a total beginner but why do I need Vite?

Hello and welcome!
You may not need Vite :slight_smile:
It depends on how you build your Typescript files.
You may use plain JS if you want, even without any build, as shown here - Starter HTML Template | Babylon.js Documentation