互联网移动化以后,催生了手机端各种app,两大手机系统上需要分别开发一个app。我一直在想,本来网页就是最便捷的跨平台方式,只要手机上有浏览器,打开对应网址就可以了,只需要开发一次,什么系统都通用。为什么不是有一个可以安装到不同系统中的容器,里面可以跑网页呢?为什么要分开开发两个app呢?

更进一步的想法是,以后所有的操作系统,都将是一个浏览器,我们所有的文件不储存在本地,而是储存在云端。所以将来我们的系统里面看见的,其实全都是使用web前端技术写的UI,网页浏览器的概念将不复存在,因为我们从一开始,就是在上网。

2015年逐渐出现一些实现前一种想法的桌面应用,gitHub团队开发的Atom编辑器和大牌Adobe开发的Brackets编辑器就是其中代表。而React-Native的出现,更加是让开发人员雀跃不已。Electron其实就是gitHub团队开发Atom时的框架,之前叫Atom-Shell。

React-Native和Electron异曲同工的地方,就是提供出一个公共的容器,以及能够调用对应系统事件的API。只是React-Native主打移动端,而Electron目前只针对桌面端而已。

下面的示例写于2016年1月,如果日后Electron的API有更新导致报错的话,请自行查询官方最新的文档。

创建文件夹并安装开发依赖包

创建文件夹electron-get-started,或命名为其它自己喜爱的名字。在文件夹下创建下列文件,全部留空即可。

1
2
3
.
├── index.html
└── main.js

执行npm init命令,生成package.json文件。其中scripts部分设置为"start": "electron main.js"

接下来安装所需的依赖包(npm默认源链接缓慢的,请使用淘宝国内镜像):

1
npm install --save-dev electron-prebuilt

完成后package.json文件如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
{
"name": "electron-get-started",
"version": "0.0.1",
"description": "",
"main": "main.js",
"scripts": {
"start": "electron main.js"
},
"author": "",
"license": "ISC",
"devDependencies": {
"electron-prebuilt": "^0.36.2"
}
}

完成以上步骤之后文件夹目录结构如下:

1
2
3
4
.
├── index.html
├── main.js
└── package.json

开玩

既然是个桌面程序,第一步自然是程序启动后要打开个窗口吧。在main.js中添加下面的代码:

1
2
3
4
5
6
7
8
9
var electron = require('electron');
var app = electron.app;
var BrowserWindow = electron.BrowserWindow;
app.on('ready', function () {
var mainWindow = new BrowserWindow({
width: 800,
height: 600
});
});

很明显electron为开发者提供了方便好用的API可供调用。app控制整个程序的生命周期,BrowserWindow可以打开一个浏览器窗口。

注:Electron引入了两个新的概念Main ProcessRenderer Process。以目前浅显的理解,Main Process指的就是浏览器窗口,Renderer Process指的就是网页。具体解释请参考官网作出的详细解释

main.js中添加好上面的代码后保存,在命令行运行npm start,应该就会打开一个800*600像素的空白窗口。从而也证明程序可以正常跑起来了。

加载网页或本地文件

既然打开了一个浏览器窗口,第一个想法自然就是,咱们开个网页吧。在main.js里面使用一个简单的方法,就可以达到目的啦。

1
2
3
4
5
6
7
8
app.on('ready', function () {
var mainWindow = new BrowserWindow({
width: 800,
height: 600
})
mainWindow.loadURL('https://github.com')
})

重新启动程序之后就可以见到效果。

可是我们在建一个桌面程序呢,肯定是要加载自己写的文件吧。loadURL同样可以为我们做到。把传入的参数改为本地文件路径就可以啦。

首先在index.html里面加点简单的内容,好让渲染成功之后能看到点东西。

1
2
3
4
5
6
7
8
9
<html>
<head>
<meta charset="UTF-8">
<title>electron-quick-start</title>
</head>
<body>
<div class="main">hello world</div>
</body>
</html>

main.js里面修改成加载本地文件:

1
2
3
4
5
6
7
8
app.on('ready', function () {
var mainWindow = new BrowserWindow({
width: 800,
height: 600
})
mainWindow.loadURL('file://' + __dirname + '/index.html')
})

重启程序应该就可以看见页面渲染出hello world字符串了。

引入额外的js文件

打开html文件成功的话,下一步自然就是引入一下jquery和其它的js吧。下面在文件夹根目录放入jquery-2.1.4.min.js(当然也可以是另外的版本,引入的时候文件名对应就可以了),再创建一张app.js。并在index.html里面引入:

1
2
3
4
5
6
7
8
9
10
11
<html>
<head>
<meta charset="UTF-8">
<title>electron-quick-start</title>
</head>
<body>
<div class="main"></div>
</body>
<script>require('./jquery-2.1.4.min.js')</script>
<script>require('./app.js')</script>
</html>

