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 建立,包括聊天應用、數據庫管理器、地圖設計器、協做設計工具和手機原型等。