Repost: Danger Crew packaged with Electron

Drew Conley

Drew Conley

Posted on January 2nd, 2022

Let's break down the technical stack of Danger Crew. We built this game with React, then wrapped it with Electron to package for Steam.

Danger Crew

Danger Crew is finally DONE and released on Steam today! Danger Crew is a top down RPG game written in HTML, CSS, and JavaScript. It's a story in <div>s about people who write <div>s! I've shared a few posts about making Danger Crew before... it even started as a demo on CodePen!

See Danger Crew on Steam

Watch the trailer to get a feel for the game!

This particular question came up on Reddit's r/reactjs:

Web Devs can publish games on Steam now? My dream is finally realizable.

(Thanks to Shawn for posting the original thread!)

Yes - It's 100% true. Web devs can absolutely publish games to Steam.

This post is a quick tour of my process for bundling up an HTML project as a standalone app for both Windows and Mac. The app can then be distributed on a platform of your choice, such as Steam, using the Electron framework. We're going to go through a brief introduction to Electron, workflow with bringing a static site over to an Electron environment, and some random gotchas I found along the way.

Before we get started, I'm going to be saying "Steam" a lot. This could be replaced with whatever platform you want to target. The idea here is to compile your browser centered code into a native application for whatever purposes you need.

I hope you leave this post with some newly sparked empowerment knowing that your front end skills can be used in places beyond web browsers. If you already knew all this stuff - I hope you take action to make something and tell us about it!

Starting with a static site

Here is my situation: I have created something in all HTML, CSS, and JavaScript. There’s no backend server to bake data from a database into the page. Any content specific to a particular user is fetched through AJAX calls. The static nature of my project keeps it nice and portable.

To be a little more specific, my specific project uses Create React App out of the box - no ejecting or fancy customization. I develop using CRA’s npm start and cut my production builds using npm build. When building for production, CRA generates a fresh build directory. I move the contents of that directory to my server and kaboom 💥 - my project is live on the web. The content of this post has no dependency on Create React App… I just wanted to call out that the thoughts here are valid for any kind of project where the final output is static files.

A Mega Simple Introduction to Electron

https://electronjs.org/

From their website: "Electron is a framework for creating native applications with web technologies like JavaScript, HTML, and CSS. It takes care of the hard parts so you can focus on the core of your application." Some popular examples of Electron apps, according to Electron's website, are Slack, VS Code, Skype, and Discord.

Me to you, developer to developer, working with Electron feels just like running any other development Terminal process on your machine. You cd into your Electron project, run the npm start command to launch a local instance of your work, then eventually run a build command to compile native production versions of your application. (More details on building later in the post!)

Your application will launch in its own private version of Chromium. Think Google Chrome but without all the personalized user browser features and extra stuff. You even have the same Dev Tools available to you! You'll feel right at home as a front end developer.

To get started, clone this repo to your machine: https://github.com/electron/electron-quick-start

Run npm start to launch the app locally, then make any change you want to the included index.html file. On reload, you'll see your changes appear in the app. Go crazy with all the styling and scripting you want - it will work as you expect!

But wait, there's more! Electron works with Node.js under the hood. In addition to client side scripts, you also have the ability to write and run what seems like backend code. For example, my app utilizes a few Node functions to read and write JSON files to the user's computer. These functions power the "Save Game" and "Continue Game" features where player progress can be stored locally with no requirement for a database or stable Internet connection.

It's front end code + the power of Node all packaged together. The possibilities are endless! This post isn't really a full Electron tutorial, but a mere prompt for you to dive in and explore what Electron can provide for your project.

Workflow for integrating your browser-based project in an Electron environment

If you intend to build a project from the ground up using Electron, you can probably skip this part of the post. Part of the promise here was "how to package your [existing] web project as an app", so now we have some gotchas for the projects that are browser first, native app second.

For my team's purposes, we like keeping our web version completely independent from our Electron builds. We can always run the app in a webpage/browser as originally intended. This way, we can quickly publish changes to our staging site for rapid development and QA.

We have two repos:

  1. The web project. This is the code that existed before Electron came into the picture. It's the game created with Create React App in my case, but again, the important part is that it's whatever runs in the normal browser for you.
  2. The Electron project. This is a separate project on my machine bootstrapped from electron-quick-start above. For my purposes, I copy outputted production files from the web project into this project. I don't mind copying the files manually because it's as simply as copy/pasting a directory every few days, but this could totally be automated if you wanted. After copying the files over, we export a final build that goes to Steam.