不要问我为什么可以直接require,因为我也没有深究。Electron就是支持哟。
有了jquery,下面的步骤自然就是操作一下dom了。我们在app.js里面简单地搞一下:

1
$('.main').text('hello kitty world')

重启程序。

嗯,如果一切步骤正确的话,你应该是得到一个空白的窗口。哈哈哈。

打开开发者工具

好了,这应该是哪里出错了吧。是不是开始怀念chrome dev tool了?

既然我们是打开了一个浏览器窗口,那应该也能打开dev tool才是。Bingo!Electron可以为你做到!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
app.on('ready', function () {
var mainWindow = new BrowserWindow({
width: 800,
height: 600
})
mainWindow.loadURL('file://' + __dirname + '/index.html')
/* 不加任何参数,可以在本窗口内打开dev tool */
/* mainWindow.webContents.openDevTools() */
/* 加上参数,可以在新窗口中打开dev tool */
mainWindow.webContents.openDevTools({detach: true})
})

重启程序就可以在dev tool中看到报错了。

Uncaught ReferenceError: $ is not defined

查到正确的引入方法应该是:

1
2
3
4
5
6
7
8
9
10
11
<html>
<head>
<meta charset="UTF-8">
<title>electron-quick-start</title>
</head>
<body>
<div class="main"></div>
</body>
<script>window.$ = require('./jquery-2.1.4.min.js')</script>
<script>require('./app.js')</script>
</html>

原因:

看一下jquery源码:

1
2
3
4
5
if ( typeof module === "object" && typeof module.exports === "object" ) {
/* 把jQuery挂到`module`对象下 */
} else {
/* 把jQuery挂到`window`对象下 */
}

app.js里面简单console.log一下可以知道,modulemodule.exports都已经定义,所以jQuery挂到了module对象下,而不是全局的window.$window.jQuery

参考资料如下:

读取本地文件内容

之前loadURL方法可以帮我们直接在浏览器中打开本地html文件。由于有nodeJS的加持,我们还能直接读取本地文件的内容。

html中加一个fileContent

1
2
3
4
5
6
7
8
9
10
11
12
<html>
<head>
<meta charset="UTF-8">
<title>electron-quick-start</title>
</head>
<body>
<div class="main"></div>
<div class="fileContent"></div>
</body>
<script>window.$ = require('./jquery-2.1.4.min.js')</script>
<script>require('./app.js')</script>
</html>

app.js中使用nodeJS的API,读取package.json文件内容:

1
2
3
var fs = require('fs')
var pckJson = fs.readFileSync('./package.json', 'utf8')
$('.fileContent').text(pckJson)

能做到这一点,就代表我们可以在app本地写一些数据来使用了。

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
import java.util.concurrent.TimeUnit;
import Nature.Time;
public class mainClass {
public static void main(String[] argv) {
Nature.Time n = new Nature.Time(0);
//start from first day;
int maxRabbitNumber = 1024;
int i = 0,k = 0;
Rabbit[] r = new Rabbit[maxRabbitNumber];//1024 rabbits is enough;
int adultRabbit = 0;
int initRabbitNum = 1;
int existRabbitNum = initRabbitNum;
int oldAdultRabbit = 0;
for(;n.getTimeByMonth() <= 100;) {
try {
if(i <= (existRabbitNum - 1))
System.out.print("[INFO] Adding rabbit in the r.");
for(;i <= (existRabbitNum - 1) && i < maxRabbitNumber;i++) {
r[i] = new Rabbit();
TimeUnit.MILLISECONDS.sleep(50);
System.out.print(".");
}
TimeUnit.MILLISECONDS.sleep(50);
System.out.println("");
oldAdultRabbit = adultRabbit;
for(k = adultRabbit;k <= (existRabbitNum - 1);k++) {
if(r[k].getAgeByMonth() >= r[k].getAdultAgeByMonth()) {
adultRabbit += 1;
}
}
if((adultRabbit - oldAdultRabbit) != 0)
System.out.println("[INFO] There are " + (adultRabbit - oldAdultRabbit)
+ " rabbit become adult.");
n.add((int) Nature.Time.month);
System.out.println("[TIME] Time passed 1 month, and this is "+ n.getTimeByMonth() +" month.");
for(k = 0;k <= (existRabbitNum - 1); k++) {
r[k].syncAge((int) n.getTimeByDay());
}
for(k = 0;k < (adultRabbit);k++) {
existRabbitNum++;
}
System.out.println(existRabbitNum);
}catch(Exception e) {
System.out.println("[ERR] Finally, time passes "
+ n.getTimeByDay() + " days, it means "
+ n.getTimeByMonth() + " months, about "
+ n.getTimeByYear() + " years, and "
+ (existRabbitNum - initRabbitNum) +" rabbits born, "
+ adultRabbit + " rabbits become adult. Bye.");
System.exit(0);
}
}
}
}

参考资料:

[1] Building a Package Featuring Electron as a Stand-Alone Application

[2] electron github docs

[3] electron官方教程