GitHub 的 Electron 框架(之前叫作 Atom Shell)容许你使用 HTML, CSS 和 JavaScript 编写跨平台的桌面应用。它是io.js 运行时的衍生,专一于桌面应用而不是 web 服务端。javascript
Electron 丰富的原生 API 使咱们可以在页面中直接使用 JavaScript 获取原生的内容。css
这个教程向咱们展现了如何使用 Angular 和 Electron 构建一个桌面应用。下面是本教程的全部步骤:html
建立一个简单的 Electron 应用java
使用 Visual Studio Code 编辑器管理咱们的项目和任务node
使用 Electron 开发(原文为 Integrate)一个 Angular 顾客管理应用(Angular Customer Manager App)mysql
使用 Gulp 任务构建咱们的应用,并生成安装包linux
起初,若是你的系统中尚未安装 Node,你须要先安装它。咱们应用的结构以下所示:git
这个项目中有两个 package.json 文件。angularjs
开发使用项目根目录下的 package.json 包含你的配置,开发环境的依赖和构建脚本。这些依赖和 package.json 文件不会被打包到生产环境构建中。github
应用使用app 目录下的 package.json 是你应用的清单文件。所以每当在你须要为你项目安装 npm 依赖的时候,你应该依照这个 package.json 来进行安装。
package.json 的格式和 Node 模块中的彻底一致。你应用的启动脚本(的路径)须要在 app/package.json 中的main属性中指定。
app/package.json看起来是这样的:
1
2
3
4
5
|
{
name:
"AngularElectron"
,
version:
"0.0.0"
,
main:
"main.js"
}
|
过执行npm init命令分别建立这两个package.json文件,也能够手动建立它们。经过在命令提示行里键入如下命令来安装项目打包必要的 npm 依赖:
1
|
npm install --save-dev electron-prebuilt fs-jetpack asar rcedit Q
|
app/main.js是咱们应用的入口。它负责建立主窗口和处理系统事件。 main.js 应该以下所示:
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
|
// 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
;
});
});
|
正如我上面提到的那样,Electron 使你可以直接在 web 页面中访问本地 npm 模块和原生 API。你能够这样建立app/index.html文件:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
<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 编辑器相结合,咱们会在下一部分展现。
1
2
3
4
5
6
7
8
9
|
// 获取依赖
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 是微软的一款跨平台代码编辑器。VS Code 是基于 Electron 和 微软自身的 Monaco Code Editor 开发的。你能够在 这里 下载到 Visual Studio Code。
在 VS Code 中打开你的 electron 应用。
有不少自动化的工具,像构建、打包和测试等。咱们大多从命令行中运行这些工具。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文件:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
{
"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 任务的信息。
打开调试面板点击配置按钮就会在.settings文件夹内建立一个launch.json文件,包含了调试的配置。
咱们不须要启动 app.js 的配置,因此移除它。
如今,你的launch.json应该以下所示:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
{
"version"
:
"0.1.0"
,
// 配置列表。添加新的配置或更改已存在的配置。
// 仅支持 "node" 和 "mono",能够改变 "type" 来进行切换。
"configurations"
: [
{
"name"
:
"Attach"
,
"type"
:
"node"
,
// TCP/IP 地址. 默认是 "localhost"
"address"
:
"localhost"
,
// 创建链接的端口.
"port"
: 5858,
"sourceMaps"
:
false
}
]
}
|
按照下面所示更改以前建立的 gulprun任务,这样咱们的 electron 将会采用调试模式运行,5858 端口也会被监听。
1
2
3
|
gulp.task(
'run'
,
function
() {
childProcess.spawn(electron, [
'--debug=5858'
,
'./app'
], { stdio:
'inherit'
});
});
|
在调试面板中选择 “Attach” 配置项,点击开始(run)或者按下 F5。稍等片刻后你应该就能在上部看到调试命令面板。
第一次接触 AngularJS?浏览 官方网站 或一些 Scotch Angular 教程 。
这一部分会讲解如何使用 AngularJS 和 MySQL 数据库建立一个顾客管理(Customer Manager)应用。这个应用的目的不是为了强调 AngularJS 的核心概念,而是展现如何在 GiHub 的 Electron 中同时使用 AngularJS 和 NodeJS 以及 MySQL 。
咱们的顾客管理应用正以下面这样简单:
顾客列表
添加新顾客
选择删除一个顾客
搜索指定的顾客
咱们的应用在 app 文件夹下,目录结构以下所示:
主页是app/index.html文件。app/scripts文件夹包含全部用在该应用中的关键脚本和视图。有许多方法能够用来组织应用的文件。
这里我更喜欢按照功能来组织脚本文件。每一个功能都有它本身的文件夹,文件夹中有模板和控制器。获取更多关于目录结构的信息,能够阅读 AngularJS 最佳实践: 目录结构
在开始 AngularJS 应用以前,咱们将使用 bower 安装客户端方面的依赖。若是你尚未 Bower 先要安装它。在命令提示行中将当前工做目录切换至你应用的根目录,而后依照下面的命令安装依赖。
1
|
bower install angular angular-route angular-material --save
|
在这个例子中,我将使用一个名字为customer-manager的数据库和一张名字为customers的表。下面是数据库的导出文件,你能够依照这个快速开始。
1
2
3
4
5
6
7
8
9
|
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`)
);
|
一旦你的数据库和表都准备好了,就能够开始建立一个 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 以下所示:
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
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
|
(
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以下所示:
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
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
|
(
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,以下所示:
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
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
|
<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 包含模块初始化脚本和应用的路由配置,以下所示:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
(
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
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
|
<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来启动你的应用。
为了构建咱们的 Angular 应用,须要安装gulp-uglify,gulp-minify-css和gulp-usemin依赖包。
1
|
npm install --save gulp-uglify gulp-minify-css gulp-usemin
|
打开你的gulpfile.js而且引入必要的模块。
1
2
3
4
5
6
7
8
9
10
|
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'
);
|
若是构建目录已经存在的话,清理一下它。
1
2
3
|
gulp.task(
'clean'
,
function
(callback) {
return
destDir.dirAsync(
'.'
, { empty:
true
});
});
|
复制文件到构建目录。咱们并不须要使用复制功能来复制 angular 应用的代码,在下一部分中usemin将会为咱们作这件事请:
1
2
3
4
5
6
7
8
9
10
11
|
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 块:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
<!-- 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 -->
|
构建任务以下所示:
1
2
3
4
5
6
7
|
gulp.task(
'build'
, [
'copy'
],
function
() {
return
gulp.src(
'./app/index.html'
)
.pipe(usemin({
js: [uglify()]
}))
.pipe(gulp.dest(
'build/'
));
});
|
在这一部分咱们将把 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
接下来,初始化咱们的构建脚本,以下所示:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
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-jetpacknode 模块进行文件操做。它提供了更灵活的文件操做。
从electron-prebuilt/dist复制默认的 electron distribution 到咱们的 dist 目录
1
2
3
|
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
1
2
3
|
function
cleanupRuntime() {
return
buildDir.removeAsync(
'resources/default_app'
);
}
|
1
2
3
4
5
6
7
|
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 替换成你本身的,更新产品的信息而后重命名应用。
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
|
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 脚本
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
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
|
!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的函数,以下所示:
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
|
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 任务能够获取到而后输出它:
1
2
3
4
5
6
7
8
9
10
|
function
build() {
return
init()
.then(copyElectron)
.then(cleanupRuntime)
.then(createAsar)
.then(updateResources)
.then(rename)
.then(createInstaller);
}
module.exports = { build: build };
|
接着,在gulpfile.js中建立 gulp 任务来执行这个构建脚本:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
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 建立,包括聊天应用、数据库管理器、地图设计器、协做设计工具和手机原型等。