译】使用 AngularJS 和 Electron 构建桌面应用

原文: Creating Desktop Applications With AngularJS and GitHub Electron

GitHub 的 Electron 框架(以前叫做 Atom Shell)允许你使用 HTML, CSS 和 JavaScript 编写跨平台的桌面应用。它是 io.js 运行时的衍生,专注于桌面应用而不是 web 服务端。

Electron 丰富的原生 API 使我们能够在页面中直接使用 JavaScript 获取原生的内容。

这个教程向我们展示了如何使用 Angular 和 Electron 构建一个桌面应用。下面是本教程的所有步骤:

  1. 创建一个简单的 Electron 应用

  2. 使用 Visual Studio Code 编辑器管理我们的项目和任务

  3. 使用 Electron 开发(原文为 Integrate)一个 Angular 顾客管理应用(Angular Customer Manager App)

  4. 使用 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.htmlmainWindow.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 } ]
}

按照下面所示更改之前创建的 gulprun任务,这样我们的 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-jetpacknode 模块进行文件操作。它提供了更灵活的文件操作。

复制 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 lzmaName "${productName}"Icon "${icon}"OutFile "${dest}"InstallDir "$PROGRAMFILES\${productName}"InstallDirRegKey HKLM "${regkey}" ""CRCCheck onSilentInstall normalXPStyle onShowInstDetails nevershowAutoCloseWindow falseWindowIcon offCaption "${productName} Setup"; Don't add sub-captions to title barSubCaption 3 " "SubCaption 4 " "Page custom welcomePage instfilesVar ImageVar ImageHandleFunction .onInit; Extract banner image for welcome pageInitPluginsDirReserveFile "${banner}"File /oname=$PLUGINSDIR\banner.bmp "${banner}"FunctionEnd; Custom welcome pageFunction welcomensDialogs::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 $ImageHandlensDialogs::Show${NSD_FreeImage} $ImageHandleFunctionEnd; Installation declarationsSection "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 installationRMDir /r "$INSTDIR"SetOutPath $INSTDIR; Include all files from /build directoryFile /r "${src}\*"; Create start menu shortcutCreateShortCut "$SMPROGRAMS\${productName}.lnk" "$INSTDIR\${exec}" "" "$INSTDIR\icon.ico"WriteUninstaller "${uninstaller}"SectionEnd; --------------------------------; Uninstaller; --------------------------------ShowUninstDetails nevershowUninstallCaption "Uninstall ${productName}"UninstallText "Don't like ${productName} anymore? Hit uninstall button."UninstallIcon "${icon}"UninstPage custom un.confirm un.confirmOnLeaveUninstPage instfilesVar RemoveAppDataCheckboxVar RemoveAppDataCheckbox_State; Custom uninstall confirm pageFunction un.confirmnsDialogs::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 $RemoveAppDataCheckboxnsDialogs::ShowFunctionEndFunction un.confirmOnLeave; Save checkbox state on page leave${NSD_GetState} $RemoveAppDataCheckbox $RemoveAppDataCheckbox_StateFunctionEnd; Uninstall declarationsSection "Uninstall"DeleteRegKey HKLM "${uninstkey}"DeleteRegKey HKLM "${regkey}"Delete "$SMPROGRAMS\${productName}.lnk"; Remove whole directory from Program FilesRMDir /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/

  • 官方文档 – https://github.com/atom/electron/tree/master/docs

  • Awesome Electron – https://github.com/sindresorhus/awesome-electron

  • Electron 应用样板 – https://github.com/szwacz/electron-boilerplate

  • 使用 ReactJs 的 Electron 样板 – https://github.com/airtoxin/Electron-React-Boilerplate

  • https://github.com/chentsulin/electron-react-boilerplate

转载于:https://www.cnblogs.com/sxz2008/p/6591197.html

