超詳細的 Electron 使用教程

Electron

Electron 是什麼,我們先看看官方 https://www.electronjs.org/ 的介紹如何說的

What is Electron?

Electron is a framework for building desktop applications using JavaScript, HTML, and CSS. By embedding Chromium and Node.js into its binary, Electron allows you to maintain one JavaScript codebase and create cross-platform apps that work on Windows, macOS, and Linux — no native development experience required.

簡單來說,Electron 是一個構建桌面應用的框架,通過它我們可以用 js、html 和 css 開發桌面應用。

我覺得最簡單的理解就是它是一個特殊的瀏覽器,或者說是一個殼,裏面加載本地或遠端的 web 應用。

下面我們來看看它怎麼用。

創建項目

前提是有 nodejs 環境,網上有很多教程,這裏不細說了。

參考官方教程 http://www.electronjs.org/docs/tutorial/quick-start#prerequisites

通過下面的命令創建項目

mkdir my-electron-app && cd my-electron-app
npm init -y
npm i --save-dev electron

創建並安裝了 electron

我的 node 和 npm 版本分別是:

bennu:~ bennu$ node -v
v14.15.5
bennu:~ bennu$ npm -v
6.14.11

是否需要完全一致並不清楚。但是我在 windows 上配置環境的時候 npm 版本不對,導致 electron 一直安裝不成功,問題如下:

最終使用的是 6.14.11 這個版本。大家可以注意一下。

創建入口 main

在創建的項目中新建入口文件

vim main.js

然後填入下面的內容:

const { app, BrowserWindow } = require('electron')
function createWindow () {
  const win = new BrowserWindow({
    width: 800,
    height: 600,
    webPreferences: {
      nodeIntegration: true
    }
  })
  win.loadFile('index.html')
}
app.whenReady().then(createWindow)
app.on('window-all-closed', () => {
  if (process.platform !== 'darwin') {
    app.quit()
  }
})
app.on('activate', () => {
  if (BrowserWindow.getAllWindows().length === 0) {
    createWindow()
  }
})

創建首頁

然後新建一個首頁文件

vim index.html

填入下面內容

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>Hello World!</title>
    <meta http-equiv="Content-Security-Policy" content="script-src 'self' 'unsafe-inline';" />
</head>
<body>
    <h1>Hello World!</h1>
    <p>
        We are using node <script>document.write(process.versions.node)</script>,
        Chrome <script>document.write(process.versions.chrome)</script>,
        and Electron <script>document.write(process.versions.electron)</script>.
    </p>
</body>
</html>

package.json

下一步需要修改 package.json 文件

{
    "name": "my-electron-app",
    "version": "0.1.0",
    "author": "your name",
    "description": "My Electron app",
    "main": "main.js",
    "scripts": {
        "start": "electron ."
    }
}

注意:

啓動

執行npm start即可啓動這個項目,可以看到打開了一個窗口,顯示index.html的內容。

本地應用網絡請求失敗

上面可以看到在 main.js 中是通過 loadFile 來加載文件的。這個是加載的本地文件,所以 url 都是 file://xxxx。

而如果想加載 url,那麼使用 loadURL 這個函數,比如loadURL('http://www.baidu.com')

但是這裏面存在一個問題

如果我們用的是本地文件,那麼 scheme 默認就是 file:// ,所以如果在 js 中做一個 get 或 post 請求時,url 沒有添加 scheme(比如 “//www.baidu.com” ),那麼就是默認使用 file:// ,那麼這個請求就一定會失敗。

所以要解決這個問題,url 必須帶着 scheme,比如 http://www.baidu.com 纔行

窗口

有了項目就可以開始開發應用了。應用只是在窗口內展示,所以 Electron 的窗口也需要我們關注,通過 Electron 提供的 api 來定義一個合適的窗口

窗口設置

在上面我們創建的main.js中,可以看到通過 BrowserWindow 創建的窗口

  const win = new BrowserWindow({
    width: 1280,
    height: 744,
    minWidth: 1280,  //最小寬度
    minHeight: 744,   //最小高度
    webPreferences: {
      nodeIntegration: true
    }
  })

可以通過 BrowserWindow 的各個屬性來調整窗口的各種參數,比如大小,最小寬度,是否全屏,是否可以最小化,是否能調整大小等等,見 https://www.electronjs.org/docs/api/browser-window

注意:如果有框的話有標題欄,標題欄是 24 高度,這樣也要考慮在 height 中,也就是說這裏的高度是內容的高度 + 24

