原文:Creating Desktop Applications With AngularJS and GitHub Electron
GitHub 的 Electron 框架(之前叫做 Atom Shell)许可你运用 HTML, CSS 和 JavaScript 编写跨平台的桌面运用。它是 io.js 运转时的衍生,专注于桌面运用而不是 web 服务端。
Electron 雄厚的原生 API 使我们能够在页面中直接运用 JavaScript 猎取原生的内容。
这个教程向我们展现了怎样运用 Angular 和 Electron 构建一个桌面运用。下面是本教程的一切步骤:
竖立一个简朴的 Electron 运用
运用 Visual Studio Code 编辑器治理我们的项目和使命
运用 Electron 开辟(原文为 Integrate)一个 Angular 主顾治理运用(Angular Customer Manager App)
运用 Gulp 使命构建我们的运用,并天生装置包
竖立你的 Electron 运用
早先,假如你的体系中还没有装置 Node,你须要先装置它。我们运用的构造以下所示:
这个项目中有两个 package.json 文件。
开辟运用
项目根目次下的 package.json 包含你的设置,开辟环境的依靠和构建剧本。这些依靠和 package.json 文件不会被打包到临盆环境构建中。运用运用
app 目次下的 package.json 是你运用的清单文件。因而每当在你须要为你项目装置 npm 依靠的时刻,你应当遵照这个 package.json 来举行装置。
package.json 的花样和 Node 模块中的完全一致。你运用的启动剧本(的途径)须要在 app/package.json 中的 main
属性中指定。
app/package.json
看起来是如许的:
{
name: "AngularElectron",
version: "0.0.0",
main: "main.js"
}
过实行 npm init
敕令离别竖立这两个 package.json
文件,也能够手动竖立它们。经由历程在敕令提示行里键入以下敕令来装置项目打包必要的 npm 依靠:
npm install --save-dev electron-prebuilt fs-jetpack asar rcedit Q
竖立启动剧本
app/main.js 是我们运用的进口。它担任竖立主窗口和处理体系事宜。main.js 应当以下所示:
// app/main.js
// 运用的掌握模块
var app = require('app');
// 竖立原生浏览器窗口的模块
var BrowserWindow = require('browser-window');
var mainWindow = null;
// 当一切窗口都封闭的时刻退出运用
app.on('window-all-closed', function () {
if (process.platform != 'darwin') {
app.quit();
}
});
// 当 Electron 完毕的时刻,这个要领将会见效
// 初始化并预备竖立浏览器窗口
app.on('ready', function () {
// 竖立浏览器窗口.
mainWindow = new BrowserWindow({ width: 800, height: 600 });
// 载入运用的 index.html
mainWindow.loadUrl('file://' + __dirname + '/index.html');
// 翻开开辟东西
// mainWindow.openDevTools();
// 窗口封闭时触发
mainWindow.on('closed', function () {
// 想要作废窗口对象的援用,假如你的运用支撑多窗口,
// 一般你须要将一切的窗口对象存储到一个数组中,
// 在这个时刻你应当删除响应的元素
mainWindow = null;
});
});
经由历程 DOM 接见原生
正如我上面提到的那样,Electron 使你能够直接在 web 页面中接见当地 npm 模块和原生 API。你能够如许竖立 app/index.html
文件:
<html>
<body>
<h1>Hello World!</h1>
We are using Electron
<script> document.write(process.versions['electron']) </script>
<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 是一个简朴的 HTML 页面。在这里,它经由历程运用 Node’s fs (file system) 模块来读取 package.json
文件并将其内容写入到 document body 中。
运转运用
一旦你竖立好了项目构造、app/index.html
、app/main.js
和 app/package.json
,你极可能想要尝试去运转初始的 Electron 运用来测试并确保它一般事情。
假如你已在体系中全局装置了 electron-prebuilt
,就可以够经由历程下面的敕令启动运用:
electron app
在这里,electron
是运转 electron shell 的敕令,app
是我们运用的目次名。假如你不想将 Election 装置到你全局的 npm 模块中,能够在敕令提示行中经由历程下面敕令运用当地 npm_modules
文件夹下的 electron 来启动运用。
"node_modules/.bin/electron" "./app"
只管你能够如许来运转运用,然则我照样发起你在 gulpfile.js
中竖立一个 gulp task,如许你就可以够将你的使命和 Visual Studio Code 编辑器相结合,我们会鄙人一部份展现。
// 猎取依靠
var gulp = require('gulp'),
childProcess = require('child_process'),
electron = require('electron-prebuilt');
// 竖立 gulp 使命
gulp.task('run', function () {
childProcess.spawn(electron, ['./app'], { stdio: 'inherit' });
});
运转你的 gulp 使命:gulp run
。我们的运用看起来会是这个模样:
设置 Visual Studio Code 开辟环境
Visual Studio Code 是微软的一款跨平台代码编辑器。VS Code 是基于 Electron 和 微软本身的 Monaco Code Editor 开辟的。你能够在这里下载到 Visual Studio Code。
在 VS Code 中翻开你的 electron 运用。
设置 Visual Studio Code Task Runner
有很多自动化的东西,像构建、打包和测试等。我们大多从敕令行中运转这些东西。VS Code task runner 使你能够将你自定义的使命集成到项目中。你能够在你的项目中直接运转 grunt,、gulp,、MsBuild 也许其他使命,这并不须要移步到敕令行。
VS Code 能够自动检测你的 grunt 和 gulp 使命。按下 ctrl + shift + p
然后键入 Run Task
敲击回车便可。
你将从 gulpfile.js
或 gruntfile.js
文件中猎取一切有效的使命。
注重:你须要确保
gulpfile.js
文件存在于你运用的根目次下。
ctrl + shift + b
会从你使命实行器(task runner)中实行 build
使命。你能够运用 task.json
文件来掩盖使命集成。按下 ctrl + shift + p
然后键入 Configure Task
敲击回车。这将会在你项目中竖立一个 .setting
的文件夹和 task.json
文件。如果你不止想要实行简朴的使命,你须要在 task.json
中举行设置。比方你也许想要经由历程按下 Ctrl + Shift + B
来运转运用,你能够如许编辑 task.json
文件:
{
"version": "0.1.0",
"command": "gulp",
"isShellCommand": true,
"args": [ "--no-color" ],
"tasks": [
{
"taskName": "run",
"args": [],
"isBuildCommand": true
}
]
}
根部份声明敕令为 gulp
。你能够在 tasks
部份写入你想要的更多使命。将一个使命的 isBuildCommand
设置为 true 意味着它和 Ctrl + Shift + B
举行了绑定。如今 VS Code 只支撑一个顶级使命。
如今,假如你按下 Ctrl + Shift + B
,gulp run
将会被实行。
你能够在这里浏览到更多关于 visual studio code 使命的信息。
调试 Electron 运用
翻开调试面板点击设置按钮就会在 .settings
文件夹内竖立一个 launch.json
文件,包含了调试的设置。
我们不须要启动 app.js 的设置,所以移除它。
如今,你的 launch.json
应当以下所示:
{
"version": "0.1.0",
// 设置列表。增加新的设置或变动已存在的设置。
// 仅支撑 "node" 和 "mono",能够转变 "type" 来举行切换。
"configurations": [
{
"name": "Attach",
"type": "node",
// TCP/IP 地点. 默许是 "localhost"
"address": "localhost",
// 竖立衔接的端口.
"port": 5858,
"sourceMaps": false
}
]
}
依据下面所示变动之前竖立的 gulp run
使命,如许我们的 electron 将会采纳调试形式运转,5858 端口也会被监听。
gulp.task('run', function () {
childProcess.spawn(electron, ['--debug=5858','./app'], { stdio: 'inherit' });
});
在调试面板中挑选 “Attach” 设置项,点击最先(run)也许按下 F5。稍等片刻后你应当就可以在上部看到调试敕令面板。
竖立 AngularJS 运用
第一次打仗 AngularJS?浏览官方网站或一些 Scotch Angular 教程。
这一部份会解说怎样运用 AngularJS 和 MySQL 数据库竖立一个主顾治理(Customer Manager)运用。这个运用的目标不是为了强调 AngularJS 的中心观点,而是展现怎样在 GiHub 的 Electron 中同时运用 AngularJS 和 NodeJS 以及 MySQL 。
我们的主顾治理运用正以下面如许简朴:
主顾列表
增加新主顾
挑选删除一个主顾
搜刮指定的主顾
项目构造
我们的运用在 app 文件夹下,目次构造以下所示:
主页是 app/index.html
文件。app/scripts
文件夹包含一切用在该运用中的症结剧本和视图。有很多要领能够用来构造运用的文件。
这里我更喜好依据功用来构造剧本文件。每一个功用都有它本身的文件夹,文件夹中有模板和掌握器。猎取更多关于目次构造的信息,能够浏览 AngularJS 最好实践: 目次构造
在最先 AngularJS 运用之前,我们将运用 bower 装置客户端方面的依靠。假如你还没有 Bower 先要装置它。在敕令提示行中将当前事情目次切换至你运用的根目次,然后遵照下面的敕令装置依靠。
bower install angular angular-route angular-material --save
设置数据库
在这个例子中,我将运用一个名字为 customer-manager
的数据库和一张名字为 customers
的表。下面是数据库的导出文件,你能够遵照这个疾速最先。
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`)
);
竖立一个 Angular Service 和 MySQL 举行交互
一旦你的数据库和表都预备好了,就可以够最先竖立一个 AngularJS service 来直接从数据库中猎取数据。运用 node-mysql
这个 npm 模块使 service 衔接数据库——一个运用 JavaScript 为 NodeJs 编写的 MySQL 驱动。在你 Angular 运用的 app/ 目次下装置 node-mysql
模块。
注重:我们将 node-mysql 模块装置到 app 目次下而不是运用的根目次,是因为我们须要在终究的 distribution 中包含这个模块。
在敕令提示行中切换事情目次至 app 文件夹然后依据下面所示装置模块:
npm install --save mysql
我们的 angular service —— app/scripts/customer/customerService.js 以下所示:
(function () {
'use strict';
var mysql = require('mysql');
// 竖立 MySql 数据库衔接
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
是一个简朴的自定义 angular service,它供应了对表 customers
的基本 CRUD 操纵。直接在 service 中运用了 node 模块 mysql
。假如你已具有了一个长途的数据服务,你也能够运用它来替代之。
掌握器 & 模板
app/scripts/customer/customerController 中的 customerController
以下所示:
(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;
// 载入初始数据
getAllCustomers();
//----------------------
// 内部要领
//----------------------
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];
});
}
}
}
})();
我们的主顾模板(app/scripts/customer/customer.html)运用了 angular material 组件来构建 UI,以下所示:
<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 包含模块初始化剧本和运用的路由设置,以下所示:
(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: '/' });
}
]);
})();
末了是我们的首页 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>
假如你已如上面那样设置过 VS Code task runner 的话,运用 gulp run
敕令也许按下 Ctrl + Shif + B
来启动你的运用。
构建 AngularJS 运用
为了构建我们的 Angular 运用,须要装置 gulp-uglify
, gulp-minify-css
和 gulp-usemin
依靠包。
npm install --save gulp-uglify gulp-minify-css gulp-usemin
翻开你的 gulpfile.js
而且引入必要的模块。
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');
假如构建目次已存在的话,清算一下它。
gulp.task('clean', function (callback) {
return destDir.dirAsync('.', { empty: true });
});
复制文件到构建目次。我们并不须要运用复制功用来复制 angular 运用的代码,鄙人一部份中 usemin
将会为我们做这件事请:
gulp.task('copy', ['clean'], function () {
return projectDir.copyAsync('app', destDir.path(), {
overwrite: true, matching: [
'./node_modules/**/*',
'*.html',
'*.css',
'main.js',
'package.json'
]
});
});
我们的构建使命将运用 gulp.src() 猎取 app/index.html 然后传递给 usemin。然后它会将输出写入到构建目次而且把 index.html 中的援用用优化版代码替代掉 。
注重: 万万不要忘记在 app/index.html 像如许定义 usemin 块:
<!-- 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 -->
构建使命以下所示:
gulp.task('build', ['copy'], function () {
return gulp.src('./app/index.html')
.pipe(usemin({
js: [uglify()]
}))
.pipe(gulp.dest('build/'));
});
为刊行(distribution)做预备
在这一部份我们将把 Electron 运用打包至临盆环境。在根目次竖立构建剧本 build.windows.js
。这个剧本用于 Windows 上。关于其他平台来讲,你应当竖立谁人平台特定的剧本而且依据平台来运转。
能够在 node_modules/electron-prebuilt/dist
目次中找到一个典范的 electron distribution。这里是构建 electron 运用的步骤:
我们主要的使命是复制 electron distribution 到我们的
dist
目次。每一个 electron distribution 都包含一个默许的运用在
dist/resources/default_app
中 。我们须要用我们终究构建的运用来替代它。为了庇护我们的运用源码和资本,你能够挑选将你的运用打包成一个 asar 归档,这会转变一点你的源码。一个 asar 归档是一个简朴的类似 tar 的花样,它会将你一切的文件拼接成单个文件,Electron 能够在不解压全部文件的情况下从中读取恣意文件。
注重:这一部份形貌的是 windows 平台下的打包。其他平台中的步骤是一样的,只是途径和运用的文件不一样罢了。你能够在 github 中猎取 OSx 和 linux 的完全构建剧本。
装置构建 electron 必要的依靠:npm install --save q asar fs-jetpack recedit
接下来,初始化我们的构建剧本,以下所示:
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() {
// 项目途径是运用的根目次
projectDir = jetpack;
// 构建目次是终究运用被构建后安排的目次
buildDir = projectDir.dir('./dist', { empty: true });
// angular 运用目次
appDir = projectDir.dir('./build');
// angular 运用的 package.json 文件
manifest = appDir.read('./package.json', 'json');
return Q();
}
这里我们运用 fs-jetpack
node 模块举行文件操纵。它供应了更天真的文件操纵。
复制 Electron Distribution
从 electron-prebuilt/dist
复制默许的 electron distribution 到我们的 dist 目次
function copyElectron() {
return projectDir.copyAsync('./node_modules/electron-prebuilt/dist', buildDir.path(), { overwrite: true });
}
清算默许运用
你能够在 resources/default_app
文件夹内找到一个默许的 HTML 运用。我们须要用我们本身的 angular 运用来替代它。依据下面所示移除它:
注重:这里的途径是针对 windows 平台的。关于其他平台历程是一致的,只是途径不一样罢了。在 OSX 中途径应当是 Contents/Resources/default_app
function cleanupRuntime() {
return buildDir.removeAsync('resources/default_app');
}
竖立 asar 包
function createAsar() {
var deferred = Q.defer();
asar.createPackage(appDir.path(), buildDir.path('resources/app.asar'), function () {
deferred.resolve();
});
return deferred.promise;
}
这将会把你 angular 运用的一切文件打包到一个 asar 包文件里。你能够在 dist/resources/
目次中找到 asar 文件。
替代为本身的运用资本
下一步是将默许的 electron icon 替代成你本身的,更新产物的信息然后重命名运用。
function updateResources() {
var deferred = Q.defer();
// 将你的 icon 从 resource 文件夹复制到构建文件夹下
projectDir.copy('resources/windows/icon.ico', buildDir.path('icon.ico'));
// 将 Electron icon 替代成你本身的
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;
}
// 重命名 electron exe
function rename() {
return buildDir.renameAsync('electron.exe', manifest.name + '.exe');
}
竖立原生装置包
你能够运用 wix 或 NSIS 竖立 windows 装置包。这里我们尽量运用更小更天真的 NSIS,它很合适收集运用。运用 NSIS 能够竖立支撑运用装置时须要的任何事情的装置包。
在 resources/windows/installer.nsis 中竖立 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
在 build.windows.js
文件中竖立一个叫做 createInstaller
的函数,以下所示:
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;
}
你应当装置了 NSIS,而且确保它在你的途径中是可用的。creaeInstaller
函数会读取装置包剧本而且遵照 NSIS 运转时运用 makensis
敕令来实行。
将他们组合到一同
竖立一个函数把一切的片断放在一同,为了使 gulp 使命能够猎取到然后输出它:
function build() {
return init()
.then(copyElectron)
.then(cleanupRuntime)
.then(createAsar)
.then(updateResources)
.then(rename)
.then(createInstaller);
}
module.exports = { build: build };
接着,在 gulpfile.js
中竖立 gulp 使命来实行这个构建剧本:
var release_windows = require('./build.windows');
var os = require('os');
gulp.task('build-electron', ['build'], function () {
switch (os.platform()) {
case 'darwin':
// 实行 build.osx.js
break;
case 'linux':
//实行 build.linux.js
break;
case 'win32':
return release_windows.build();
}
});
运转下面敕令,你应当就会获得终究的产物:
gulp build-electron
你终究的 electron 运用应当在 dist
目次中,而且目次构造应当和下面是类似的:
总结
Electron 不仅仅是一个支撑打包 web 运用成为桌面运用的原生 web view。它如今包含 app 的自动晋级、Windows 装置包、崩溃报告、关照和一些别的有效的原生 app 功用——一切的这些都经由历程 JavaScript API 挪用。
到如今为止,很大局限的运用运用 electron 竖立,包含谈天运用、数据库治理器、地图设想器、合作设想东西和手机原型等。
下面是 Github Electron 的一些有效的资本:
官方网站 – http://electron.atom.io/
Awesome Electron – https://github.com/sindresorhus/awesome-electron
Electron 运用榜样 – https://github.com/szwacz/electron-boilerplate
运用 ReactJs 的 Electron 榜样 – https://github.com/airtoxin/Electron-React-Boilerplate
本文中运用源码(译者注)