浅谈Nodejs开发桌面应用 针对 Electron

electron.js 是 github 发布跨平台桌面应用开发工具,基于 web 技术。


基本假设

开始教程之前,请允许我假设你已经有了一个常用的的编辑器(或者 IDE),系统中也安装了Node.js 和 npm,并有基础的 HTML/CSS/JavaScript (对 Node.js 的 CommonJS 模块概念有所了解是最好,但不强求) 知识。如果以上知识你并不了解,为了防止这篇文章看到你头昏脑胀,推荐你先看看之前我写过的博文,补充一下基础知识。

万事俱备,现在就把精力集中在学习 Electron 上,不要再担心界面的事情(将会构建的界面本质上就是普通的 Web 页面而已)。

Electron 概览

简而言之,Electron 提供了一个实时构建桌面应用的纯 JavaScript 环境。Electron 可以获取到你定义在 package.json 中 main 文件内容,然后执行它。通过这个文件(通常我们称之为 main.js),可以创建一个应用窗口,这个应用窗口包含一个渲染好的 web 界面,还可以和系统原生的 GUI 交互。

具体来说,就是当你启动了一个 Electron 应用,就有一个主进程(main process )被创建了。这条进程将负责创建出应用的 GUI(也就是应用的窗口),并处理用户与这个 GUI 之间的交互。

但直接启动 main.js 是无法显示应用窗口的,在 main.js 中通过调用BrowserWindow模块才能将使用应用窗口。然后每个浏览器窗口将执行它们各自的渲染器进程( renderer process )。渲染器进程将会处理一个真正的 web 页面(HTML + CSS + JavaScript),将页面渲染到窗口中。鉴于 Electron 使用的是基于Chrominum的浏览器内核,你就不太需要考虑兼容的问题。


举个例子,如果你只想做一个计算器,那你的 main process 只会做一件事情:实例化一个窗口,并内置了一个计算器的界面(这个界面是你用 HTML、CSS 和 JavaScript 写的)。

虽然理论上只有 main process 才能和原生 GUI 产生交互,但其实我们可以通过一些手段让 renderer process 与原生 GUI 交互(在后文中你将学习到如何实现)。

main process 可以通过 Electron 中的一些模块直接和原生 GUI 交互。你的桌面应用可以使用任意的 Node 模块,比如用node-notifier显示系统通知,用request发出 HTTP 请求……

Hello, world!

做好前期准备,现在让我们从 Hello World 开始吧!

使用的 repo

这篇教程是基于一个声效器教程的github 仓库,请使用下面的命令将它克隆到本地:

git clone https://github.com/bojzi/sound-machine-electron-guide.git

然后查看一下,你可以看看这个仓库中有哪些 tag:

git checkout

我们将跟随这些 tag 将声效器一步步构建出来:

git checkout 00-blank-repository

拉取(checkout)目标 tag 之后,执行:

npm install

这么做能保证项目所依赖的 Node 模块都会被拉取。

如果你无法切换到某一个 tag,最简单的解决方式就是重置仓库,然后再 checkout:

git add -A

git reset --hard

开工

先把 tag 为 ‘00-blank-repository’ 拉取下拉:

git checkout 00-blank-repository

在项目文件夹中创建一个 package.json 文件,并在文件中加入以下内容:

{

"name": "sound_machine",

"version": "0.1.0",

"main": "./main.js",

"scripts": {

"start": "electron ."

}

}

这个 package.json 的作用是:

确定应用的名字和版本号,

告诉 Electron main.js 是 main process 的入口,

定义启动口令 - 在 CLI (终端或者命令行)中执行 npm start 即可完成依赖安装。

现在快把 Electron 安装上吧。最简单的安装方式应该是通过 npm 安装预构建好的二进制文件,然后把它作为开发依赖(development dependency)写入 package.json 中(安装时带上 --save-dev 参数即可自动写入依赖)。在 CLI 中进入项目目录,执行下面的命令:

npm install --save-dev electron-prebuilt

预构建的二进制文件会根据操作系统不同而不同的,通过执行 npm start 安装。我们以开发依赖的方式使用它,是因为在项目构建中只有在开发阶段才会使用到 Electron。