菜單欄

默認窗口是有菜單欄的,在 mac 上因爲是在通知欄上而不是窗口上,所以不是很明顯,但是在 windows 上就很明顯了。

如果我們不希望展示這個菜單欄,就可以通過下面代碼隱藏它

const { app, BrowserWindow, globalShortcut, Menu } = require('electron')
function createWindow () {
  ...
}
Menu.setApplicationMenu(null)
...

先引入 menu 模塊,然後通過setApplicationMenu隱藏即可。當然通過setApplicationMenu也可以設置自定義的菜單。

但是注意,這麼處理完之後應用的複製和粘貼功能也實效了,因爲複製和粘貼的快捷鍵是設置在對應的菜單上的,如果將菜單取消,ctrl+c 這類的快捷鍵也失效了,導致無法複製和粘貼。

所以如果你需要保留這些快捷操作,就不能完全去掉菜單欄,至少保留一些基本功能,如下:

const template = [
  {
    label: 'Edit',
    submenu: [
      {role: 'cut'},
      {role: 'copy'},
      {role: 'paste'},
      {role: 'about'}
    ]
  }
];
...
Menu.setApplicationMenu(Menu.buildFromTemplate(template));

注意:這裏的 about 菜單隻顯示英文名(後面會提到通過 forge 將應用名覆蓋成中文,但是這裏覆蓋不到),而且在執行npm start直接啓動的時候,顯示的是 electron 的版本和圖標,但是沒關係通過npm run make打包後安裝啓動就會顯示我們設置的應用圖標和版本了。

開發者工具

在瀏覽器中,我們可以通過右鍵 -> 檢查來打開開發者工具,可以看到控制檯輸出、文件、代碼、報錯等信息。

但是用 Electron 打包後就無法看到,尤其控制檯輸出的日誌,沒有日誌有問題後很難排查。

其實 electron 也可以開啓開發者工具,在 main.js 文件中加入相關代碼。比如我們上面寫的 main.js 的 createWindow 函數的最後添加:

function createWindow () {
  ...
  win.loadFile('index.html')
  win.webContents.openDevTools()  //開啓開發者工具
}

注意,這行代碼必須在 loadFile 之後纔行。

然後在運行或打包運行,就可以看到右側出現與瀏覽器一樣的開發者工具了,可以很方便的查看控制檯的日誌輸出。

但是上線前需要將這行代碼去掉,這樣太麻煩,我們可以通過快捷鍵來喚出開發者工具,只要將快捷鍵設置的足夠複雜,線上用戶就基本不會觸發它。

修改 mian.js 代碼如下:

const { app, BrowserWindow, globalShortcut } = require('electron')
var win;
function createWindow () {
  win = new BrowserWindow({
    ...
  })
  win.loadFile('index.html')
  globalShortcut.register('Alt+CommandOrControl+Shift+D', () => {
    win.webContents.openDevTools({mode:'detach'}) //開啓開發者工具
  })
}

可以看到,使用globalShortcut來註冊快捷鍵,這裏用Alt+CommandOrControl+Shift+D組合(由於 mac 和 windows 的差異,所以第二個是 CommandOrControl),這裏因爲是三個功能鍵 + 一個字母,所以一般用戶不會觸發。

當觸發這個快捷鍵,就打開開發者工具。而且這裏將工具的模式設置爲detach,即跟主頁面分離,也就是說兩個窗口,這樣工具就不會佔用主窗口的空間了,不會影響主窗口的內容。

但是注意:因爲之前是在createWindow中創建const win,所以如果使用快捷鍵後再關閉重新打開應用,再使用快捷鍵時,這時候win.webContentswin還是之前的對象,已經銷燬了,就會報錯object has destroy。所以將win改成全局變量,如上面的代碼。

發佈

以上我們僅僅是創建了一個項目並運行起來,如果要交付給用戶使用,則需要將這個項目打包成可執行文件。下面看看如何進行打包。

Electron 打包有幾種選擇

官方推薦使用 Electron Forge,因爲更簡單易上手。

Electron Forge

導入 Electron Forge 命令

npx @electron-forge/cli@latest import

這裏遇到了兩個問題,記錄一下:

執行命令後會開始安裝 Electron Forge

✔ Checking your system
✔ Initializing Git Repository
✔ Writing modified package.json file
✔ Installing dependencies
✔ Writing modified package.json file
✔ Fixing .gitignore
We have ATTEMPTED to convert your app to be in a format that electron-forge understands.
Thanks for using "electron-forge"!!!

