The series has been going for a while now, and somehow I never got to the key subject of packaging. The main appeal of Electron apps is that they can be packaged and distributed, so the users can run them without having to install any dependencies. Even something as seemingly simple as "install Java" invariably leads to endless tech support issues.
I want to try two scenarios, and then try to package them. First, a purely static app. And then an app with compiled SPA backend like Svelte or React.
So before we get to packaging, let's build a simple static app - cookie clicker game. To have a few different types of assets to package we'll use:
- cookie picture from Wikipedia
- coin sound from freesounds
- jQuery from npm package
The game will be totally trivial. Click the cookie, it plays sound and increases score.
Setup
$ npm install --save-dev electron jquery
package.json
{
"devDependencies": {
"electron": "^15.1.1",
"jquery": "^3.6.0"
},
"scripts": {
"start": "electron ."
}
}
index.js
We just need to open index.html
. I left empty preload.js
just in case we need it for something.
let { app, BrowserWindow } = require("electron")
function createWindow() {
let win = new BrowserWindow({
webPreferences: {
preload: `${__dirname}/preload.js`,
},
})
win.loadFile("index.html")
}
app.on("ready", createWindow)
app.on("window-all-closed", () => {
app.quit()
})
index.html
The cookie will be in the body
, and #score
will display the score. That's the whole game!
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<link rel="stylesheet" href="app.css">
<title>Cookie Clicker</title>
</head>
<body>
<div id="score">Score: 0</div>
<script src="./node_modules/jquery/dist/jquery.js"></script>
<script src="./app.js"></script>
</body>
</html>
app.css
To prevent some complications depending on where exactly the user clicks, we need to tell the browser we don't want the score to be clickable (pointer-events: none
) or selectable (user-select: none
).
body {
margin: 0;
padding: 0;
overflow: hidden;
background-image: url("cookie.jpg");
background-size: cover;
background-repeat: no-repeat;
height: 100vh;
width: 100vw;
}
#score {
pointer-events: none;
user-select: none;
font-size: 36px;
font-family: fantasy;
}
app.js
And finally the game logic. The only non-obvious thing is that we need to set audio.currentTime = 0
. If user clicks while the sound is playing, we want it to start the sound from the beginning. Having just audio.play()
without rewinding there would ignore the call, and that's a much worse experience.
Alternatively we could play multiple sounds at once, but that could be quite jarring if someone clicks really fast.
let score = 0
let audio = new Audio("coin.wav")
$("body").on("click", (e) => {
e.preventDefault()
score += 1
$("#score").text(`Score: ${score}`)
audio.currentTime = 0
audio.play()
})
Results
Here's the results:
Now that we have a simple static app, in the next episode we'll try to package it into something users can just download and run without installing any dependencies.
As usual, all the code for the episode is here.
Top comments (0)