以上,就是本次 Electron 教程所需要的全部东西了。

对世界说 Hi

创建一个 app 文件夹,在文件夹中新建 index.html 文件,并写入以下内容:

Hello, world!

在项目的根目录创建 main.js 文件。Electron 主线程的入口是这个 JS 文件,然后 “Hello world!” 页面也通过它显示出来:

'use strict';varapp=require('app');varBrowserWindow=require('browser-window');varmainWindow=null;app.on('ready',function(){mainWindow=newBrowserWindow({height:600,width:800});mainWindow.loadUrl('file://'+__dirname+'/app/index.html');});

看起来并不难吧?

app 模块控制着应用的生命周期(比如,当应用进入准备状态(ready status)的时候要采取什么行动)。

BrowserWindow 模块控制窗口的创建。

mainWindow 对象就是你的应用窗口的主界面,当 JavaScript 垃圾回收机制被触发时窗口就会被关闭,此时该对象的值是null。

当 app 获取到 ready 事件后,我们通过 BrowserWindow 创建一个 800x600 窗口。

这个 window 的渲染器线程将会渲染 index.html 文件。

执行下面这行代码,看看我们的应用是什么样的:

npm start

现在沐浴在这个 app 的圣光中吧。

开发一个真正的应用

华丽丽的声效器

开始之前,我要问个问题:什么是声效器?

声效器是一个小设备,当你按下不同按键的时候,它会发出不同声音,比如卡通音或者效果音。在办公室里听到这样有趣的声音,好像整个人都明亮起来了呢。用这个例子作为探索如何使用 Electron 是个很棒的主意。

具体来说,我们将会实现以下功能,并涉及到以下知识:

声效器的基础(实例化浏览器窗口),

关闭声效器(主进程和渲染器进程之间的通信),

随时播放声音(全局快捷键),

创建一个快捷修饰键(修饰键,modifier keys, 指的是 Shift、Ctrl 和 Alt 键)设置页面(并将用户设置保存在主目录下),

添加一个托盘图标(创建原生 GUI 元素、了解菜单和托盘图标的使用),

将应用打包到 Mac、Windows 和 Linux 平台。

实现声效器的基本功能

开始构建以及应用的结构

在开发过 “Hello World” 应用之后,现在可以着手制做我们的声效器了。

一个典型的声效器会有很多的按钮,你需要按下那些按钮才能让机器发声,通常会是拟声词(比如笑声、掌声、打碎玻璃的声音等等)。

响应点击 – 这是我们要做的第一件事情。

我们的应用结构非常简单直白。

在应用的根目录中,要有一个 package.json、main.js 和其他全局所需的应用文件。

app/ 目录中要包含 HTML 文件、CSS 目录、JS 目录、wav 目录还有图片目录。

出于简化这个教程的目的,所有和网页设计相关的文件都已经在一开始就放在仓库中了。请在命令行中输入git checkout 01-start-project 获取。现在,请你可以输入以下命令,重置你的仓库并拉取新的 tag:

If you followed along with the "Hello, world!" example:

git add -A

git reset --hard

Follow along with the tag 01-start-project:

git checkout 01-start-project

在本教程中,我们只使用两种声效,后面再找一些别的音效和图标,修改* index.js *就将它们扩展成有16种音效的声效器。

main process 的其他内容

首先找到 main.js 中定义声效器外形的部分,用下面这段替换掉:

'use strict';varapp=require('app');varBrowserWindow=require('browser-window');varmainWindow=null;app.on('ready',function(){mainWindow=newBrowserWindow({frame:false,height:700,resizable:false,width:368});mainWindow.loadUrl('file://'+__dirname+'/app/index.html');});

当窗口被定义了大小,我们也就是在自定义这个窗口,使得它不可拉伸没有框架,让它看起来就像一个真正的声效器浮在桌面上。

现在问题来了 – 要如何移动或者关闭一个没有标题栏的窗口。

很快我就会说到自定义窗口(和应用)的关闭动作,还会谈到如何在主进程和渲染器进程中通信。不过现在让我们先把目光聚焦到“拖拽效果”上。你可以在 app/css 目录下找到 index.css 文件:

html,body{...-webkit-app-region:drag;...}