安裝完成後執行npm run make即可進行打包,出現下面的信息

> my-gsod-electron-app@1.0.0 make /my-electron-app
> electron-forge make
 Checking your system
 Resolving Forge Config
We need to package your application before we can make it
 Preparing to Package Application for arch: x64
 Preparing native dependencies
 Packaging Application
Making for the following targets: zip
 Making for target: zip - On platform: darwin - For arch: x64

打包完成在項目目錄下的 out 目錄下就可以看到打包好的程序

我這裏是 mac 電腦,所以以 mac 爲例子

其中 make 目錄下是一個壓縮文件,解壓後就是可執行的 app 文件。

另外 projectName-xxx-xxx 目錄下則是對應平臺的可執行文件。

mac 安裝包

安裝 forge 後打包時默認的是壓縮包,make 目錄下是一個名爲 zip 的文件夾,文件夾裏最終是一個 zip 文件,解壓後是 app 格式的 mac 執行文件,可以直接打開。

那麼如果打一個安裝包?

先看看安裝 forge 後 package.json 文件的變化,增加了相關的依賴和 config,如下:

{
  ...
  "devDependencies": {
    "@electron-forge/cli": "^6.0.0-beta.54",
    "@electron-forge/maker-deb": "^6.0.0-beta.54",
    "@electron-forge/maker-rpm": "^6.0.0-beta.54",
    "@electron-forge/maker-squirrel": "^6.0.0-beta.54",
    "@electron-forge/maker-zip": "^6.0.0-beta.54",
    "electron": "11.3.0"
  },
  "dependencies": {
    "electron-squirrel-startup": "^1.0.0"
  },
  "config": {
    "forge": {
      "packagerConfig": {},
      "makers": [
        {
          "name": "@electron-forge/maker-zip",
          "platforms": [
            "darwin"
          ]
        },
        {
          "name": "@electron-forge/maker-deb",
          "config": {}
        },
        ...
      ]
    }
  }
}

可以看到依賴了 maker-zip,maker-deb 等等,而在configforge下有一個makers屬性,是一個 array,其中除了@electron-forge/maker-zip,還有@electron-forge/maker-deb等其他maker,這個maker就是打包關鍵。

