# <font color="#4078c0">Quick Start</font>
###### tags: `Electron`
原文:[Electron - Quick Start](https://www.electronjs.org/docs/tutorial/quick-start#access-nodejs-from-the-renderer-with-a-preload-script)
Hackmd:[連結](https://hackmd.io/@RdUg6kDRTcKHmb2WGCucdA/SyR45_Gqd)
這個章節將會按照步驟用很直白的方式在Electron內去開發一個Hello World App。
在章節的最後,若有按照步驟完成的話,將可以執行一個應用程式版的瀏覽器,上面顯示Chromium、Node.js及Electron版本。
---
### <font color="#4078c0">首先 安裝Node.js</font>
為了要可以執行Electron,我們需要先去安裝Node.js,這邊是建議安裝最新的LTS版本。
>請用平台預先提供的安裝程序去安裝Node.js,不然在不同的開發工具上可能會有相容性的問題。
可以檢查看看Node.js是否有安裝成功,請在終端機輸入以下語法。
```
node -v
npm -v
```

這時候應該就可以看到node及npm的版本了!
##### <font color="#4078c0">**Note:**</font>Electron會將node.js嵌進它二進位編制中,所以它實際執行的版本有可能會與我們電腦所安裝的版本不一樣。
---
### <font color="#4078c0">產生一個應用程式</font>
#### <font color="#4078c0">建立一個專案</font>
Electron專案與node.js的專案結構一樣,所以要先新增一個專案並初始化npm的套件。
```
mkdir my-electron-app
cd my-electron-app
npm init
```
執行npm init時,會跳出選項填寫,完成後可以看到資料夾底下多了package.json檔案,這邊有一些規則是必須遵從的:
- entry point 要改為 main.js. (原本是預設index.js)
- author和description可以是任意填寫,但是打包的時候會用到
那完成之後,package.json檔應該會是這個樣子
```json=
{
"name": "my-electron-app",
"version": "1.0.0",
"description": "Hello World!",
"main": "main.js",
"author": "Jane Doe",
"license": "MIT"
}
```
那接下來要在專案底下安裝Electron的套件。
```
npm install --save-dev electron
```
再來如果要能執行Electron,請在package.json的script物件內加入start這個指令,如下所示:
```json=
{
"scripts": {
"start": "electron ."
}
}
```
這時候的package.json應該會類似這個樣子

完成之後,就可以輸入start指令嘗試打開Electron
```
npm start
```
> 這個指令會通知Electron要在這個專案啟動,不過在目前這個步驟中,應該是會得到錯誤訊息且app還無法運行。
#### <font color="#4078c0">執行主程序</font>
Electrin在執行時,主要是要透過main script。這腳本主要是控制在node.js的環境中執行的過程、專案中的生命週期及操作的介面、渲染的過程等。
在執行的過程中,Electron會去package.json的main指令去找所對應到的檔案,當初在init所設定的main.js
**<font color="#AE0000">為了要讓main指令可以運作,必須要在專案的根目錄底下新增一個檔案命名為main.js。</font>**

> 這時候再去執行一次npm start的指令,應該就不會有錯誤訊息出現,不過也不會有任何反應,因為還沒在main.js內加入任何程式。
#### <font color="#4078c0">新增一個網頁</font>
在應用程式產生一個視窗之前,我們需要提供內容讓它能夠載入。在Electron中,視窗呈現的網頁內容可以是專案內的Html檔案或是一個遠端的網址。
在這章節中會使用前者,在專案底下新增一個index.html檔案,內容就如下:
```htmlembedded=
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<!-- https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP -->
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self'">
<meta http-equiv="X-Content-Security-Policy" content="default-src 'self'; script-src 'self'">
<title>Hello World!</title>
</head>
<body>
<h1>Hello World!</h1>
We are using Node.js <span id="node-version"></span>,
Chromium <span id="chrome-version"></span>,
and Electron <span id="electron-version"></span>.
</body>
</html>
```
> Note:可以注意到在這個HTML文件中,版本號的位置是沒有值的,這部分接下來的步驟會用javascript去產生。
#### <font color="#4078c0">在瀏覽器視窗開啟網頁</font>
現在已經有一個網頁了,要將它載進應用程式的視窗內,所以需要用到兩個Electron的套件:
- app: 控制應用程式內事件的生命週期。
- BrowserWindow: 產生並運作一個應用程式的視窗。
```javascript=
// main.js
const { app, BrowserWindow } = require('electron')
```
接下來為了要載入index.html,所以需要新增一個createWindow的function,然後新增一個BrowserWindow的實例。
```javascript=
// main.js
function createWindow () {
const win = new BrowserWindow({
width: 800,
height: 600
})
win.loadFile('index.html')
}
```
接下來,呼叫createWindow()來開啟視窗。
在Electron中,瀏覽器視窗只會在app套件中的ready事件被觸發之後才會產生。所以可以使用app.whenReady()事件來等待事件的觸發。在whenReady()的callback function中 呼叫createWindow()就可以解決app還沒準備好的問題。
```javascript=
// main.js
app.whenReady().then(() => {
createWindow()
})
```
> 這時候再去執行一次npm start,Electron的應用程式應該就可以成功run起來,並顯示index.html的內容。
目前main.js就會是這個樣子

及Electron run起來的樣子

#### <font color="#4078c0">管理視窗的生命週期</font>
雖然現在已經可以成功執行瀏覽器視窗,但因在每個平台的操作行為不同,所以可以透過額外的樣板程式碼(boilerplate code)來讓你的視窗更加地適應在每個平台上。Electron將這個責任賦予給開發者去屢行這些設定。
通常可以使用process內platform這個全域的屬性來操作系統內的設定。
> 什麼是boilerplate code?
> 很類似一種樣版,在使用不同程式語言時,常常會有一些基本設定,或是常常需要重複寫的code,每次開新專案時就要輸入的話會相當耗時,所以boilerplate就把東西包好好,直接執行就可以產出你所要開發的樣本了
> Ex.Vue-Cli 就是自動產生Vue開發的樣本
##### <font color="#4078c0">當所有的視窗都被關閉結束這個app(Windows & Linux)</font>
在Window及Linux中,當關閉所有視窗時,通常該應用程式也會被完整的結束運行。
為了要實現這個做法,可以去監聽app模組中的window-all-closed事件,然後當使用者的作業系統不是MacOS(darwin)的話就去呼叫app.quit()這個function。
```javascript=
app.on('window-all-closed', function () {
if (process.platform !== 'darwin') app.quit()
})
```
##### <font color="#4078c0">當視窗都被關閉時開啟一個新視窗(MacOS)</font>
macOS與Linux及Windows作業系統關閉視窗後就會結束該應用程式的規則不一樣,當沒有視窗時通常它還是會持續運行,而且會去觸發該應用程式去開啟一個新的視窗。
為了要去實行這個特點,可以去監聽app模組中的activate事件,當沒有瀏覽器視窗被開啟時,再次呼叫createWindow()這個function。
由於上面有提到說視窗不能在ready事件之前被創造,所以應該要在當初始化app時去監聽activate事件,將該事件放在已經存在的whenReady()的callback function中。
```javascript=
app.whenReady().then(() => {
createWindow()
app.on('activate', function () {
if (BrowserWindow.getAllWindows().length === 0) createWindow()
})
})
```
> 這時候,我們這視窗的控制就相當的齊全了!
#### <font color="#4078c0">透過preload腳本從renderer來訪問Node.js</font>
現在,最後一步要做的事情是要把Electron及相依的工具版本號印出來並顯示在網頁上。
為了要得知版本的資訊,我們可以使用Node的全域物件process來取得,但是!我們無法直接在主程序中去控制DOM元件,因為它沒有權限去渲染document的內文。它們是完全不同的程序。
> 如果需要更深入的研究關於Electron執行的程序,可以去看看[Process Model](https://www.electronjs.org/docs/tutorial/process-model)的文件
preload會在渲染的程序之前被載入,並將進入window、document及Node.js環境之間,這邊preload很像是這兩者之間的橋樑,將不同程序的東係搭載在一起。
所以現在需要在專案底下新增一個preload.js的檔案,內容如下:
```javascript=
window.addEventListener('DOMContentLoaded', () => {
const replaceText = (selector, text) => {
const element = document.getElementById(selector)
if (element) element.innerText = text
}
for (const dependency of ['chrome', 'node', 'electron']) {
replaceText(`${dependency}-version`, process.versions[dependency])
}
})
```
上面的程式碼是透過process.version物件去取得Node.js的版本,並執行replaceText()將取得的版本號依序加到HTML中。
為了要將preload的程式渲染到現有的BrowserWindow,可以將preload.js的路徑傳入webPreferences.preload。
```javascript=
// include the Node.js 'path' module at the top of your file
const path = require('path')
// modify your existing createWindow() function
function createWindow () {
const win = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
preload: path.join(__dirname, 'preload.js')
}
})
win.loadFile('index.html')
}
// ...
```
這邊有使用到兩個Node.js的概念
- __dirname: 正在執行的專案的路徑(該範例來看就是專案的根目錄)
- path.join: 連接多個路徑,結合之後產生一個新的路徑字串,並可以在該平台的各地方使用。
如果使用相對路徑的話,就可以在開端及打包的時候都可以使用。
#### <font color="#4078c0">額外:在網頁中增加更多的功能</font>
在這個段落,可以學到如何在應用程式中增加更多的功能
因為渲染器會在原生的網頁中運行,所以在index.html中body標籤結尾的地方加入script標籤,將可以把想要執行的script都包含進這個index.html。
```htmlembedded=
<script src="./renderer.js"></script>
```
在路徑下新增一個名為render.js的檔案,內的程式皆可以使用原生javascript的api及工作來做前端的開發。像是使用webpack來壓縮程式碼,或是使用React來管理使用者介面等。
### <font color="#4078c0">回顧</font>
如果都有照上述的步驟來做的話,應該已經可以成功執行一個完整的Electron應用程式,像是以下範例

以下為完整的程式碼:
```javascript=
// main.js
// Modules to control application life and create native browser window
const { app, BrowserWindow } = require('electron')
const path = require('path')
function createWindow () {
// Create the browser window.
const mainWindow = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
preload: path.join(__dirname, 'preload.js')
}
})
// and load the index.html of the app.
mainWindow.loadFile('index.html')
// Open the DevTools.
// mainWindow.webContents.openDevTools()
}
// This method will be called when Electron has finished
// initialization and is ready to create browser windows.
// Some APIs can only be used after this event occurs.
app.whenReady().then(() => {
createWindow()
app.on('activate', function () {
// On macOS it's common to re-create a window in the app when the
// dock icon is clicked and there are no other windows open.
if (BrowserWindow.getAllWindows().length === 0) createWindow()
})
})
// Quit when all windows are closed, except on macOS. There, it's common
// for applications and their menu bar to stay active until the user quits
// explicitly with Cmd + Q.
app.on('window-all-closed', function () {
if (process.platform !== 'darwin') app.quit()
})
// In this file you can include the rest of your app's specific main process
// code. You can also put them in separate files and require them here.
```
```javascript=
// preload.js
// All of the Node.js APIs are available in the preload process.
// It has the same sandbox as a Chrome extension.
window.addEventListener('DOMContentLoaded', () => {
const replaceText = (selector, text) => {
const element = document.getElementById(selector)
if (element) element.innerText = text
}
for (const dependency of ['chrome', 'node', 'electron']) {
replaceText(`${dependency}-version`, process.versions[dependency])
}
})
```
```htmlembedded=
<!--index.html-->
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<!-- https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP -->
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self'">
<meta http-equiv="X-Content-Security-Policy" content="default-src 'self'; script-src 'self'">
<title>Hello World!</title>
</head>
<body>
<h1>Hello World!</h1>
We are using Node.js <span id="node-version"></span>,
Chromium <span id="chrome-version"></span>,
and Electron <span id="electron-version"></span>.
<!-- You can also require other files to run in this process -->
<script src="./renderer.js"></script>
</body>
</html>
```
總結上述所完成的步驟:
- 使用了Node.js的應用程式,並加入Electron
- 新增main.js的檔案作為執行的主程序,並可以控制該應用程式及在Node.js的環境中運行,此外在另一個程序中,使用了Electron內的app及BrowserWindow的套件來產生一個瀏覽器視窗來顯示網頁內容。
- 為了要使用Node.js內的一些功能,我們將preload的腳本放入BrowserWindow內。
### <font color="#4078c0">打包並發布應用程式</font>
要快速發佈最新的產生的app,可以使用Electron Forge這個套件。
1.在專案底下安裝Electron Forge套件
```
npm install --save-dev @electron-forge/cli
```
2.透過import指令去設定架設Forge
```
npx electron-forge import
```
這時候畫面結果會長這樣,代表有成功執行

3.透過Forge指令來發佈應用程式
```
npm run make
```
看到以下畫面代表你的應用程式已經成功被打包了

打包完成的檔案會被放在專案路徑下的out的資料夾底下
```
// Example for macOS
out/
├── out/make/zip/darwin/x64/my-electron-app-darwin-x64-1.0.0.zip
├── ...
└── out/my-electron-app-darwin-x64/my-electron-app.app/Contents/MacOS/my-electron-app
```

在electron-app-darwin-x64這個資料夾裡面,就可以看到可執行的檔案
Electrom Quick Start 這邊教學就到這邊,期待大家都可以做出自己第一個Elecrtron Application,就這樣 ㄅㄅ