Table of Contents
- Introduction
- Setting Things Up
- Building the Application
- Time to Build
- Package and Distribute
- Final Thoughts and Resources
GitHub: https://github.com/justinctlam/BabylonJS-Electron
1. Introduction
Are you interested in building a cross-platform desktop 3D application? Maybe you want to do a visualization application or even a game? I'm here to help, the following tutorial will guide you on setting up BabylonJS with Electron. I will also incorporate TypeScript so that you can see how all these technologies work together. I hope you find this combination useful and informative.
I wrote this article to help me as well as others that are coming from a non-web background, maybe from iOS, Android, desktop application, backend, etc... I'll do my best to make this as straight forward as possible and I am targeting those who are just starting out with web development.
First some basic explanation of the tech stack.
- BabylonJS: A complete JavaScript framework for building 3D games with HTML5, WebGL, WebVR and Web Audio.
- Electron: Build cross platform desktop apps with JavaScript, HTML, and CSS.
- TypeScript: A free and open-source programming language developed and maintained by Microsoft. It is a strict syntactical superset of JavaScript, and adds optional static typing to the language.
To glue all this tech together I will be using the following infrastructure.
- Visual Studio Code: Visual Studio Code is a code editor redefined and optimized for building and debugging modern web and cloud applications.
- NPM: A package manager for JavaScript and the world’s largest software registry. Discover packages of reusable code — and assemble them in powerful new ways.
- Electron-builder: A Complete solution to package and build a ready for distribution Electron app with “auto update” support out of the box.
This tutorial will not assume you have anything already installed. I will cover all the tools that are necessary to build it from scratch. Let's begin!
2. Setting Things Up
Let's start by installing NodeJS which will give us the ability to use NPM. You can find it here (https://nodejs.org/en/)
This tutorial was tested on NodeJS version 6, which you can download here (https://nodejs.org/dist/latest-v6.x/).
Optional: I would recommend using Visual Studio Code as your IDE. This is purely optional and not necessary to complete the tutorial. You can get VS Code here (https://code.visualstudio.com/).
Next, let's create a folder, let's call it babylonjsElectron
In a terminal or command prompt shell execute the following:
npm init
Select default for all options. This will create a package.json
for you with some parameters set.
Next we will need to download and install some packages through NPM for our project.
Execute the following commands in your terminal or command prompt.
npm install --save babylonjs@3.1.1
npm install --save-dev typescript@2.6.2
npm install --save-dev electron@1.7.10
From here onwards this tutorial will work with the following versions of the packages. 3.1.1 of BabylonJS, 2.6.2 of TypeScript, and 1.7.9 of Electron.
A little bit of explanation about the package dependencies.
Dependencies
are the packages we will need to run our application and devDependencies
are packages we will use to help build the application but isn't required in the distribution of the application.
Your package.json
file should now contain these properties:
"dependencies": {
"babylonjs": "^3.1.1"
},
"devDependencies": {
"typescript": "^2.6.2",
"electron": "^1.7.9"
},
3. Building the Application
We'll need 3 files to start out index.html
, main.ts
, and renderer.ts
Create a src
folder under your current directory, for example babylonjsElectron/src
.
In the src
directory, create the two files main.ts
and renderer.ts
.
In the root directory under babylonjsElectron/
create index.html
.
In main.ts
put the following boiler plate code:
import { app, BrowserWindow } from "electron";
import * as path from "path";
import * as url from "url";
let mainWindow: Electron.BrowserWindow;
function createWindow() {
// Create the browser window.
mainWindow = new BrowserWindow({
height: 600,
width: 800,
});
// and load the index.html of the app.
mainWindow.loadURL(url.format({
pathname: path.join(__dirname, "../index.html"),
protocol: "file:",
slashes: true,
}));
// Open the DevTools.
mainWindow.webContents.openDevTools();
// Emitted when the window is closed.
mainWindow.on("closed", () => {
// Dereference the window object, usually you would store windows
// in an array if your app supports multi windows, this is the time
// when you should delete the corresponding element.
mainWindow = null;
});
}
// This method will be called when Electron has finished
// initialization and is ready to create browser windows.
// Some APIs can only be used after this event occurs.
app.on("ready", createWindow);
// Quit when all windows are closed.
app.on("window-all-closed", () => {
// On OS X it is common for applications and their menu bar
// to stay active until the user quits explicitly with Cmd + Q
if (process.platform !== "darwin") {
app.quit();
}
});
app.on("activate", () => {
// On OS X it"s common to re-create a window in the app when the
// dock icon is clicked and there are no other windows open.
if (mainWindow === null) {
createWindow();
}
});
// In this file you can include the rest of your app"s specific main process
// code. You can also put them in separate files and require them here.
In the renderer.ts
file put the following code:
import * as BABYLON from 'babylonjs';
export default class Renderer {
private _canvas: HTMLCanvasElement;
private _engine: BABYLON.Engine;
private _scene: BABYLON.Scene;
createScene(canvas: HTMLCanvasElement, engine: BABYLON.Engine) {
this._canvas = canvas;
this._engine = engine;
// This creates a basic Babylon Scene object (non-mesh)
const scene = new BABYLON.Scene(engine);
this._scene = scene;
// This creates and positions a free camera (non-mesh)
const camera = new BABYLON.FreeCamera("camera1", new BABYLON.Vector3(0, 5, -10), scene);
// This targets the camera to scene origin
camera.setTarget(BABYLON.Vector3.Zero());
// This attaches the camera to the canvas
camera.attachControl(canvas, true);
// This creates a light, aiming 0,1,0 - to the sky (non-mesh)
const light = new BABYLON.HemisphericLight("light1", new BABYLON.Vector3(0, 1, 0), scene);
// Default intensity is 1. Let's dim the light a small amount
light.intensity = 0.7;
// Our built-in 'sphere' shape. Params: name, subdivs, size, scene
const sphere = BABYLON.Mesh.CreateSphere("sphere1", 16, 2, scene);
// Move the sphere upward 1/2 its height
sphere.position.y = 1;
// Our built-in 'ground' shape. Params: name, width, depth, subdivs, scene
const ground = BABYLON.Mesh.CreateGround("ground1", 6, 6, 2, scene);
}
initialize(canvas: HTMLCanvasElement) {
const engine = new BABYLON.Engine(canvas, true);
this.createScene(canvas, engine);
engine.runRenderLoop(() => {
this._scene.render();
});
window.addEventListener('resize', function () {
engine.resize();
});
}
}
const renderer = new Renderer();
renderer.initialize(document.getElementById('render-canvas') as HTMLCanvasElement);
In the index.html
put the following in it:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Babylon JS Electron App</title>
<style>
html,
body {
overflow: hidden;
width: 100%;
height: 100%;
margin: 0;
padding: 0;
}
#render-canvas {
width: 100%;
height: 100%;
touch-action: none;
}
</style>
</head>
<body>
<canvas id="render-canvas"></canvas>
<script>
require('./app/renderer.js');
</script>
</body>
</html>
4. Time to Build
We are going to build the application now. Normally you don't need to build JavaScript files but for TypeScript there is a "build" step that takes the TypeScript files and transpiles them into JavaScript for consumption.
To start off, we will need a tsconfig.json
. Create a tsconfig.json
under the babylonjsElectron/
folder.
Then put the following in tsconfig.json
:
{
"compilerOptions": {
"target": "es6",
"module": "commonjs",
"noImplicitAny": true,
"sourceMap": true,
"outDir": "app",
"baseUrl": ".",
"paths": {
"*": ["node_modules/*"]
}
},
"include": [
"src/**/*"
]
}
The "include" property tells the TypeScript compiler where to look for the source files. We will be outputting our JavaScript files under babylonjsElectron/app
folder as specified in the "outDir" property. We also have "sourceMap" set to true so that the debugger can find and put breakpoints in the TypeScript file. For more information about the other properties, you can find them here in this resource: https://www.typescriptlang.org/docs/handbook/tsconfig-json.html
Next we will add some command line scripts to package.json
. These command line scripts are used to help use execute various commands or combination of commands.
In package.json add the following under the "scripts" property:
"build": "tsc",
"start": "electron ./app/main.js",
So your package.json
file should look like the following:
"scripts": {
"build": "tsc",
"start": "electron ./app/main.js",
"test": "echo \"Error: no test specified\" && exit 1"
},
tsc
in the "build" property is the TypeScript compiler. In the "start" property, we will launch electron with our compiled TypeScript source, main.js.
You maybe wondering, what about renderer.ts
file? It's not reference in main.ts
but it is reference in index.html
.
Electron has two processes, the main process and the render process. The main process is responsible for creating and managing BrowserWindow instances and various application events. The render process is responsible for running the user-interface of your app. You can find a more detail explanation here: https://codeburst.io/deep-dive-into-electrons-main-and-renderer-processes-7a9599d5c9e2.
In your terminal or command prompt, execute the following command:
npm run build
This will "build" your TypeScript file. Next, execute the following command:
npm start
This will launch the electron application. Success!
You may see a warning like the following in your terminal or command prompt:
Electron Helper[68582:2728327] Couldn't set selectedTextBackgroundColor from default ()
At the time of this tutorial, this is a bug in Chromium. I'm going to assume it will be fixed in future builds of Electron so let's not worry about it.
As you can see, when you run the application that the development panel is opened. If you prefer the development panel not open automatically, comment out the following line in main.ts
:
mainWindow.webContents.openDevTools();
5. Package and Distribute
Alright! Now that we have a working application, let's get ready to package it up for distribution.
For this task we will need the electron-builder NPM package to help us.
Execute the following command in your terminal or command prompt:
npm install --save-dev electron-builder@19.49.0
Next we'll add the following script to "scripts" in package.json
:
"dist": "build"
So now package.json
will look like this:
"description": "This is a babylonjs electron sample",
"main": "./app/main.js",
"scripts": {
"build": "tsc",
"start": "electron ./app/main.js",
"dist": "build",
"test": "echo \"Error: no test specified\" && exit 1"
},
Be sure that you have something in your "description" property as well.
You will also want to make sure that the "main" property is pointing to the main.js
file as shown above.
We are at the final stretch.
Finally, execute the following command:
npm run dist
This will generate a DMG file for you on Mac. You can find the file under babylonjsElectron/dist
folder. When you open up your DMG, it should look like this:
In order to make a Windows application just run the following in your Windows command prompt.
npm run dist
This will generate an exe file for you on Windows. You can find the file under babylonjsElectron/dist
folder.
You can customize a few options for each system as well.
If you intend to release your app, you will need to code-sign your application. For the purpose of this tutorial I will not be covering this but you can find more details here: https://www.electron.build/code-signing.
On Mac, you can specify some options for the DMG generator.
In package.json
, you can add the following options to the DMG property:
"build": {
"appId": "yourappid",
"dmg": {
"contents": [
{
"x": 110,
"y": 0
},
{
"x": 240,
"y": 0,
"type": "link",
"path": "/Applications"
}
]
}
}
Let's quickly discuss what some of these properties mean:
- "appId" is where you would put your application ID if you ever plan to submit to a store like App Store.
- In contents, you can specify the variety of files and paths to appear within the DMG.
You can find more details of the properties here: https://www.electron.build/configuration/dmg.
On Windows, you can customize the type of installer to use.
By default, electron-builder will build a "nsis" (Nullsoft Scriptable Install System) installer. There are various other installer options such as a "portable" installer which will produce an executable that just runs and doesn't install, and a "squirrel" installer that does one click package updates.
For example, you can add these addition configuration properties to the 'package.json' file to create a no installer executable:
"build": {
"appId": "yourappid",
"win": {
target: "portable"
}
}
You can find more details of the properties here:
https://www.electron.build/configuration/win.
6. Final Thoughts and Resources
It's pretty cool that one can easily develop cross platform applications using just JavaScript, HTML, and CSS. Now, not only can you create a cross platform application but you can create a cross platform 3D application for richer and immersive experiences.
At of the time of this tutorial, Chromium supports WebVR but Electron has yet to use the latest version of Chromium. I'm hoping that not too long from now Electron will support WebVR and we can start building cross platform VR applications!
You can find the full source code here on GitHub: https://github.com/justinctlam/BabylonJS-Electron
Some resources I used to write this tutorial:
- BabylonJS - https://www.babylonjs.com/
- Babylon.js Editor and Electron - https://medium.com/babylon-js/babylon-js-editor-and-electron-706d6faf479b
- Electronylon - https://github.com/mmmeff/Electronylon
- Building a desktop application with Electron - https://medium.com/developers-writing/building-a-desktop-application-with-electron-204203eeb658
- A complete guide to packaging your Electron app - https://medium.com/how-to-electron/a-complete-guide-to-packaging-your-electron-app-1bdc717d739f
- Cross-platform Desktop JavaScript 3D Game Engine with NodeJS, Electron and BabylonJS - http://grosan.co.uk/cross-platform-javascript-3d-game-engine-with-nodejs-electron-and-babylonjs/
Top comments (7)
It gives an error. WebGl is supported by my Chome. I can run Babylon playground.
I found similar problem (js) decribed as :
"It's because your canvas is a JQuery element, instead of being a DOM element.
use this:
var canvas = $("#renderCanvas").get(0);"
How can I use in TS?
Uncaught Error: WebGL not supported
at new Engine (C:\sample\node_modules\babylonjs\dist\preview release\babylon.max.js:8138)
at Renderer.initialize (C:\sample\app\renderer.js:29)
at Object. (C:\sample\app\renderer.js:41)
at Object. (C:\sample\app\renderer.js:43)
at Module._compile (module.js:571)
at Object.Module._extensions..js (module.js:580)
at Module.load (module.js:488)
at tryModuleLoad (module.js:447)
at Function.Module._load (module.js:439)
at Module.require (module.js:498)
Can you file a GitHub issue?
Hi Justin,
such a nice tutorial, excatly what I was looking for.
It works perfectly with the same package versions you give. Unfortunatly I get issues when trying to use the latest version, aka
babylonjs@4.0.3
electron@6.0.12
typescript@3.6.4
"Uncaught ReferenceError: require is not defined at index.html:34"
I'm new to all those techs, so I can't figure out how to make it work and start with a clean base with up to date packages, do you have any clue?
There was a breaking change in electron. the "nodeIntegration" flag is now default to false. You need to set it to true as it inserts "require" into the DOM. So add this to your BrowserWindow:
mainWindow = new BrowserWindow({
webPreferences: {
nodeIntegration: true
}
});
thx Johannes,
it works like a charm.
Perfect thanks, I was looking everywhere for a simple hello world for npm integration. Electron is separate POC currently so It's create stumbling on a post that combines the two.
I'm a backend (games and ai) dev slowly moving to full stack, so it's great seeing good code - not random unstructured script.
This is really great work... clapping.