-webkit-app-region: drag;把整个 html 都变成了一个可拖拽的对象。现在问题来了,在可拖拽的对象上你怎么点击啊?!好的,可能你会想到把 html 中某个部分的这个属性值设置为no-drag;,那就允许该元素不可拖拽(但可以点击了)。让我们想想下面这段 index.css 片段:

.button-sound {

...

-webkit-app-region: no-drag;

}

展示声效器

现在通过 main.js 文件可以创建一个新窗口,并在窗口中显示出声效器的界面。如果通过npm start启动应用,你将会看到一个有动态效果的声效器。因为我们就是从一个静态页面开始,所以现在你看到的也是不会动的页面:

将下面这段代码保存到 index.js 文件中(位置在 app/js 目录下),运行后应用后,你会发现可以与声效器交互了:

'use strict';varsoundButtons=document.querySelectorAll('.button-sound');for(vari=0;i

通过上面这段代码,我们:

获取声音按钮,

迭代访问按钮的data-sound属性值,

给每个按钮加上背景图,

通过HTMLAudioElement 接口给每个按钮都添加一个点击事件,使之可以播放音频,

通过下面这行命令运行你的应用吧:

npm start

通过远程事件从浏览窗口中关闭应用

接着拉取02-basic-sound-machine的内容:

git checkout 02-basic-sound-machine

简单来说 - 应用窗口(渲染器进程)不应该和 GUI 发生交互(也就是不应该和“关闭窗口”有关联),Electron 的官方教程上说了:

考虑到在网页中直接调用原生的 GUI 容易造成资源溢出,这很危险,开发者不能这么使用。如果开发者想要在网页上执行 GUI 操作,必须要通过渲染器进程和主进程的通信实现。

Electron 为主进程和渲染器进程提供了 ipc (跨进程通信)模块,ipc 模块允许接收和发送通信频道的信息。频道由字符串表示(比如“channel-1”,“channel-2”这样),可以用于区分不同的信息接收者。传递的信息中也可以包含数据。根据接收到的信息,订阅者可以做出响应。信息传递的最大好处就是做到分离任务 – 主进程不需要知道是哪些渲染器进程发送了信息。

这正是我们想要做的 – 将主进程(main.js)订阅到“关闭主窗口”频道中,当用户点击关闭按钮时,从渲染器进程(index.js)向该频道发送信息。

Add the following to main.js to subscribe to a channel:

将下面的代码实现了频道订阅,将它添加到 main.js 中:

varipc=require('ipc');ipc.on('close-main-window',function(){app.quit();});

把 ipc 模块包含进来之后,从频道中订阅信息就非常简单了:过 on() 方法和频道名称,再加上一个回调函数就行了。

要向该频道发送信息,就要把下面的代码加入 index.js 中:

varipc=require('ipc');varcloseEl=document.querySelector('.close');closeEl.addEventListener('click',function(){ipc.send('close-main-window');});

我们依然需要把 ipc 模块引入到文件中,给关闭按钮绑定点击事件。当点击了关闭按钮时,通过 send() 方法发送一条信息到“关闭主窗口”频道。

不要忘记在在 index.css 中将关闭按钮设置为不可拖拽:

.settings{...-webkit-app-region:no-drag;}

就这样,我们的应用现在可以通过按钮关掉了。ipc 的通信可以通过事件和参数的传递变得很复杂,在后文中会有传递参数的例子。

通过全局快捷键播放声音

拉取03-closable-sound-machine:

git checkout 03-closable-sound-machine

声效器的地基已经打的不错。但是我们还面临着使用性的问题 – 这个应用要始终保持在桌面最前方,且可以被重复点击。

这就是全局快捷键要介入的地方。Electron 提供了全局快捷模块(global shortcut module)允许开发者捕获组合键并做出相应的反应。在 Eelctron 中组合键被称为加速器,它以字符串的形式被记录下(比如 “Ctrl+Shift+1”)。

因为我们想要捕获到原生的 GUI 事件(全局快捷键),并执行应用窗口事件(播放声音),我们将使用 ipc 模块从主进程发送信息到渲染器进程。

在看代码之前,还有两件事情要我们考虑:

全局快捷键会在 app 的 ready 事件被触发后注册(相关代码片段要被包含在 ‘ready’ 中)

通过 ipc 模块从主进程向渲染器进程发送信息,你必须使用窗口对象的引用(类似于createdWindow.webContents.send(‘channel’))。

记住上面的两点了吗?现在让我们来改写* main.js *吧:

var globalShortcut = require('global-shortcut');

app.on('ready', function() {

... // 之前写过的代码

globalShortcut.register('ctrl+shift+1', function () {

mainWindow.webContents.send('global-shortcut', 0);

});

globalShortcut.register('ctrl+shift+2', function () {

mainWindow.webContents.send('global-shortcut', 1);

});

});

首先,要先引入 global-shortcut 模块,当应用进入ready状态之时,我们将会注册两个快捷键 – ‘Ctrl+Shift+1’ 和 ‘Ctrl+Shift+2’。这两个快捷键可以通过不同的参数向“全局快捷键”频道( “global-shortcut”channel)发送信息。通过参数匹配到到底要播放哪种声音,将下面的代码加入 index.js 中:

ipc.on('global-shortcut', function (arg) {

var event = new MouseEvent('click');

soundButtons[arg].dispatchEvent(event);

});

为了保证整个架构足够简单,我们将会用 soundButtons 选择器模拟按钮的点击播放声音。当发送的信息是“1”时,我们将会获取 soundButtons[1] 元素,触发鼠标点击事件(注意:在生产环境中的应用,你需要封装好播放声音的代码,然后执行它)。

在新窗口中通过用户设置配置 modifier keys

下面请拉取04-global-shortcuts-bound:

git checkout 04-global-shortcuts-bound

通常我们会同时运行好多个应用,声效器中设置的快捷键很可能已经被占用了。所以现在要引入一个设置界面,允许用户更改修饰键(modifier keys)的原因(Ctrl、Alt 和 Shift)。

要完成这一个功能,我们需要做下面这些事情:

在主界面上添加设置按钮,

实现一个设置窗口(设置页面上有对应的HTML、CSS 和 JS),

开启和关闭设置窗口,以及更新全局快捷键的 ipc 信息,

从用户的系统中读写存储设置信息的 JSON 文件。

piu~ 以上就是我们要做的。

设置按钮和设置窗口

和关闭主窗口类似,我们将会把事件绑定到设置按钮上,(settings button),在* index.js *中加入发送给频道的信息:

var settingsEl = document.querySelector('.settings');

settingsEl.addEventListener('click', function () {

ipc.send('open-settings-window');

});

当点击了设置按钮,将会有一条信息向“打开设置窗口”这个频道发送。* main.js 可以响应这个事件,并打开一个新窗口,将以下代码加入 main.js *中:

var settingsWindow = null;

ipc.on('open-settings-window', function () {

if (settingsWindow) {

return;

}

settingsWindow = new BrowserWindow({

frame: false,

height: 200,

resizable: false,

width: 200

});

settingsWindow.loadUrl('file://' + __dirname + '/app/settings.html');

settingsWindow.on('closed', function () {

settingsWindow = null;

});

});

这一步和之前的类似,我们将会打开一个新的窗口。唯一的不同点就是,为了防止实例化两个一样的对象,我们将会检查设置窗口是否已经被打开了。

当上述代码成功执行之后,我们需要再添加一个关闭设置窗口的动作。类似的,我们需要向频道中发送一条信息,但这次是从* settings.js 中发送(关闭按钮的事件是在 settings.js 中)。新建 settings.js *文件,并添加以下代码(如果已经有该文件,就直接在原文件中添加):

'use strict';

var ipc = require('ipc');

var closeEl = document.querySelector('.close');

closeEl.addEventListener('click', function (e) {

ipc.send('close-settings-window');

});

在 main.js 中监听该频道:

ipc.on('close-settings-window', function () {

if (settingsWindow) {

settingsWindow.close();

}

});

现在,设置窗口已经可以实现我们的逻辑了。

用户设置的读写

执行05-settings-window-working:

git checkout 05-settings-window-working

设置窗口的交互过程是,存储设置信息以及刷新应用:

创建一个 JSON 文件用于读写用户设置,

用这个设置初始化设置窗口,

通过用户的操作更新这个设置文档,

通知主进程要更新设置页面。

我们可以把实现读写设置的部分直接写进 main.js 中,但是如果把这部分独立成模块,可以随处引用这样不是更好吗?

使用 JSON 做配置文件

现在我们要创建一个 configuration.js 文件,再将这个文件引入到项目中。Node.js 使用了CommonJS作为编写模块的规范,也就是说你需要将你的 API 和这个 API 中可用的函数都要暴露出来。

为了更简单地读写文件,我们将会使用 nconf 模块,这个模块封装了 JSON 文件的读写。但首先,我们需要将这个模块包含到项目中来:

npm install --save nconf

这行命令意味着 nconf 模块将会作为应用依赖被安装到项目中,当我们要发布应用的时候,这个模块会被一起打包给用户(save-dev 参数会使安装的模块只出现在开发阶段,发布应用的时候不会被包含进去)。

在根目录下创建 configuration.js 文件,它的内容非常简单:

'use strict';varnconf=require('nconf').file({file:getUserHome()+'/sound-machine-config.json'});functionsaveSettings(settingKey,settingValue){nconf.set(settingKey,settingValue);nconf.save();}functionreadSettings(settingKey){nconf.load();returnnconf.get(settingKey);}functiongetUserHome(){returnprocess.env[(process.platform=='win32')?'USERPROFILE':'HOME'];}module.exports={saveSettings:saveSettings,readSettings:readSettings};

我们要把文件位置和文件名传 nconf 模块(用 Node.js 的 process.env 获取到文件的位置),具体路径会根据平台而异。

通过 nconf 模块的 set() 和 get() 方法结合文件操作的 save() 和 load(),我们可以实现设置文件的读写操作,然后通过 module.exports 将接口暴露到外部。

初始化默认的快捷键设置

在讲设置交互之前,为了避免用户是第一次打开这个应用,要先初始化一个设置文件。我们将会以数组的形式储存热键,对应的键是 “shortcutKeys”,储存在 main.js 中,我们需要把 configuration 模块包含到项目中:

'use strict';varconfiguration=require('./configuration');app.on('ready',function(){if(!configuration.readSettings('shortcutKeys')){configuration.saveSettings('shortcutKeys',['ctrl','shift']);}...}

我们需要先检测键 ‘shortcutKeys’ 是否已经有对应的值了,如果没有我们需要初始化一个值。

在 main.js 中,我们将重写全局快捷键的注册方法,在之后我们更新设置的时候,会直接调用这个方法。将原来的注册代码改成以下内容:

app.on('ready', function () {

...

setGlobalShortcuts();

}

function setGlobalShortcuts() {

globalShortcut.unregisterAll();

var shortcutKeysSetting = configuration.readSettings('shortcutKeys');

var shortcutPrefix = shortcutKeysSetting.length === 0 ? '' : shortcutKeysSetting.join('+') + '+';

globalShortcut.register(shortcutPrefix + '1', function () {

mainWindow.webContents.send('global-shortcut', 0);

});

globalShortcut.register(shortcutPrefix + '2', function () {

mainWindow.webContents.send('global-shortcut', 1);

});

}

上述方法重置了全局快捷键的值,从设置中读取热键的数组,将它传入加速器兼容字符串(Accelerator-compatible)并注册新键。

设置窗口的交互

回到 settings.js 文件,我们需要绑定点击事件来改变我们的全局快捷键。首先,我们将会遍历复选框,记录下被勾选的选项(从 configuration 模块中读值):

varconfiguration=require('../configuration.js');varmodifierCheckboxes=document.querySelectorAll('.global-shortcut');for(vari=0;i

现在我们需要绑定复选框的行为。考虑到设置窗口(和它的渲染器进程)是不允许改变 GUI 绑定的。这说明我们需要从 setting.js 中发送信息(之后会处理这个信息的):

for (var i = 0; i < modifierCheckboxes.length; i++) {

...

modifierCheckboxes[i].addEventListener('click', function (e) {

bindModifierCheckboxes(e);

});

}

function bindModifierCheckboxes(e) {

var shortcutKeys = configuration.readSettings('shortcutKeys');

var modifierKey = e.target.attributes['data-modifier-key'].value;

if (shortcutKeys.indexOf(modifierKey) !== -1) {

var shortcutKeyIndex = shortcutKeys.indexOf(modifierKey);

shortcutKeys.splice(shortcutKeyIndex, 1);

}

else {

shortcutKeys.push(modifierKey);

}

configuration.saveSettings('shortcutKeys', shortcutKeys);

ipc.send('set-global-shortcuts');

}

这段代码看起来比较长,但事实上它很简单。我们将会遍历所有的复选框,并绑定点击事件,在每次点击的时候检查设置数组中是否包含有热键。根据检查结果,更改数组,将结果保存到设置中,并向主进程发送信息,更新我们的全局快捷键。

现在的工作就是在 main.js 中将 ipc 信息订阅到“设置全局快捷键”频道,并更新我们的全局快捷键:

ipc.on('set-global-shortcuts',function(){setGlobalShortcuts();});

就这么简单,我们的全局快捷键已经可配置了!

菜单中要放什么?

接下来拉取 06-shortcuts-configurable:

git checkout 06-shortcuts-configurable

另一个在桌面应用中的重要概念就是“菜单”,比如右键菜单(点击右键出现的菜单)、托盘菜单(通常会有一个托盘 icon)和应用菜单(在 OS X 中)等等。

在这一节中,我们将会添加一个托盘菜单。我们也将会借此机会尝试在remote 模块中使用别的进程间的通信方式。

remote 模块从渲染器进程到主进程完成 RPC 类型的调用。将模块引入的时候,这个模块是在主进程中被实例化的,所以它们的方法也会在主进程中被执行。实际开发中,这个行为是在远程地请求 index.js 中的原生 GUI 模块,然后又在 main.js 中调用 GUI 的方法。这么做的话,你需要在 index.js 中将 BrowserWindow 模块引入,然后实例化一个新的浏览器窗口。其实在主进程中有一个同步的调用,实际上是这个调用创建了新的浏览器窗口。

现在让我们来看看要怎么样创建一个菜单,并在渲染器进程中将它绑定到一个托盘图标上。将下面这段代码加入 index.js 中:

varremote=require('remote');varTray=remote.require('tray');varMenu=remote.require('menu');varpath=require('path');vartrayIcon=null;if(process.platform==='darwin'){trayIcon=newTray(path.join(__dirname,'img/tray-iconTemplate.png'));}else{trayIcon=newTray(path.join(__dirname,'img/tray-icon-alt.png'));}vartrayMenuTemplate=[{label:'Sound machine',enabled:false},{label:'Settings',click:function(){ipc.send('open-settings-window');}},{label:'Quit',click:function(){ipc.send('close-main-window');}}];vartrayMenu=Menu.buildFromTemplate(trayMenuTemplate);trayIcon.setContextMenu(trayMenu);

原生的 GUI 模块(菜单和托盘)通过remote模块包含进来比较安全。

OS X 支持图片模板(将图片文件名以 ‘Template’ 结尾,就会被定义成为图片模板),托盘图标可以通过模板来定义,这样我们的图标就会有“暗黑”和“光明”两个主题了。其他的操作系统用正常的图标就行。

在 Electron 中有很多绑定菜单的方法。这里介绍的方法只是创建了一个菜单模板(将菜单项用数组的方式存储),然后通过这个模板创建菜单,托盘 icon 再绑定上这个菜单,就实现了我们的菜单功能。

应用打包

接下来拉取 07-ready-for-packaging:

git checkout 07-ready-for-packaging

如果你做了一个应用结果人们连下载都下载不了,怎么会有人用呢?

通过electron-packager你可以将应用打包到全平台。这一步骤在 shell 中就可以完成,将应用打包好以后就能发布了。

它可以作为一个命令行应用或者作为开发应用过程中的一步,构建一个更复杂的开发场景不是这篇文章要谈的内容,不过我们将通过 npm 脚本让应用打包更简单一点。用 electron-packager 打包的命令是这样的:

electron-packager

以上命令:

将目录切换到项目所在路径,

参数 ‘name of project’ 是你的项目名,参数 ‘plateform’ 确定了你要构建哪个平台的应用(Windows、Mac 还是 Linux),

参数 ‘architecture’ 决定了使用 x86 还是 x64 还是两个架构都用,

决定了使用的 Electron 版本。

第一次打包应用需要比较久的时间,因为所有平台的二进制文件都需要下载,之后打包应用会比较快了。

在 Mac 上我是这么做的:

electron-packager ~/Projects/sound-machine SoundMachine --all --version=0.30.2 --out=~/Desktop --overwrite --icon=~/Projects/sound-machine/app/img/app-icon.icns

首先你要将图标的格式转换成 .icns(在 Mac 上)或者 .ico(在 Windows 上),网络上有工具可以把 PNG 做这样的转换(确保下载的图片的扩展名是 .icns 而不是 .hqx)。如果从非 Windows 的系统上打包了 Windows 的应用,你应该需要处理一下路径(Mac 用户可以用 brew,Linux 用户可以用 apt-get)。

每次都要执行这么长的一句命令一点都不合理。所以你可以在 package.json 中添加另一个脚本。首先,将electron-packager 作为 development dependency 安装:

npm install --save-dev electron-packager

然后在 package.json 中添加以下内容:

"scripts":{"start":"electron .","package":"electron-packager ./ SoundMachine --all --out ~/Desktop/SoundMachine --version 0.30.2 --overwrite --icon=./app/img/app-icon.icns"}

接着执行:

npm run-script package

打包命令启动了 electron-packager,在当前目录中查看项目,在 Desktop 目录中构建。如果你使用的是 Windows,脚本内容需要一些细微的更新。

声效器目前是 100MB 大小,不要担心,当你压缩它之后,所占空间会减半。

如果你对此还有更大的计划,可以看看electron-builder,它是根据 electron-packager 构建出的应用打包再做自动安装的处理。

添加其他的特性

现在你可以尝试开发别的功能了。

这里有一些方案,可以启发你的灵感:

应用的使用手册,说明了有那些快捷键和应用作者,

在应用中给使用手册添加一个图标和菜单入口,

构建一个打包脚本,用于快速构建和分发,

使用* node-notifier *添加一个提示系统,告诉用户正在播放哪一个声音,

使用* lodash *让你的代码更加干净、具有更好的扩展性,

在打包之前不要忘了压缩你的 CSS 和 JavaScript,

结合上文提到的* node-notifier *和一个服务器端的调用,通知用户是否需要更新版本……

还有一个值得一试的东西 – 将代码中关于浏览器窗口的逻辑抽离出来,通过类似 browserify 的工具创建一个和声效器一样的网页。一份代码,两个产品(桌面端和 Web 引用)。酷毙了!

更深入研究 Electron

我们只是尝试了 Electron 的冰山一角,想要知道监控主机的电源情况、获取当前窗口的信息(比如光标的位置)等,Eletron 都能帮你做到。

对于所有的内置工具(通常在开发 Electron 应用时使用),查看Electron API 文档

这些文档在 Electron 的 github 仓库中都能找到。

Sindre Sorhus 正在维护一份Electron 资源清单,在那个上面你可以看到很多非常酷的项目,还能了解到一些系统架构做的很好的 Electron 应用,这些都能给你的开发带来灵感。

Electron 是基于 io.js 的,大部分 Node.js 模块都可以兼容,可以使用它们扩展你的应用。去npmjs.com上看看有没有合适的。

这样就够了吗?

当然不。

现在,可以来构建一个更大型的应用了。在这篇文章中,我几乎没有说到如何使用外部的库或者构建工具来构建一个应用,不过用 ES6 和 Typescript 的语法结合 Angular 和 React 来构建 Electron 应用也很简单,还可以用 gulp 或 grunt 构建流程。

干嘛不用你最喜欢的语言,框架和工具,来试试构建一个 Filckr 同步工具(借助 Filckr API 和 node-filckrapi)或者一个 Gmail 客户端(使用 Google 的官方 Node.JS 客户端库?)

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,271评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,275评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,151评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,550评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,553评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,559评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,924评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,580评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,826评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,578评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,661评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,363评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,940评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,926评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,156评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,872评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,391评论 2 342

推荐阅读更多精彩内容