原文:Creating Your First Desktop App With HTML, JS and Electron
作者:Danny Markov
近年來 web 應用變得越來越強大,但是桌面應用仍然有充分利用硬件的優勢。
今天,我們可以通過我們熟悉的 HTML、JS 和 Node.js 來創建桌面應用,打包成一個可執行文件,並且發布在 Windows, OS X 和 Linux 上。
有兩個受歡迎的開源項目,能夠幫助我們實現這個目的。一個是幾個月前我們討論到的 NW.js,另一個是今天我們將要使用的 Electron(點擊這里查看它們的不同之處)。我們將把舊項目的 NW.js 重構成 Electron,所以你可以輕松地比較他們的不同。
開始使用 Electron
用 Electron 構建的應用僅僅是一個網站而已,這個網站在嵌入了 Chromium 的瀏覽器中運行。除了常規的 HTML5 接口,這些應用能夠使用所有的 Node.js 模塊,以及 Electron 特有的模塊,這個模塊能夠讓應用擁有訪問操作系統的能力。
這篇文章中,我們將會構建一個簡單的應用,通過 RSS 訂閱,來獲取 Tutorialzine 上最新的文章,並通過好看的輪播的形式展現出來。這個項目用到的所有文件,你可以點擊文章頂部的下載按鈕獲取(譯者注:下載鏈接)。
下載后,解壓內容到一個文件夾。從文件結構來看,你永遠猜不到這是一個桌面應用,而不僅僅是一個簡單的網站。
接下來,我們將仔細分析這些項目文件,了解它們是如何工作的。但首先,我們來運行這個應用。
運行應用
由於 Electron 應用只是一個 Node.js 應用,你需要安裝 npm。你可以點擊這里學習怎樣安裝,這很容易。
解壓項目后,在解壓目錄下打開 cmd 或者命令行,執行以下命令:
npm install
這條命令會創建一個 node_modules
文件夾,里面包含了項目需要所有的 Node.js 依賴。接下來,在同個終端上執行下面的命令:
npm start
這個應用會打開一個新的窗口。注意看,窗口有一個頂部菜單以及其他東西。
你可能已經留意到,這個應用的啟動方式不怎么友好。但這僅僅是開發時啟動應用的方式。應用打包發布后,就會像普通的程序那樣只是一個安裝文件,雙擊圖標就可以啟動。
它是如何工作的
這一段內容,我們將討論 Electron 應用必要的文件。我們將從 package.json
文件講起,這個文件包含關於這個項目的各種信息,比如版本號、npm 依賴以及其他重要的配置。
package.json
{
"name": "electron-app",
"version": "1.0.0",
"description": "",
"main": "main.js",
"dependencies": {
"pretty-bytes": "^2.0.1"
},
"devDependencies": {
"electron-prebuilt": "^0.35.2"
},
"scripts": {
"start": "electron main.js"
},
"author": "",
"license": "ISC"
}
如果你之前使用過 Node.js,你已經知道了這個文件是做什么的。在這里最值得講述的是 scripts
這個屬性,它定義了 npm start
這條命令,允許我們使用命令來運行應用。當我們執行這條命令的時候,electron 就會執行 main.js
。這個 JS 文件的腳本打開了應用的窗口,定義了一些配置和事件處理。
main.js
var app = require('app'); // Module to control application life.
var BrowserWindow = require('browser-window'); // Module to create native browser window.
// Keep a global reference of the window object, if you don't, the window will
// be closed automatically when the JavaScript object is garbage collected.
var mainWindow = null;
// 當所有窗口關閉時退出應用
app.on('window-all-closed', function() {
// On OS X it is common for applications and their menu bar
// to stay active until the user quits explicitly with Cmd + Q
if (process.platform != 'darwin') {
app.quit();
}
});
// This method will be called when Electron has finished
// initialization and is ready to create browser windows.
app.on('ready', function() {
// 創建瀏覽器窗口
mainWindow = new BrowserWindow({width: 900, height: 600});
// 加載這個應用的 index.html
mainWindow.loadURL('file://' + __dirname + '/index.html');
// 當窗口關閉時執行
mainWindow.on('closed', function() {
// Dereference the window object, usually you would store windows
// in an array if your app supports multi windows, this is the time
// when you should delete the corresponding element.
mainWindow = null;
});
});
我們來看一下,在 ready
方法做了哪些事情。首先我們定義了一個瀏覽器窗口,並且設置了它的初始化大小。然后,在窗口里面加載了 index.html
文件,這一步就像在瀏覽器中打開了一個 HTML 文件。
正如你所看到的,這個 HTML 文件本身並沒有什么特別——它包含了一個輪播組件的容器標簽和一個用來展示 CPU 和 RAM 的 p
標簽。
index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Tutorialzine Electron Experiment</title>
<link rel="stylesheet" href="./css/jquery.flipster.min.css">
<link rel="stylesheet" href="./css/styles.css">
</head>
<body>
<div class="flipster">
<ul>
</ul>
</div>
<p class="stats"></p>
<!-->In Electron, this is the correct way to include jQuery<-->
<script>window.$ = window.jQuery = require('./js/jquery.min.js');</script>
<script src="./js/jquery.flipster.min.js"></script>
<script src="./js/script.js"></script>
</body>
</html>
這個 HTML 文件也引用了幾個必要的樣式文件,JS 庫和腳本。注意在這里 jQuery 是通過一種奇怪的方式引入進來的。更多相關信息可以查看這個issue。
最后,是這個應用的實際腳本文件。腳本里面,我們調用 Tutorialzine 的 RSS 訂閱接口,獲取了最新的文章並且展示出來。如果我們在瀏覽器環境這么做,是獲取不到數據的,因為跨域的原因,我們無法獲取到數據。但在 Electron 里就沒有跨域的限制,我們可以簡單地通過 AJAX 請求來獲取我們需要的信息。
$(function(){
// Display some statistics about this computer, using node's os module.
var os = require('os');
var prettyBytes = require('pretty-bytes');
$('.stats').append('Number of cpu cores: <span>' + os.cpus().length + '</span>');
$('.stats').append('Free memory: <span>' + prettyBytes(os.freemem())+ '</span>');
// Electron's UI library. We will need it for later.
var shell = require('shell');
// 獲取 Tutorialzine 上最新的文章
var ul = $('.flipster ul');
// The same-origin security policy doesn't apply to electron, so we can
// send ajax request to other sites. Let's fetch Tutorialzine's rss feed:
$.get('http://feeds.feedburner.com/Tutorialzine', function(response){
var rss = $(response);
// Find all articles in the RSS feed:
rss.find('item').each(function(){
var item = $(this);
var content = item.find('encoded').html().split('</a></div>')[0]+'</a></div>';
var urlRegex = /(http|ftp|https):\/\/[\w\-_]+(\.[\w\-_]+)+([\w\-\.,@?^=%&:/~\+#]*[\w\-\@?^=%&/~\+#])?/g;
// Fetch the first image of the article.
var imageSource = content.match(urlRegex)[1];
// Create a li item for every article, and append it to the unordered list.
var li = $('<li><img /><a target="_blank"></a></li>');
li.find('a')
.attr('href', item.find('link').text())
.text(item.find("title").text());
li.find('img').attr('src', imageSource);
li.appendTo(ul);
});
// Initialize the flipster plugin.
$('.flipster').flipster({
style: 'carousel'
});
// 文章被點擊時,在系統默認瀏覽器打開連接
// 不這樣做的話,會在 electron 窗口中打開連接,這不是我們想要的效果
$('.flipster').on('click', 'a', function (e) {
e.preventDefault();
// 在默認瀏覽器中打開鏈接
shell.openExternal(e.target.href);
});
});
});
上面的代碼中,比較酷的是,我們在一個文件同時使用了:
- JavaScript 庫 - 使用 jQuery 和 jQuery Flipster 來生成輪播效果。
- Electron 原生模塊 - Shell 這個模塊提供了桌面相關的任務接口,在示例中,我們通過這個接口調用默認瀏覽器中打開了一個鏈接。
- Node.js 模塊 - 用來訪問操作系統信息的 OS 模塊,用來格式化的 Pretty Bytes 模塊。通過這些模塊,我們完成了這個應用!
打包和發布
還有一件重要的事需要我們去做,就是准備好我們的應用給最終用戶使用。你需要把項目打包成一個可執行文件,可以在用戶的機器上雙擊運行。由於 Electron 應用可以在多個不同的操作系統上運行,我們需要分別打包成 Windows、OS X 和 Linux 各個版本。像 Electron Packager 這個 npm 模塊就是一個不錯的工具。
需要考慮的是,打包項目時會把所有資源,所有的 Node.js 依賴,加上一個縮小版的 WebKit 瀏覽器,打包成一個可執行的文件。這些東西加起來的結果就是,打包后的應用體積大約為 50mb。這個體積已經算是比較大了,對於一些簡單的應用,比如這個示例項目來說,有些不太理想。但對於那些比較大而又復雜的項目來說,就無關緊要了。
總結
從這個示例項目可以看出,Electron 與 NW.js 的主要差異在於,NW.js 是直接打開一個 HTML 頁面,而 Electron 是通過執行一個腳本文件,在代碼中創建一個窗口。Electron 這種創建方式,能夠讓我們更大程度地控制應用,比如我們可以輕松地構建一個多窗口應用,並在窗口之間進行通信。
整體上,Electron 是一個令人激動的、通過 web 技術來構建桌面應用的方式。接下來,你可以繼續閱讀:
我(譯者)的總結
- 翻譯這篇文章,主要是因為這篇文章已經有中文譯文了,翻譯完成后,我可以對比其他譯文,總結不足之處。對比了一下,差距還是很大的。
- 翻譯到一半才發現,運行效果和文中的截圖不一致。調試發現,是因為 Tutorialzine 的接口不能用了。這導致翻譯出來的文章實用性大大降低,這一點,以后需要注意。
- 文章沒有講到如何打包 Electron 應用,有點可惜。有時間我會補充,或者另寫一篇。
- 一年前就接觸了 Electron,當時 Electron 還有很多不足,現在 Electron 改進了一些,比較看好 Electron 的發展。
- 這是我的第一篇翻譯,萬事開頭難,中間更難,結尾最難,還需繼續努力。