AngularJS 和 Electron 构建桌面应用相关推荐

  1. 使用 AngularJS 和 Electron 构建桌面应用

    使用 AngularJS 和 Electron 构建桌面应用 GitHub 的 Electron 框架(以前叫做 Atom Shell)允许你使用 HTML, CSS 和 JavaScript 编写跨 ...

  2. 【译】使用 AngularJS 和 Electron 构建桌面应用

    原文:Creating Desktop Applications With AngularJS and GitHub Electron GitHub 的 Electron 框架(以前叫做 Atom S ...

  3. ZEGO教程:如何通过electron构建桌面跨平台音视频应用

    近年来,视频直播.短视频.在线教育.在线医疗.人工智能.以及VR等视频领域的相关行业都非常热门,成为大众瞩目的焦点.而5G网络的相继普及,移动网速飞速提升,又将引起下一轮视频应用的革命. 可以看到,在 ...

  4. 使用 Electron 构建桌面应用

    作者:kmokidd 链接:https://zhuanlan.zhihu.com/p/20225295 来源:知乎 著作权归作者所有.商业转载请联系作者获得授权,非商业转载请注明出处. 译者注:前一段 ...

  5. 使用 Electron 构建桌面应用程序的介绍

    事实上的团队协作软件,Visual Studio Code - 在撰写本文时市场上最受欢迎的代码编辑器之一,以及WhatsApp的桌面版本都有一个共同点:它们都是用Electron构建的.js.随着这 ...

  6. 利用Electron构建桌面应用

    只需五分钟,就会Electron. 关于Electron Electron是由Github开发,用HTML,CSS和JavaScript来构建跨平台桌面应用程序的一个开源库. Electron通过将C ...

  7. html getelementbyid 修改图片_如何使用HTML、CSS和JS轻松构建桌面应用程序

    HTML,CSS和JavaScript语言是否可以真正用于构建桌面应用程序? 答案是肯定的. 在这篇文章中,我们将主要关注Electron如何使用HTML.CSS和Javascript等网络技术来创建 ...

  8. Electron开发桌面应用

    由 Bazzzinga威 同学翻译自 Medium.https://medium.com/developers-writing/building-a-desktop-application-with- ...

  9. pywebview:使用python构建桌面客户端应用

    前言 我编写了一些python程序,但我并不满足与只用命令行运行,我希望能构建一个具有界面的桌面客户端应用,来运行我python程序的各种功能. 作为一个前端工程师,我最熟悉的当然就是Electron ...

最新文章

  1. HTML5权威指南--Web Storage,本地数据库,本地缓存API,Web Sockets API,Geolocation API(简要学习笔记二)...
  2. IM 融云 之 初始化及登录
  3. linux 0660 权限,CentOS 6上安装RAC权限绑定问题
  4. webpack代理配置打包后接口404_webpack 从零开始
  5. 拉链法导致的链表过深问题为什么不用二叉查找树代替,而选择红黑树?为什么不一直使用红黑树?
  6. python元编程_python元编程详解(3)
  7. bootstrap table相关操作
  8. 代码批量加引号_如何用Word批量制作员工工作证?1分钟搞定1000份!只需三步
  9. 图像有用区域(广搜)
  10. java 根据类名示例化类_Java收集器类– 18个示例
  11. shell学习之定时运行作业
  12. 静态成员变量.xml
  13. 计算机培训短期速成班,【电脑基础班、速成班、短期班、计算机一级培训班】价格,厂家,电子商务-搜了网...
  14. ldap 统一认证 java_LDAP统一用户认证
  15. 川农《组织行为学(本科)》21年12月作业考核
  16. Android吃鸡 3dtouch,绝地求生刺激战场3Dtouch怎么用 刺激战场3Dtouch吃鸡技巧详解
  17. 朗润外盘国际期货:ChatGPT这个人工智能有点东西
  18. 如何写一个vscode插件
  19. 产品经理如何开好需求评审会
  20. Blazor组件自做十三: VideoPlayer 视频播放器

热门文章

  1. Java:JSON扁平化和去扁平化
  2. 人是Web3最终进化
  3. 34.14. 切换字符集
  4. win7怎么打开微软更新服务器地址,Win7旗舰版上的windows update服务无法启动
  5. GSM系统信令接续流程(一)(转)
  6. 《自然》杂志发布2018年度影响世界的十大科学人物,中国神童入榜...
  7. 基于规则的中文地名识别系统的设计与实现
  8. 无线授时服务器接LED屏,实现ipad远程无线控制led大屏幕分以下几个步骤!
  9. 【C++】类的6个默认成员函数详解
  10. 计算机硬盘做u盘启动不了,u盘启动盘读不出来硬盘如何解决_电脑U盘启动找不到硬盘的解决教程...