Here's the catch with bundling your existing web project with Electron. Electron is a Node environment. It assumes your project is built with CommonJS. Maybe you're already doing that in development, especially if using a modern tooling chain like Create React App, but we want to provide our browser-friendly production build files to Electron - not development source files. Our production webpage has references to assets like this:

  <!-- you know, the good 'ol way! -->
<link rel="stylesheet" type="text/css" href="/css/my.production.stylesheet.css">
<script src="/js/my.production.bundle.min.js"></script>

When initially viewing your project's HTML through Electron's development environment, you might see an unstyled page with a bunch of 404 network errors. Electron is not set up to resolve paths to relative assets like the public server of our static files. We're making clever use of Electron here by porting over an existing project, so we need to help it out. Here's a way to fix the path resolution. Make these edits to the main.js file that comes with electron-quick-start:

/* Augmentations to the default main.js file that comes with electron-quick-start */

//The top section of `requires` will already be there. Make sure the following requires are present!
const { app, protocol } = require('electron');
const url = require('url');

//This is the "out of the box" loadFile line:
//mainWindow.loadFile('index.html')

//Use this instead - it's the first step in helping Electron find your assets
mainWindow.loadURL(
	url.format({
		pathname: 'index.html',
		protocol: 'file',
		slashes: true
	})
);

app.on('ready', () => {
	//This event should already exist in main.js
	//Add this block! It's the second step in helping Electron find your assets
	protocol.interceptFileProtocol(
		'file',
		(request, callback) => {
			const url = request.url.substr(7);
			callback({ path: path.normalize(`${__dirname}/${url}`) });
		},
		(err) => {
			if (err) console.error('Failed to register protocol');
		}
	);
});

After making these changes, you should see your assets loading correctly in the Electron environment! No need to change your existing code to use CommonJS!

IMPORTANT DISCLAIMER - making this change comes with some baggage. You may be opting out of some of Electron's capabilities by configuring this behavior. Research and experimentation will be required to figure out if it's a deal breaker for you. If so, you may be in the camp of combining your web project with your Electron project. For our project, the changes outlined above have been totally fine.

Exporting the final native apps

Okay, nice work! You've done your development and are ready to export a final application that real users will download and run on their computers. The Electron community has a few options for exporting. I've been using one called Electron Builder: https://github.com/electron-userland/electron-builder

Install Electron Builder in your project by following the instructions listed on the GitHub page above. After installation, you will be able to run a command to create a packaged version of your app:

  # Run this from the root of your Electron project after installing `electron-builder`
electron-builder

To my understanding, this command will default to whatever platform you're on. I'm on a Mac, so this command will create a Mac OSX [MyProjectName].app file in a new /dist directory of my Electron project. I run electron-builder --win while on my Mac to create a Windows version. I bet the reverse is true for creating a Mac version while on a Windows machine.

At this point, the contents of dist are all you need to distribute your app! Open the exported files and you will have native versions of your web project running on your machine! 🎉 These are the files that eventually get distributed to Steam, or the platform of your choice.

Bonus: Rapid fire things you should know about Steam

The post is over, but I thought I'd leave you with some random knowledge I acquired from going through the process of distributing Danger Crew on Steam.

  • Steam is not just for games. People release software on Steam, too. My favorite is Aesprite. Anything [appropriate] you built for the web is also valid for Steam!

  • Uploading your game to Steam involves installing the Steam SDK to your computer. You copy your builds (same content as the dist folder above) into a specified location and run Valve's build script. Most of the documentation is for Windows, but the SDK works fine on Mac, too. Read the documentation. This screencast helped me wrap my head around the process.

  • Steam is really just a launcher that opens and updates software on your computer. If you upload a new version of your app, any user who has downloaded your app through Steam will receive an automatic updates (assuming the user has an Internet connection and has not turned off the updating feature). You don't have to write any extra code to make Steam do its thing.

  • However, Steam does inject some code to make features like Steam Overlay work. (That's where a player presses Shift + Tab to access Steam Chat and other widgets while in game) This does not work out of the box with Electron. I'm still trying to figure out why.

  • Controller support just works! It's the craziest feeling - you plug in a Steam Controller, Xbox Controller, or PS4 Controller, configure the bindings (like, the "A" button on Xbox Controller means "press the spacebar" key) and your game/app is instantly controller compatible. You do not need to write custom code to make controller bindings work.

If you want to make games + you know HTML, CSS, and JavaScript... there is nothing stopping you.

If you prefer video content, I recorded a presentation about the project here on YouTube.

Back to all Articles