Josh Bavari's Thoughts

Thoughts on technology and philosophy

Releasing Electron for Windows

about a 3 minute read

Releasing Electron applications on Windows can be a tricky issue. Especially if you mainly use a Mac (like me). And you have to think about that pesky code signing thing you have to do to avoid the annoying ‘SmartScreen’ filter users may get.

Thankfully, there’s a great tool called Squirrel made by Paul Betts that does a ton of the heavy lifting for you. Codesigning and all.

I really got a ton of knowledge from the blog post, Creating a Windows Distribution of an Electron App using Squirrel and Using Electron Packager to Package an Electron App.

I wanted to curate a ton of knowledge in one place, so here we go.

I use a few tools to get this done on my Mac:

First, let’s look at the project layout:

Project Layout

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
/build # Installers go here
  /osx
  /resources # Icons, iconset, images, etc
  /win # Binaries to build, Package.nuspec file to specify configurations
  packager.json # File used by electron-builder to build OSX DMG file.
/dist # Distributions go here (.app, .exe, .dmg)
  /osx
  /win
/docs # Docs about project.
/node_modules # Modules used for building/packaging/testing
/scss # Sass for CSS compilation in www
/spec # Tests
  AppCtrl.spec.js
www # Source code for the application
  /css
  /data
  /img
  /js
  /lib
  /node_modules # Modules here used by the application itself.
  /templates

karma.conf.js # Configuration for tests.
livereload.js # Dev script to set up live reload in Electron
package.json # Main package.json with scripts/dependencies to package/build.

Process

First we’ll need to make the exe and associated files to a dist folder. From there, we take the win dist files and pack them into the Setup.exe file, where Squirrel will do the heavy lifting to pack all this into a one step process.

npm Scripts

We’ll use the npm script pack:win task to put all our www files into a nice package (resources, exe, etc) and output to the dist folder.

pack:win step will just execute electron-packager with some relevant information. Please note the asar=true, this is recommended because sometimes node_modules can get nested a few times and the file paths will be too long for certain Windows platforms.

Script:

1
2
3
4
{
  "scripts": {
    "pack:win": "electron-packager ./www \"Project\" --out=dist/win --platform=win32 --arch=ia32 --version=0.29.1 --icon=build/resources/icon.ico --version-string.CompanyName=\"My Company\" --version-string.ProductName=\"Project\" --version-string.FileDescription=\"Project\" --asar=true"
  }

Electron Build script

I used a simple build script in node to assist in some of the heavy lifting. I recommend getting an Extended Validation certificate from this blog post.

This will take the windows package in dist/win and create dist/win/Setup.exe.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
#!/usr/bin/env node
// File is in root/build/win/build.js
// First call nuget pack Package.nuspec
// Then you'll have Project.<version>.nupkg
// Run Squirrel.exe --releaseify Project.<version>.nupkg --icon iconPath --loadingGif loadingGifPath
// resources in build/resources/

//Need to get around weird command line passes with windows paths
function addWindowsPathFix(path) {
  return ['"', path, '"'].join('');
}

var childProcess = require('child_process'),
  path = require('path'),
  packageJsonPath = path.join(__dirname, '..', '..', 'package.json'),
  packageJson = require(packageJsonPath),
  loadingGifPath = path.join(__dirname, '..', 'resources', 'windows-loader.png'),
  nugetPackageSpecPath = path.join(__dirname, 'Package.nuspec'),
  nugetPackageOutputPath = path.join(__dirname),
  nugetPackageName = ['Project', '.1.0.0', '.nupkg'].join(''),
  // nugetPackageName = ['Project', packageJson.version, '.nupkg'].join(''),
  nugetPackagePath = path.join(nugetPackageOutputPath, nugetPackageName),
  nugetExePath = path.join(__dirname, 'nuget.exe'),
  setupIconPath = path.join(__dirname, '..', 'resources', 'icon.ico'),
  setupReleasePath = path.join(__dirname, '..', '..', 'dist', 'win'),
  signatureCertificatePath = path.join(__dirname, 'Certificate.pfx'),
  signParams = ['"/a /f "', addWindowsPathFix(signatureCertificatePath), '" /p ', process.env.PRIVATE_CERT_PASSWORD, '"'].join(''),
  squirrelExePath = path.join(__dirname, 'Squirrel.exe');

  console.log('sign params', signParams);

var createNugetPackageCommand = [addWindowsPathFix(nugetExePath), 'pack', addWindowsPathFix(nugetPackageSpecPath), '-OutputDirectory', addWindowsPathFix(nugetPackageOutputPath)].join(' ');
var createSetupCommand = [
              addWindowsPathFix(squirrelExePath),
              '--releasify', addWindowsPathFix(nugetPackagePath),
              '--loadingGif', addWindowsPathFix(loadingGifPath),
              '--icon', addWindowsPathFix(setupIconPath),
              '--releaseDir', addWindowsPathFix(setupReleasePath),
              '--signWithParams', signParams
            ].join(' ');


console.log('Creating nuget package from nuget spec file:', nugetPackageSpecPath);
// console.log(createNugetPackageCommand);
childProcess.execSync(createNugetPackageCommand);
console.log('Created nuget package');

console.log('Building Setup.exe');
// console.log(createSetupCommand);
childProcess.execSync(createSetupCommand);
console.log('Built Setup.exe');

Hope this helps!

Comments