This tutorial is out of date and no longer maintained.
GitHub’s Electron framework (formerly known as Atom Shell) lets you write cross platform desktop application using HTML, CSS and JavaScript. It’s a variant of io.js run-time which is focused on desktop applications instead of web servers.
Electron’s rich native APIs enables us to access native things directly from our pages with JavaScript.
This tutorial shows us how to build a desktop application with Angular and Electron. The steps for this tutorial are as follows:
To get started, install Node if you don’t have it in your system already. Our application should be structured as follows:
There are two package.json
files in this project.
For development:
The package.json
directly inside the project root contains the configurations, dependiencies for your development environment and build scripts. These dependencies and package.json
file will not be included inside the production build.
For your application:
The package.json inside app
folder is the manifest file for your application. So whenever you need to install npm dependencies to be used in your application directly, you should install it against this package.json
The format of package.json
is exactly same as that of Node’s module. Your application’s startup script should be specified in main property inside your app/package.json
.
app/package.json
might look like this:
{
name: "AngularElectron",
version: "0.0.0",
main: "main.js"
}
You can create both package.json
files either by entering the npm init
command. You can also manually create these files. Install npm dependencies that are required for packaging the application by entering following command in your command line prompt:
- npm install --save-dev electron-prebuilt fs-jetpack asar rcedit Q
app/main.js
is the entry point of our application. This script is responsible for creating the main window and handling the system events. main.js
should look like the following:
// Module to control application life.
var app = require('app');
// Module to create native browser window.
var BrowserWindow = require('browser-window');
var mainWindow = null;
// Quit when all windows are closed.
app.on('window-all-closed', function () {
if (process.platform != 'darwin') {
app.quit();
}
});
// This method will be called when Electron has finished
// initialization and is ready to create browser windows.
app.on('ready', function () {
// Create the browser window.
mainWindow = new BrowserWindow({ width: 800, height: 600 });
// and load the index.html of the app.
mainWindow.loadUrl('file://' + __dirname + '/index.html');
// Open the devtools.
// mainWindow.openDevTools();
// Emitted when the window is closed.
mainWindow.on('closed', function () {
// 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;
});
});
As I mentioned above, Electron enables you to access local npm modules and native APIs directly from your web pages. Create your app/index.html
file as follows:
<html>
<body>
<h1>Hello World!</h1>
We are using Electron
<script> document.write(process.versions['electron']) </script> on
<script> document.write(process.platform) </script>
<script type="text/javascript">
var fs = require('fs');
var file = fs.readFileSync('app/package.json');
document.write(file);
</script>
</body>
</html>
app/index.html
is a simple HTML page. Here it reads your package.json
using Node’s fs (file system) module and writes the content into the document body.
Once you have created the project structure, app/index.html
, app/main.js
, app/package.json
, you’ll probably want to try running your initial Electron application to test it and make sure it’s working as expected.
If you’ve installed electron-prebuilt
globally in your system, you can run our application with following command:
- electron app
Here electron
is the command to run electron shell and app
is our application folder name. If you don’t want to install Electron into your global npm modules, then run with your local modules installed into npm_modules
folder as follows in your command line prompt:
- "node_modules/.bin/electron" "./app"
While you could do it that way, I recommend you create a gulp task to run your application in your gulpfile.js
, so that you can integrate your task into Visual Studio Code Editor which we will check in next section.
// get the dependencies
var gulp = require('gulp'),
childProcess = require('child_process'),
electron = require('electron-prebuilt');
// create the gulp task
gulp.task('run', function () {
childProcess.spawn(electron, ['./app'], { stdio: 'inherit' });
});
Run your gulp task:
- gulp run
Visual Studio Code is a cross platform code editor from Microsoft. Behind the scene of VS Code is Electron and Microsoft’s proprietary monaco code editor. You can download Visual Studio Code here.
Open your Electron application in VS Code.
Lots of tools exist to automate our tasks like building, packaging or testing our application. Mostly we run these tools from the command line. VS Code’s built in task runner enables you to integrate your custom tasks into your project. You can run your grunt, gulp, MsBuild or any other tasks directly from within your project without moving to the command line.
VS Code can detect your grunt and gulp tasks automaticaly. Press CTRL+SHIFT+P
then type Run Task
followed by ENTER
.
You will get all available tasks from your gulpfile.js
or gruntfile.js
.
Note: You should have your gulpfile.js
in root directory of your application.
CTRL+SHIFT+B
will execute build
task from your task runner. You can override this intergration using the task.json
file. Press CTRL+SHIFT+P
then type Configure Task
followed by ENTER
. This will create .setting
folder and a task.json
in your project. You need to configure the tasks in tasks.json
if you want to do more than simply run the task. For example you might want to run the application when you press the CTRL+SHIFT+B
. To do this edit the task.json
file as follows:
{
"version": "0.1.0",
"command": "gulp",
"isShellCommand": true,
"args": [ "--no-color" ],
"tasks": [
{
"taskName": "run",
"args": [],
"isBuildCommand": true
}
]
}
Root section says that the command is gulp
. You can have more tasks inside tasks
section as you want. Setting isBuildCommand
true for a task will bind the CTRL+SHIFT+B
to that task. Currently VS Code supports one top level task only.
Now if you press CTRL+SHIFT+B
, gulp run
will be executed.
You can read more about visual studio code tasks here
Open the debug panel and click configure button which will create a launch.json
file inside .settings
folder with debug configuration.
We don’t need launch app.js
configuration, so remove it.
Now your launch.json
should be as follows:
{
"version": "0.1.0",
// List of configurations. Add new configurations or edit existing ones.
// ONLY "node" and "mono" are supported, change "type" to switch.
"configurations": [
{
"name": "Attach",
"type": "node",
// TCP/IP address. Default is "localhost".
"address": "localhost",
// Port to attach to.
"port": 5858,
"sourceMaps": false
}
]
}
Change your gulp run
task that we created before as follows, so that our electron will start in debug mode and will listen to port 5858:
gulp.task('run', function () {
childProcess.spawn(electron, ['--debug=5858','./app'], { stdio: 'inherit' });
});
In the debug panel choose “Attach” configuration and click run or press F5. After few seconds you should see the debug command panel in the top.
New to AngularJS? Check out the official website here or some of the Scotch Angular tutorials.
This section explains how to create a simple Customer Manager application using AngularJS with a MySQL database as the backend. The goal of this application is not to highlight the core concepts of AngularJS but to demonstrate how to use the AngularJS and NodeJS together with MySQL backend inside GitHub Electron.
Our Customer Manager application is as simple as the following:
Our application is located inside app
folder.
The home page is the app/index.html
file. The app/scripts
folder contains all the key scripts and views used in this application. There are several techniques that can be used for organizing application files.
Here I prefer scripts organized by features. Each feature has its own folder with templates and controllers inside the same folder. For more info on folder structure, read: AngularJS Best Practices: Directory Structure
Before get started with the AngularJS application, we’re going to install client side dependencies using Bower. Install Bower if you don’t have it already. Change the current working directory to the root of the application in your terminal then install dependencies as follows from your command line prompt:
- bower install angular angular-route angular-material --save
For this demo I’ll be using a database called customer-manager
and a table called customers
. Here is the dump of database so that you can get up and run quickly.
CREATE TABLE `customer_manager`.`customers` (
`customer_id` INT NOT NULL AUTO_INCREMENT,
`name` VARCHAR(45) NOT NULL,
`address` VARCHAR(450) NULL,
`city` VARCHAR(45) NULL,
`country` VARCHAR(45) NULL,
`phone` VARCHAR(45) NULL,
`remarks` VARCHAR(500) NULL, PRIMARY KEY (`customer_id`)
);
Once you have your database and table ready, let’s create an AngularJS service to access the data directly from this database. The service connects to database using node-mysql
npm module - a NodeJS driver for MySQL written in JavaScript. Install node-mysql
module in your app
folder where your Angular application resides.
Note: We install node-mysql module inside app folder, not in the application root, as we need to include this module inside the final distribution.
Change the current working directory to app
folder in your command prompt install using following command.
- npm install --save mysql
Our Angular service - app/scripts/customer/customerService.js
, should looks like following:
(function () {
'use strict';
var mysql = require('mysql');
// Creates MySQL database connection
var connection = mysql.createConnection({
host: "localhost",
user: "root",
password: "password",
database: "customer_manager"
});
angular.module('app')
.service('customerService', ['$q', CustomerService]);
function CustomerService($q) {
return {
getCustomers: getCustomers,
getById: getCustomerById,
getByName: getCustomerByName,
create: createCustomer,
destroy: deleteCustomer,
update: updateCustomer
};
function getCustomers() {
var deferred = $q.defer();
var query = "SELECT * FROM customers";
connection.query(query, function (err, rows) {
if (err) deferred.reject(err);
deferred.resolve(rows);
});
return deferred.promise;
}
function getCustomerById(id) {
var deferred = $q.defer();
var query = "SELECT * FROM customers WHERE customer_id = ?";
connection.query(query, [id], function (err, rows) {
if (err) deferred.reject(err);
deferred.resolve(rows);
});
return deferred.promise;
}
function getCustomerByName(name) {
var deferred = $q.defer();
var query = "SELECT * FROM customers WHERE name LIKE '" + name + "%'";
connection.query(query, [name], function (err, rows) {
if (err) deferred.reject(err);
deferred.resolve(rows);
});
return deferred.promise;
}
function createCustomer(customer) {
var deferred = $q.defer();
var query = "INSERT INTO customers SET ?";
connection.query(query, customer, function (err, res)
if (err) deferred.reject(err);
deferred.resolve(res.insertId);
});
return deferred.promise;
}
function deleteCustomer(id) {
var deferred = $q.defer();
var query = "DELETE FROM customers WHERE customer_id = ?";
connection.query(query, [id], function (err, res) {
if (err) deferred.reject(err);
deferred.resolve(res.affectedRows);
});
return deferred.promise;
}
function updateCustomer(customer) {
var deferred = $q.defer();
var query = "UPDATE customers SET name = ? WHERE customer_id = ?";
connection.query(query, [customer.name, customer.customer_id], function (err, res) {
if (err) deferred.reject(err);
deferred.resolve(res);
});
return deferred.promise;
}
}
})();
customerService
is a simple custom angular service that provides basic CRUD operations on customers
table . It uses Node’s mysql
module directly inside the service. If you already have a remote data service, you can use it instead.
OurcustomerController
inside app/scripts/customer/customerController
is as follows:
(function () {
'use strict';
angular.module('app')
.controller('customerController', ['customerService', '$q', '$mdDialog', CustomerController]);
function CustomerController(customerService, $q, $mdDialog) {
var self = this;
self.selected = null;
self.customers = [];
self.selectedIndex = 0;
self.filterText = null;
self.selectCustomer = selectCustomer;
self.deleteCustomer = deleteCustomer;
self.saveCustomer = saveCustomer;
self.createCustomer = createCustomer;
self.filter = filterCustomer;
// Load initial data
getAllCustomers();
//----------------------
// Internal functions
//----------------------
function selectCustomer(customer, index) {
self.selected = angular.isNumber(customer) ? self.customers[customer] : customer;
self.selectedIndex = angular.isNumber(customer) ? customer: index;
}
function deleteCustomer($event) {
var confirm = $mdDialog.confirm()
.title('Are you sure?')
.content('Are you sure want to delete this customer?')
.ok('Yes')
.cancel('No')
.targetEvent($event);
$mdDialog.show(confirm).then(function () {
customerService.destroy(self.selected.customer_id).then(function (affectedRows) {
self.customers.splice(self.selectedIndex, 1);
});
}, function () { });
}
function saveCustomer($event) {
if (self.selected != null && self.selected.customer_id != null) {
customerService.update(self.selected).then(function (affectedRows) {
$mdDialog.show(
$mdDialog
.alert()
.clickOutsideToClose(true)
.title('Success')
.content('Data Updated Successfully!')
.ok('Ok')
.targetEvent($event)
);
});
}
else {
//self.selected.customer_id = new Date().getSeconds();
customerService.create(self.selected).then(function (affectedRows) {
$mdDialog.show(
$mdDialog
.alert()
.clickOutsideToClose(true)
.title('Success')
.content('Data Added Successfully!')
.ok('Ok')
.targetEvent($event)
);
});
}
}
function createCustomer() {
self.selected = {};
self.selectedIndex = null;
}
function getAllCustomers() {
customerService.getCustomers().then(function (customers) {
self.customers = [].concat(customers);
self.selected = customers[0];
});
}
function filterCustomer() {
if (self.filterText == null || self.filterText == "") {
getAllCustomers();
}
else {
customerService.getByName(self.filterText).then(function (customers) {
self.customers = [].concat(customers);
self.selected = customers[0];
});
}
}
}
})();
Our Customer template(app/scripts/customer/customer.html
) uses angular material components to build user interface, and its is as follows:
<div style="width:100%" layout="row">
<md-sidenav class="site-sidenav md-sidenav-left md-whiteframe-z2"
md-component-id="left"
md-is-locked-open="$mdMedia('gt-sm')">
<md-toolbar layout="row" class="md-whiteframe-z1">
<h1>Customers</h1>
</md-toolbar>
<md-input-container style="margin-bottom:0">
<label>Customer Name</label>
<input required name="customerName" ng-model="_ctrl.filterText" ng-change="_ctrl.filter()">
</md-input-container>
<md-list>
<md-list-item ng-repeat="it in _ctrl.customers">
<md-button ng-click="_ctrl.selectCustomer(it, $index)" ng-class="{'selected' : it === _ctrl.selected }">
{{it.name}}
</md-button>
</md-list-item>
</md-list>
</md-sidenav>
<div flex layout="column" tabIndex="-1" role="main" class="md-whiteframe-z2">
<md-toolbar layout="row" class="md-whiteframe-z1">
<md-button class="menu" hide-gt-sm ng-click="ul.toggleList()" aria-label="Show User List">
<md-icon md-svg-icon="menu"></md-icon>
</md-button>
<h1>{{ _ctrl.selected.name }}</h1>
</md-toolbar>
<md-content flex id="content">
<div layout="column" style="width:50%">
<br />
<md-content layout-padding class="autoScroll">
<md-input-container>
<label>Name</label>
<input ng-model="_ctrl.selected.name" type="text">
</md-input-container>
<md-input-container md-no-float>
<label>Email</label>
<input ng-model="_ctrl.selected.email" type="text">
</md-input-container>
<md-input-container>
<label>Address</label>
<input ng-model="_ctrl.selected.address" ng-required="true">
</md-input-container>
<md-input-container md-no-float>
<label>City</label>
<input ng-model="_ctrl.selected.city" type="text" >
</md-input-container>
<md-input-container md-no-float>
<label>Phone</label>
<input ng-model="_ctrl.selected.phone" type="text">
</md-input-container>
</md-content>
<section layout="row" layout-sm="column" layout-align="center center" layout-wrap>
<md-button class="md-raised md-info" ng-click="_ctrl.createCustomer()">Add</md-button>
<md-button class="md-raised md-primary" ng-click="_ctrl.saveCustomer()">Save</md-button>
<md-button class="md-raised md-danger" ng-click="_ctrl.cancelEdit()">Cancel</md-button>
<md-button class="md-raised md-warn" ng-click="_ctrl.deleteCustomer()">Delete</md-button>
</section>
</div>
</md-content>
</div>
</div>
app.js
contains module initialization script and application route config as follows:
(function () {
'use strict';
var _templateBase = './scripts';
angular.module('app', [
'ngRoute',
'ngMaterial',
'ngAnimate'
])
.config(['$routeProvider', function ($routeProvider) {
$routeProvider.when('/', {
templateUrl: _templateBase + '/customer/customer.html' ,
controller: 'customerController',
controllerAs: '_ctrl'
});
$routeProvider.otherwise({ redirectTo: '/' });
}
]);
})();
Finally here is our index page app/index.html
<html lang="en" ng-app="app">
<title>Customer Manager</title>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge"gt;
<meta name="description" content="">
<meta name="viewport" content="initial-scale=1, maximum-scale=1, user-scalable=no" />
<!-- build:css assets/css/app.css -->
<link rel="stylesheet" href="../bower_components/angular-material/angular-material.css" />
<link rel="stylesheet" href="assets/css/style.css" />
<!-- endbuild -->
<body>
<ng-view></ng-view>
<!-- build:js scripts/vendor.js -->
<script src="../bower_components/angular/angular.js"></script>
<script src="../bower_components/angular-route/angular-route.js"></script>
<script src="../bower_components/angular-animate/angular-animate.js"></script>
<script src="../bower_components/angular-aria/angular-aria.js"></script>
<script src="../bower_components/angular-material/angular-material.js"></script>
<!-- endbuild -->
<!-- build:app scripts/app.js -->
<script src="./scripts/app.js"></script>
<script src="./scripts/customer/customerService.js"></script>
<script src="./scripts/customer/customerController.js"></script>
<!-- endbuild -->
</body>
</html>
Run your application using gulp run
command or press Ctrl + Shif + B
if you already configured VS Code task runner as above.
To build our Angular application install gulp-uglify
, gulp-minify-css
and gulp-usemin
packages.
- npm install --save gulp-uglify gulp-minify-css gulp-usemin
Open your gulpfile.js
and import required modules
var childProcess = require('child_process');
var electron = require('electron-prebuilt');
var gulp = require('gulp');
var jetpack = require('fs-jetpack');
var usemin = require('gulp-usemin');
var uglify = require('gulp-uglify');
var projectDir = jetpack;
var srcDir = projectDir.cwd('./app');
var destDir = projectDir.cwd('./build');
Clean build directory if it already exists.
gulp.task('clean', function (callback) {
return destDir.dirAsync('.', { empty: true });
});
Copy files into build directory, We don’t need to copy the angular application code using copy function. usemin
will do this for us in next section:
gulp.task('copy', ['clean'], function () {
return projectDir.copyAsync('app', destDir.path(), {
overwrite: true, matching: [
'./node_modules/**/*',
'*.html',
'*.css',
'main.js',
'package.json'
]
});
});
Our build task takes our app/index.html
with gulp.src()
and then we pipe it to usemin. It then writes the output into build directory and replace references in index.html with optimized version of code.
Don’t forget to define usemin blocks inside app/index.html
as follows:
<!-- build:js scripts/vendor.js -->
<script src="../bower_components/angular/angular.js"></script>
<script src="../bower_components/angular-route/angular-route.js"></script>
<script src="../bower_components/angular-animate/angular-animate.js"></script>
<script src="../bower_components/angular-aria/angular-aria.js"></script>
<script src="../bower_components/angular-material/angular-material.js"></script>
<!-- endbuild -->
<!-- build:app scripts/app.js -->
<script src="./scripts/app.js"></script>
<script src="./scripts/customer/customerService.js"></script>
<script src="./scripts/customer/customerController.js"></script>
<!-- endbuild -->
The build task should look as follows:
gulp.task('build', ['copy'], function () {
return gulp.src('./app/index.html')
.pipe(usemin({
js: [uglify()]
}))
.pipe(gulp.dest('build/'));
});
In this section we will package our Electron application for production. Create your build script in root directory as build.windows.js
. This script is intended to be used on Windows. For other platform you should create scripts specific to that platform and should run according to your platform.
A typical electron distribution can be found inside node_modules/electron-prebuilt/dist
directory. Here is our steps to build the electron application:
dist
folder.dist/resources/default_app
folder. We need to replace this application with our final angular application build.Note: This section describes about packaging in Windows platform. All these steps are same for other platforms but paths and files used here is different for other platforms. You can get the full build scripts for OSX and Linux here in GitHub.
Install dependencies required to build the electron as following:
- npm install --save q asar fs-jetpack recedit
Next initialize our build script as follows:
var Q = require('q');
var childProcess = require('child_process');
var asar = require('asar');
var jetpack = require('fs-jetpack');
var projectDir;
var buildDir;
var manifest;
var appDir;
function init() {
// Project directory is the root of the application
projectDir = jetpack;
// Build directory is our destination where the final build will be placed
buildDir = projectDir.dir('./dist', { empty: true });
// angular application directory
appDir = projectDir.dir('./build');
// angular application's package.json file
manifest = appDir.read('./package.json', 'json');
return Q();
}
Here we use fs-jetpack
node module for the file operation. It gives more flexibility on file operations.
Copy the default electron distribution from electron-prebuilt/dist
to our dist directory.
function copyElectron() {
return projectDir.copyAsync('./node_modules/electron-prebuilt/dist', buildDir.path(), { overwrite: true });
}
You can find a default HTML application inside resources/default_app
folder. We need to replace this application with our angular application. Remove it as follows:
Note: Here path is specific to Windows platform. For other platforms process is same but path is different. In OSX it’s Contents/Resources/default_app
function cleanupRuntime() {
return buildDir.removeAsync('resources/default_app');
}
function createAsar() {
var deferred = Q.defer();
asar.createPackage(appDir.path(), buildDir.path('resources/app.asar'), function () {
deferred.resolve();
});
return deferred.promise;
}
This combines all your Angular application files into single asar package file. You can find the destination asar file in dist/resources/
directory.
Next step is to replace the default electron icon with your own, update the product information and rename the application.
function updateResources() {
var deferred = Q.defer();
// Copy your icon from resource folder into build folder.
projectDir.copy('resources/windows/icon.ico', buildDir.path('icon.ico'));
// Replace Electron icon for your own.
var rcedit = require('rcedit');
rcedit(buildDir.path('electron.exe'), {
'icon': projectDir.path('resources/windows/icon.ico'),
'version-string': {
'ProductName': manifest.name,
'FileDescription': manifest.description,
}
}, function (err) {
if (!err) {
deferred.resolve();
}
});
return deferred.promise;
}
// Rename the electron exe
function rename() {
return buildDir.renameAsync('electron.exe', manifest.name + '.exe');
}
You can either use wix or NSIS to create windows installer. Here we use NSIS which is designed to be small and flexible as possible and is very suitable for internet distribution. With NSIS you can create such installers that are capable of doing everything that is needed to setup your software.
Create NSIS script in resources/windows/installer.nsis
!include LogicLib.nsh
!include nsDialogs.nsh
; --------------------------------
; Variables
; --------------------------------
!define dest "{{dest}}"
!define src "{{src}}"
!define name "{{name}}"
!define productName "{{productName}}"
!define version "{{version}}"
!define icon "{{icon}}"
!define banner "{{banner}}"
!define exec "{{productName}}.exe"
!define regkey "Software\${productName}"
!define uninstkey "Software\Microsoft\Windows\CurrentVersion\Uninstall\${productName}"
!define uninstaller "uninstall.exe"
; --------------------------------
; Installation
; --------------------------------
SetCompressor lzma
Name "${productName}"
Icon "${icon}"
OutFile "${dest}"
InstallDir "$PROGRAMFILES\${productName}"
InstallDirRegKey HKLM "${regkey}" ""
CRCCheck on
SilentInstall normal
XPStyle on
ShowInstDetails nevershow
AutoCloseWindow false
WindowIcon off
Caption "${productName} Setup"
; Don't add sub-captions to title bar
SubCaption 3 " "
SubCaption 4 " "
Page custom welcome
Page instfiles
Var Image
Var ImageHandle
Function .onInit
; Extract banner image for welcome page
InitPluginsDir
ReserveFile "${banner}"
File /oname=$PLUGINSDIR\banner.bmp "${banner}"
FunctionEnd
; Custom welcome page
Function welcome
nsDialogs::Create 1018
${NSD_CreateLabel} 185 1u 210 100% "Welcome to ${productName} version ${version} installer.$\r$\n$\r$\nClick install to begin."
${NSD_CreateBitmap} 0 0 170 210 ""
Pop $Image
${NSD_SetImage} $Image $PLUGINSDIR\banner.bmp $ImageHandle
nsDialogs::Show
${NSD_FreeImage} $ImageHandle
FunctionEnd
; Installation declarations
Section "Install"
WriteRegStr HKLM "${regkey}" "Install_Dir" "$INSTDIR"
WriteRegStr HKLM "${uninstkey}" "DisplayName" "${productName}"
WriteRegStr HKLM "${uninstkey}" "DisplayIcon" '"$INSTDIR\icon.ico"'
WriteRegStr HKLM "${uninstkey}" "UninstallString" '"$INSTDIR\${uninstaller}"'
; Remove all application files copied by previous installation
RMDir /r "$INSTDIR"
SetOutPath $INSTDIR
; Include all files from /build directory
File /r "${src}\*"
; Create start menu shortcut
CreateShortCut "$SMPROGRAMS\${productName}.lnk" "$INSTDIR\${exec}" "" "$INSTDIR\icon.ico"
WriteUninstaller "${uninstaller}"
SectionEnd
; --------------------------------
; Uninstaller
; --------------------------------
ShowUninstDetails nevershow
UninstallCaption "Uninstall ${productName}"
UninstallText "Don't like ${productName} anymore? Hit uninstall button."
UninstallIcon "${icon}"
UninstPage custom un.confirm un.confirmOnLeave
UninstPage instfiles
Var RemoveAppDataCheckbox
Var RemoveAppDataCheckbox_State
; Custom uninstall confirm page
Function un.confirm
nsDialogs::Create 1018
${NSD_CreateLabel} 1u 1u 100% 24u "If you really want to remove ${productName} from your computer press uninstall button."
${NSD_CreateCheckbox} 1u 35u 100% 10u "Remove also my ${productName} personal data"
Pop $RemoveAppDataCheckbox
nsDialogs::Show
FunctionEnd
Function un.confirmOnLeave
; Save checkbox state on page leave
${NSD_GetState} $RemoveAppDataCheckbox $RemoveAppDataCheckbox_State
FunctionEnd
; Uninstall declarations
Section "Uninstall"
DeleteRegKey HKLM "${uninstkey}"
DeleteRegKey HKLM "${regkey}"
Delete "$SMPROGRAMS\${productName}.lnk"
; Remove whole directory from Program Files
RMDir /r "$INSTDIR"
; Remove also appData directory generated by your app if user checked this option
${If} $RemoveAppDataCheckbox_State == ${BST_CHECKED}
RMDir /r "$LOCALAPPDATA\${name}"
${EndIf}
SectionEnd
Create a function called createInstaller
in your build.windows.js
file as follows:
function createInstaller() {
var deferred = Q.defer();
function replace(str, patterns) {
Object.keys(patterns).forEach(function (pattern) {
console.log(pattern)
var matcher = new RegExp('{{' + pattern + '}}', 'g');
str = str.replace(matcher, patterns[pattern]);
});
return str;
}
var installScript = projectDir.read('resources/windows/installer.nsi');
installScript = replace(installScript, {
name: manifest.name,
productName: manifest.name,
version: manifest.version,
src: buildDir.path(),
dest: projectDir.path(),
icon: buildDir.path('icon.ico'),
setupIcon: buildDir.path('icon.ico'),
banner: projectDir.path('resources/windows/banner.bmp'),
});
buildDir.write('installer.nsi', installScript);
var nsis = childProcess.spawn('makensis', [buildDir.path('installer.nsi')], {
stdio: 'inherit'
});
nsis.on('error', function (err) {
if (err.message === 'spawn makensis ENOENT') {
throw "Can't find NSIS. Are you sure you've installed it and"
+ " added to PATH environment variable?";
} else {
throw err;
}
});
nsis.on('close', function () {
deferred.resolve();
});
return deferred.promise;
}
You should have NSIS installed, and should be available in your path. creaeInstaller
function reads installer script and execute it against NSIS runtim with makensis
command.
Create a function to put all pieces together, then export it to be accessed from gulp task:
function build() {
return init()
.then(copyElectron)
.then(cleanupRuntime)
.then(createAsar)
.then(updateResources)
.then(rename)
.then(createInstaller);
}
module.exports = { build: build };
Next create gulp task in gulpfile.js
to execute this build script:
var release_windows = require('./build.windows');
var os = require('os');
gulp.task('build-electron', ['build'], function () {
switch (os.platform()) {
case 'darwin':
// execute build.osx.js
break;
case 'linux':
//execute build.linux.js
break;
case 'win32':
return release_windows.build();
}
});
You can have your final product by running:
- gulp build-electron
Your final electron application should be in dist
folder.
Electron is not just a native web view that let’s you to wrap your web application into desktop application . It now includes automatic app updates, Windows installers, crash reporting, notifications, and other useful native app features — all exposed through JavaScript APIs.
So far there is huge range of apps created using electron including chat apps, database explorers, map designers, collaborative design tools, and mobile prototyping apps.
Here is some useful resources about GitHub Electron:
Thanks for learning with the DigitalOcean Community. Check out our offerings for compute, storage, networking, and managed databases.
This textbox defaults to using Markdown to format your answer.
You can type !ref in this text area to quickly search our full set of tutorials, documentation & marketplace offerings and insert the link!