electron-forge 的一個優點就是集成了大量的工具,使得打包操作更加簡單方便,每個 maker 就是一種打包編譯工具,比如 maker-zip 就是打包成 zip 格式,forge 包含的所有 maker 如下(參考 https://www.electronforge.io/config/makers ):

可以看到除了 Zip,其他都依賴某個或某幾個平臺,甚至部分還依賴特地的環境。比如 Squirrel.Windows 如果在 mac 或 linux 上編譯,則需要先安裝 mono 和 wine。

回到我們的問題,可以看到上面依賴了很多 forge 的 maker 其實是不用的,只有 Zip 可用,所以可以暫時刪除。而我們要打包安裝包,則需要依賴 maker-dmg,執行安裝命令npm i @electron-forge/maker-dmg --save-dev,安裝後在configmaker中添加一條 maker-dmg,如下:

{
  ...
  "devDependencies": {
    "@electron-forge/cli": "^6.0.0-beta.54",
    "@electron-forge/maker-dmg": "^6.0.0-beta.54",
    "@electron-forge/maker-zip": "^6.0.0-beta.54",
    "electron": "11.3.0"
  },
  "dependencies": {
    "electron-squirrel-startup": "^1.0.0"
  },
  "config": {
    "forge": {
      "packagerConfig": {
        ...
      },
      "makers": [
        {
          "name": "@electron-forge/maker-zip",
          "platforms": [
            "darwin"
          ]
        },
        {
          "name": "@electron-forge/maker-dmg",
          "config": {
              "name": "小盒課堂mac",   //可以是中文,但只是安裝包名稱。
              "icon": "./icon.icns", 
              "background": "./share_parent_bg_blue.png"          
          }
        }
      ]
    }
  }
}

上面清理了不需要的 maker,只留下 zip 和 dmg,然後執行npm run make打包。打包完成在 / out/make / 目錄下看到除了之前的 zip 目錄,多生成了一個 dmg 文件,這樣安裝包就打好了。(如果只打包 dmg,則可以在 maker 中刪除 maker-zip 即可)

後面可以通過 maker-dmg 的 config 進行一些設置,如安裝背景 background、安裝包名稱等。見 https://js.electronforge.io/maker/dmg/interfaces/makerdmgconfig

注意: config中的name如果不設置,則默認應用名稱,先取prodectName,沒有再取name。而且注意如果不設置的話background好像也不會生效,感覺是個 bug。

windows 安裝包

windows 上與 mac 類似,安裝 forge 後也會默認安裝多個 maker,如 zip、squirrel、rpm 和 deb。其中 zip 就是壓縮包,squirrel 則是安裝包,另外兩個用不到可以刪除相關代碼,如下:

{
  ...
  "devDependencies": {
    "@electron-forge/cli": "^6.0.0-beta.54",
    "@electron-forge/maker-squirrel": "^6.0.0-beta.54",
    "@electron-forge/maker-zip": "^6.0.0-beta.54",
    "electron": "12.0.1"
  },
  "dependencies": {
    "electron-squirrel-startup": "^1.0.0"
  },
  "config": {
    "forge": {
      "packagerConfig": {
          "icon": "./icon",
          "platform": "all"      
      },
      "makers": [
        {
          "name": "@electron-forge/maker-zip",
          "platforms": [
            ...
          ]
        },
        {
          "name": "@electron-forge/maker-squirrel",
          "config": {}
        },
        ...
      ]
    }
  }
}

這樣打包的時候會在 out/make 目錄下生成 zip 和安裝包(exe)。當然還需要配置一下.

zip 下的 platforms 可以去掉,去掉後會根據平臺默認打包。

雖然我們在 packagerConfig 設置了圖標,但是這個是應用的圖標,即安裝後應用入口文件的圖標,而安裝包文件的圖標需要另外設置:

{
  "name": "@electron-forge/maker-squirrel",
  "config": {
     "name": "xxx",  //不能是中文,否則安裝時出錯,可以不設置
     "setupExe": "xxxsetup.exe",//可以是中文,可以不設置
     "setupIcon": "./icon.ico",  //安裝包圖標,可以不設置   
  }
}

其中setupExe設置安裝包的名字(這裏可以是中文),而setupIcon則是安裝圖標,這裏單獨設置setupIcon好像不起作用,必須兩個都設置纔行。

然後在安裝的過程中,會默認出現一個加載的動畫,這個也可以進行替換,設置loadingGif即可:

{
  "name": "@electron-forge/maker-squirrel",
  "config": {
     "name": "xxx",   //不能是中文,否則安裝時出錯,可以不設置
     "setupExe": "xxx.exe",//可以是中文,可以不設置
     "setupIcon": "./icon.ico",  //安裝包圖標,可以不設置   
     "loadingGif": "./installing.gif"   
  }
}

這個動畫是一個 gif 動畫。

這裏有一個問題,如果將productName設置成中文名稱,那麼通過 squirrel 打包的時候會報錯Unable to load file,反覆測試發現是setupIcon導致的,不設置這個屬性就可以正常打包。

但是使用英文名稱就沒有問題,目前沒有找到根本原因,如果要使用中文的應用名稱,就不設置setupIcon使用默認的好了。

squirrel 官網介紹:https://www.electronforge.io/config/makers/squirrel.windows

相關參數

https://js.electronforge.io/maker/squirrel/classes/makersquirrel#constructor

https://js.electronforge.io/maker/squirrel/interfaces/makersquirrelconfig

最終的配置如下:

{
  ...
  "devDependencies": {
    "@electron-forge/cli": "^6.0.0-beta.54",
    "@electron-forge/maker-squirrel": "^6.0.0-beta.54",
    "@electron-forge/maker-zip": "^6.0.0-beta.54",
    "electron": "12.0.1"
  },
  "dependencies": {
    "electron-squirrel-startup": "^1.0.0"
  },
  "config": {
    "forge": {
      "packagerConfig": {
          "icon": "./icon",
          "platform": "all"      
      },
      "makers": [
        {
          "name": "@electron-forge/maker-zip",
        },
        {
          "name": "@electron-forge/maker-squirrel",
          "config": {
             "name": "xxx",  
             "setupExe": "xxx.exe", //可以不設置。如果不設置setupIcon,那麼這裏也可以不設置,用默認的名稱就好
             "setupIcon": "./icon.ico",  //如果使用中文應用名稱,暫時不能設置setupIcon,否則報錯
             "loadingGif": "./installing.gif"            
          }
        },
        ...
      ]
    }
  }
}

最後在安裝過程中程序會啓動幾次,體驗很不好,要解決這個問題,需要在 main.js 的開頭添加:

const { app } = require('electron');
if (require('electron-squirrel-startup')) return app.quit();

這樣就可以防止多次啓動。

安裝包安裝後會在桌面和開始菜單創建快捷方式。

應用名稱和圖標

名稱

package.json中的第一個屬性name就是應用名稱(實際上是 application id)。但是這裏的name不能是中文,Electron 不支持 applicationId 設置爲中文,我們可以通過設置productName來設置中文名稱,如下:

{
    "name": "my-electron-app",
    "productName": "中文名稱",
    "version": "0.1.0",
    "author": "your name",
    "description": "My Electron app",
    "main": "main.js",
    "scripts": {
        "start": "electron ."
    }
}

這樣所有顯示的應用名稱都會換成productName

但是這裏有一個問題,productName改成中文後,在 windows 上我們通過 Squirrel.Windows 的方式打安裝包,但是執行npm run make到 squirrel maker 的時候會報錯Unable to load file。這個上面說過,就不再細說了。

圖標

修改圖標則需要對 electron forge 設置,通過上面爲項目安裝使用 electron forge 後,在 package.json 中會自動添加相關的 config,如下:

{
  ...
  "config": {
    "forge": {
      "packagerConfig": {},
      "makers": [
        {
          "name": "@electron-forge/maker-zip",
          "platforms": [
            "darwin"
          ]
        }
      ]
    }
  }
}

我們在packagerConfig中添加圖標即可,macOS 上的圖標必須是 icns 格式,而 windows 的圖標必須是 ico 格式。如果只是一個平臺,如 mac,將圖標放到項目的跟目錄下,修改packagerConfig如下:

     "packagerConfig": {
        "icon": "./icon.icns"
      },

這樣即可,注意使用npm start直接運行的時候圖標還是默認的 electron 圖標,但是使用npm run make打包後圖標就是我們設置的圖標了。

那麼如果多個平臺怎麼辦?

根據官方文檔(https://electron.github.io/electron-packager/master/interfaces/electronpackager.options.html#icon ),將每個平臺的圖標都放到項目中,並且名稱一樣,這樣在配置時不添加後綴名即可,打包時會根據平臺自動找到相應圖標。

If the file extension is omitted, it is auto-completed to the correct extension based on the platform, including when platform: 'all' is in effect.

比如將 mac 的圖標 icon.icns 和 windows 的圖標 icon.ico 都放在根目錄下,然後修改配置

     "packagerConfig": {
        "icon": "./icon",
        "platform": "all"
      },

即可。

其他問題

跨域問題

應用使用的過程中發現出現了跨域問題,無法訪問某些地址,提示CORS(Cross-Origin Resource Sharing)問題。

這是因爲 Electron 的默認配置導致的,在 Electron 中默認是開啓同源策略的,這樣就導致無法訪問外部的一些鏈接。我們需要手動進行開啓,修改 main.js 代碼如下:

const { app, BrowserWindow } = require('electron')
function createWindow () {
  const win = new BrowserWindow({
    width: 800,
    height: 600,
    webPreferences: {
      nodeIntegration: true,
      webSecurity: false  //禁用同源策略
    }
  })
  win.loadFile('index.html')
}
...

相關說明如下:

主進程日誌問題

我們在 Electron 的 main.js 中通過console.log打印日誌,以便查看調試應用。但是如果通過終端執行npm start的時候,可以在終端中看到日誌輸出。但是打包成安裝包(或可執行文件),直接打開就沒法看到日誌了。雖然上面提到了,我們可以通過打開開發者工具查看 console,但是發現這裏根本沒有打印這部分日誌。

這是因爲 Electron 有兩個進程:主進程 和 渲染進程,main.js 是運行在主進程中的,而通過 BrowserWindow 裝載 load 的文件或網站則運行在渲染進程,上面提到的開發者工具,實際上只能查看渲染進程的。

所以主進程中的日誌就無法查看了,但是有幾個方法可以考慮:

const { dialog } = require('electron')
...
dialog.showMessageBox({message:"xxxxxx"})

應用更新

這個涉及較多,我單獨寫一篇文章來講。

禁止本地緩存

使用過程中發現一個問題,因爲我們是將代碼放在服務端,在 electron 中只是加載了一個 url。發現有時候雖然服務端文件更新了,但是在 electron 應用內(通過 npm start 啓動)訪問的還是舊的代碼,這時候我們直接用瀏覽器訪問就是新的代碼。

這應該就是 electron 有本地緩存導致的,查了一下只要加上下面一行代碼即可:

app.commandLine.appendSwitch("--disable-http-cache")
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/GLAadPpTKCekVVUbQ1DOiA