使用 NW.js 创建桌面应用

前段时间简单研究了一下 NW.js,基于 Chromium 和 node.js,可以使用 Web 技术来编写跨平台的桌面应用。

安装

其实理论上是可以不用安装 NW.js 到本地的,但是为了方便开发,也可以按照其在 GitHub 上的介绍进行下载安装。

sublime 工具

这里我还安装了个 sublime 工具,配置好后在根目录使用快捷键 ⌘ B 就可以启动应用。

创建项目

并没有特别的要求,只要有 package.json 就可以了:

1
2
3
4
5
6
7
8
9
10
{
"name": "nw-demo",
"version": "0.0.1",
"main": "index.html",
"window": {
"toolbar": true,
"width": 660,
"height": 500
}
}

其中 "main": "index.html" 指定了入口文件,那么这个 index.html 也是必须要存在的:

1
2
3
4
5
6
7
8
9
<!DOCTYPE html>
<html>
<head>
<title>demo</title>
</head>
<body>
<h1>demo</h1>
</body>
</html>

启动项目

在 package.json 所在目录执行(Mac):

1
/Applications/nwjs.app/Contents/MacOS/nwjs .

就会弹出应用窗口(内容为上述 index.html)。其他操作系统看这里,我也没试过。

使用 JS

在 index.html 内,我们有两种方法来使用 JS:

1
2
<!-- index.html -->
<script src="path/to/index.js"></script>

1
2
3
4
<!-- index.html -->
<script>
// do something
</script>

这两种方式是无差异的。什么意思?就是第一种方式等同于把 index.js 的内容直接按第二种方式的方法贴在页面 <script> 标签中,和 index.js 本身所在的路径无关。

模块化

看起来这个这个 index.js 的文件路径时候没多大关系,但当项目大起来的时候,模块化的概念还是得引入,而此时模块路径就是一个不得不关注的话题了。

但是我翻了好几个 demos repositoryList of apps and companies using nw.js 发现都没有使用任何模块化工具:无一例外地是把全部 js 以 <script src="..."> 的方式插入页面。

但是我还是不甘心,还是试了一下:鉴于 NW.js 是基于 Node 的,那自然可以使用 CommonJS 规范:

1
2
3
4
5
6
7
8
<!-- index.html -->
<!--
这里不用 <script src="...">
是为了让这个无效的 src 在 require 时不产生相对路径的困惑
-->
<script>
require('./js/index');
</script>
1
2
3
4
/**
* js/index.js
*/
require('./cmp/alert')();
1
2
3
4
5
6
/**
* js/cmp/alert.js
*/
module.exports = function () {
alert('something');
};

完美运行,一切看起来很好。

使用 Node Module

回到 NW.js 基于 Node 上来,我们可以使用 Node 本身的 Module,也可以用 npm 上的:

1
npm i --save jquery
1
2
3
<!-- index.html -->
<ul id="files"></ul>
<script>require('./js/files')();</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
/**
* js/files.js
*/
var fs = require('fs');
var $ = require('jquery');
var $files = $('#files');
module.exports = function () {
fs.readdir(process.cwd(), function (err, files) {
files.forEach(function (f) {
$('<li>').html(f).appendTo($files);
});
});
}

完美运行,这里的 require('fs')process.cwd() 都如同写 Node 一样,一切看起来很好。

使用 nw.gui

做桌面应用难免要使用到一些 Native 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
/**
* js/menu.js
*/
// Load native UI library
var gui = require('nw.gui');

// Create an empty menu
var menu = new gui.Menu({ type: 'menubar' });

// Create a normal item with label and icon
var item = new gui.MenuItem({
type: 'normal',
label: 'I\'m a menu item',
click: function() {
alert('WaW!');
},
});

// Add some items
menu.append(item);
menu.append(new gui.MenuItem({ type: 'separator' }));
menu.append(new gui.MenuItem({ label: 'Item C' }));

document.body.addEventListener('contextmenu', function(ev) {
ev.preventDefault();
menu.popup(ev.x, ev.y);
return false;
});
1
2
<!-- index.html -->
<script src="js/menu.js"></script>

用这样的方法插入 js 到 html 里,依旧是完美运行。但是如果使用上面的 require 的方式呢:

1
2
<!-- index.html -->
<script>require('./js/menu');</script>

结果就是,报错:

1
Error: Cannot find module 'nw.gui'

嗯,我似乎明白为什么别人不用模块化了…

打包应用

官方的介绍老长老长了,我用了 node-webkit-builder

1
npm i --save-dev node-webkit-builder

然后写个 build.js:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
var NwBuilder = require('node-webkit-builder');
var nw = new NwBuilder({
files: [
'./package.json',
'./css/*',
'./img/*',
'./index.html',
'./js/**/**',
'./node_modules/jquery/dist/jquery.js'
],
macIcns: './logo.icns',
platforms: ['osx64'] // 'osx32', 'osx64', 'win32', 'win64'
});

//Log stuff you want
nw.on('log', console.log);

// Build returns a promise
nw.build().then(function () {
console.log('all done!');
}).catch(function (error) {
console.error(error);
});

执行 node build.js 就可以了。生成的应用会在目录 build/<package.name>/ 下面,按平台分成多个文件夹。

这里配置了应用的图标,随便找个可以转换 icns 文件的网站把自己的设计图搞上去生成就可以了。