[electron]終極奧義 五千字教程丟給你

小心夾手發表於2018-09-18

前言

本文包含打包自動更新簡易API除錯程式通訊等相關知識點,內容較多,可能會引起不適,請酌情檢視(手動滑稽)。

electron

簡介

electron是由Github開發,是一個用Html、css、JavaScript來構建桌面應用程式的開源庫,可以打包為Mac、Windows、Linux系統下的應用。

electron是一個執行時環境,包含Node和Chromium,可以理解成把web應用執行在node環境中

結構

electron主要分為主程式和渲染程式,關係如下圖

[electron]終極奧義 五千字教程丟給你

electron執行package.json中的main欄位標明指令碼的程式稱為主程式

在主程式建立web頁面來展示使用者頁面,一個electron有且只有一個主程式

electron使用Chromium來展示web頁面,每個頁面執行在自己的渲染程式

快速開始

接下來,讓程式碼來發聲,雷打不動的hello world

建立資料夾,並執行npm init -y,生成package.json檔案,下載electron模組並新增開發依賴

mkdir electron_hello && cd electron_hello && npm init -y && npm i electron -D
複製程式碼

下載速度過慢或失敗,請嘗試使用cnpm,安裝方式如下

# 下載cnpm
npm install -g cnpm --registry=https://registry.npm.taobao.org
# 下載electron
cnpm i electron -D
複製程式碼

建立index.js,並寫入以下內容

const {app, BrowserWindow} = require('electron')

// 建立全域性變數並在下面引用,避免被GC
let win

function createWindow () {
    // 建立瀏覽器視窗並設定寬高
    win = new BrowserWindow({ width: 800, height: 600 })
    
    // 載入頁面
    win.loadFile('./index.html')
    
    // 開啟開發者工具
    win.webContents.openDevTools()
    
    // 新增window關閉觸發事件
    
    win.on('closed', () => {
        win = null  // 取消引用
    })
}

// 初始化後 呼叫函式
app.on('ready', createWindow)  

// 當全部視窗關閉時退出。
app.on('window-all-closed', () => {
   // 在 macOS 上,除非使用者用 Cmd + Q 確定地退出,
   // 否則絕大部分應用及其選單欄會保持啟用。
   if (process.platform !== 'darwin') {
        app.quit()
   }
})
  
app.on('activate', () => {
// 在macOS上,當單擊dock圖示並且沒有其他視窗開啟時,
// 通常在應用程式中重新建立一個視窗。
    if (win === null) {
      createWindow()
    }
})
複製程式碼

建立index.html

<!DOCTYPE html>
<html>
    <head>
      <meta charset="UTF-8">
      <title>Hello World!</title>
    </head>
    <body>
        <h1 id="h1">Hello World!</h1>
        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>
    </body>
</html>
複製程式碼

最後,修改packge.json中的main欄位,並新增start命令

{
    ...
    main:'index.js',
    scripts:{
        "start": "electron ."
    }
}
複製程式碼

執行npm run start後,就會彈出我們的應用來。

[electron]終極奧義 五千字教程丟給你

除錯

我們知道electron有兩個程式,主程式和渲染程式,開發過程中我們需要怎麼去除錯它們呢?老太太吃柿子,我們們撿軟的來

渲染程式

BrowserWindow 用來建立和控制瀏覽器視窗,我們呼叫它的例項上的API即可

win = new BrowserWindow({width: 800, height: 600})
win.webContents.openDevTools() // 開啟除錯
複製程式碼

除錯起來是和Chrome是一樣的,要不要這麼酸爽

[electron]終極奧義 五千字教程丟給你

主程式

使用VSCode進行除錯

使用VSCode開啟專案,點選除錯按鈕

[electron]終極奧義 五千字教程丟給你

點選除錯後的下拉框

[electron]終極奧義 五千字教程丟給你

選擇新增配置,選擇node

[electron]終極奧義 五千字教程丟給你

此時會把預設的除錯配置開啟,大概長這樣

[electron]終極奧義 五千字教程丟給你

什麼?你的不是,不是的話,就直接把下面的複製並替換你的配置

差不多這麼辦,那就把configurations裡面第二項複製到你的configurations配置裡面,第一個配置是用來除錯node的

{
  "version": "0.2.0",
  "configurations": [
    {
      "type": "node",
      "request": "launch",
      "name": "啟動程式",
      "program": "${workspaceFolder}/main.js"
    },
    {
        "name": "Debug Main Process",
        "type": "node",
        "request": "launch",
        "cwd": "${workspaceFolder}",
        "runtimeExecutable": "${workspaceFolder}/node_modules/.bin/electron",
        "windows": {
          "runtimeExecutable": "${workspaceFolder}/node_modules/.bin/electron.cmd"
        },
        "args" : ["."]
      }
  ]
}
複製程式碼

可以看到${workspaceFolder},這是關於VSCode的變數,用來表示當前開啟的資料夾的路徑

更多變數請猛戳這裡

修改完配置後,我們除錯皮膚,選擇我們剛才配置的

[electron]終極奧義 五千字教程丟給你

在程式碼中標記需要除錯的地方,然後點選綠色的小三角,就可以愉快的除錯了

[electron]終極奧義 五千字教程丟給你

程式通訊

在Electron中, GUI 相關的模組 (如 dialog、menu 等) 僅在主程式中可用, 在渲染程式中不可用。 為了在渲染程式中使用它們, ipc 模組是向主程式傳送程式間訊息所必需的,以下介紹幾種程式間通訊的方法。

哥倆好

ipcMainipcRenderer是兩個好基友,通過這兩個模組可以實現程式的通訊。

  • ipcMain 在主程式中使用,用來處理渲染程式(網頁)傳送的同步和非同步的資訊

  • ipcRenderer 在渲染程式中使用,用來傳送同步或非同步的資訊給主程式,也可以用來接收主程式的回覆資訊。

以上兩個模組的通訊,可以理解成釋出訂閱模式,接下來,我們看下它們具體的使用方法

主程式

const {ipcMain} = require('electron')

// 監聽渲染程式發來的事件
ipcMain.on('something', (event, data) => {
    event.sender.send('something1', '我是主程式返回的值')
})
複製程式碼

渲染程式

const { ipcRenderer} = require('electron') 

// 傳送事件給主程式
ipcRenderer.send('something', '傳輸給主程式的值')  

// 監聽主程式發來的事件
ipcRenderer.on('something1', (event, data) => {
    console.log(data) // 我是主程式返回的值
})
複製程式碼

以上程式碼使用的是非同步傳輸訊息,electron也提供了同步傳輸的API。

傳送同步訊息將會阻塞整個渲染程式,你應該避免使用這種方式 - 除非你知道你在做什麼。

切忌用 ipc 傳遞大量的資料,會有很大的效能問題,嚴重會讓你整個應用卡住。

remote模組

使用 remote 模組, 你可以呼叫 main 程式物件的方法, 而不必顯式傳送程式間訊息。

const { dialog } = require('electron').remote
dialog.showMessageBox({type: 'info', message: '在渲染程式中直接使用主程式的模組'})
複製程式碼

webContents

webContents負責渲染和控制網頁, 是 BrowserWindow 物件的一個屬性, 我們使用send方法向渲染器程式傳送非同步訊息。

主程式

const {app, BrowserWindow} = require('electron')

let win

app.on('ready', () => {

    win = new BrowserWindow({width: 800, height: 600})
    
    // 載入頁面
    win.loadURL('./index.html')
    
    // 導航完成時觸發,即選項卡的旋轉器將停止旋轉,並指派onload事件後。
    win.webContents.on('did-finish-load', () => {

        // 傳送資料給渲染程式
        win.webContents.send('something', '主程式傳送到渲染程式的資料')
    })
})
複製程式碼

渲染程式

<!DOCTYPE html>
<html>
    <head>
    </head>
    <body>
        <script>
            require('electron').ipcRenderer.on('something', (event, message) => {
                console.log(message) // 主程式傳送到渲染程式的資料
            })
        </script>
    </body>
</html>
複製程式碼

渲染程式資料共享

更多情況下,我們使用HTML5 API實現,如localStorage、sessionStorage等,也可以使用electron的IPC機制實現

主程式

global.sharedObject = {
    someProperty: 'default value'
}
複製程式碼

渲染程式

第一個頁面

require('electron').remote.getGlobal('sharedObject').someProperty = 'new value'
複製程式碼

第二個頁面

console.log(require('electron').remote.getGlobal('sharedObject').someProperty) // new value
複製程式碼

總結

以上四個方法均可實現主程式和渲染程式的通訊,可以發現使用remote模組是最簡單的,渲染程式程式碼中可以直接使用electron模組

常用模組

快捷鍵與選單

本地快捷鍵

[electron]終極奧義 五千字教程丟給你

只有在應用聚焦的時候才會觸發,主要程式碼如下

官方文件親測沒有效果

const { Menu, MenuItem } = require('electron')
const menu = new Menu()
  
menu.append(new MenuItem({
  label: '自定義快捷鍵',
  submenu: [
    {
      label: '測試',
      accelerator: 'CmdOrCtrl+P',
      click: () => { 
          console.log('我是本地快捷鍵')
      }
    }
  ]
}))
Menu.setApplicationMenu(menu)
複製程式碼

全域性快捷鍵

註冊全域性,無論應用是否聚焦,都會觸發,使用globalShortcut來註冊, 主要程式碼如下,

const {globalShortcut, dialog} = require('electron')

app.on('read', () => {
    globalShortcut.register('CmdOrCtrl+1', () => {
        dialog.showMessageBox({
            type: 'info',
            message: '你按下了全域性註冊的快捷鍵'
          })
    })
})
複製程式碼

顯示如下,使用了dialog模組

[electron]終極奧義 五千字教程丟給你

上下文選單

上下文選單就是我們點選右鍵的時候顯示的選單, 設定在渲染程式裡面

[electron]終極奧義 五千字教程丟給你

主要程式碼如下

const remote = require('electron').remote;  // 通過remote模組使用主程式才能使用的模組
const Menu = remote.Menu;
const MenuItem = remote.MenuItem;
var menu = new Menu();
menu.append(new MenuItem({ label: 'MenuItem1', click: function() { console.log('item 1 clicked'); } }));
menu.append(new MenuItem({ type: 'separator' }));
menu.append(new MenuItem({ label: 'MenuItem2', type: 'checkbox', checked: true }));

window.addEventListener('contextmenu', (e) => {
  e.preventDefault();
  menu.popup(remote.getCurrentWindow());
}, false);
複製程式碼

應用選單

[electron]終極奧義 五千字教程丟給你

上文中我們通過Menu模組建立了本地快捷鍵,應用選單也是用Menu模組,我們看到長得好像一樣,其實實現起來也差不多,我們這裡使用建立模版的方式來實現,主要程式碼如下:

const {app, Menu} = require('electron')
const template = [
  {
    label: '操作',
    submenu: [{
        label: '複製',
        accelerator: 'CmdOrCtrl+C',
        role: 'copy'
    }, {
        label: '貼上',
        accelerator: 'CmdOrCtrl+V',
        role: 'paste'
    }, {
        label: '重新載入',
        accelerator: 'CmdOrCtrl+R',
        click: function (item, focusedWindow) {
            if (focusedWindow) {
                // on reload, start fresh and close any old
                // open secondary windows
                if (focusedWindow.id === 1) {
                    BrowserWindow.getAllWindows().forEach(function (win) {
                        if (win.id > 1) {
                            win.close()
                        }
                    })
                }
                focusedWindow.reload()
            }
        }
    }]
  },
  {
    label: '載入網頁',
    submenu: [
      {
        label: '優酷',
        accelerator: 'CmdOrCtrl+P',
        click: () => { console.log('time to print stuff') }
      },
      {
        type: 'separator'
      },
      {
        label: '百度',
      }
    ]
  }
]

if (process.platform === 'darwin') {
    const name = electron.app.getName()
    template.unshift({
      label: name,
      submenu: [{
        label: `關於 ${name}`,
        role: 'about'
      }, {
        type: 'separator'
      }, {
        label: '服務',
        role: 'services',
        submenu: []
      }, {
        type: 'separator'
      }, {
        label: `隱藏 ${name}`,
        accelerator: 'Command+H',
        role: 'hide'
      }, {
        label: '隱藏其它',
        accelerator: 'Command+Alt+H',
        role: 'hideothers'
      }, {
        label: '顯示全部',
        role: 'unhide'
      }, {
        type: 'separator'
      }, {
        label: '退出',
        accelerator: 'Command+Q',
        click: function () {
          app.quit()
        }
      }]
    })
}

app.on('read', () => {
    const menu = Menu.buildFromTemplate(template);
    Menu.setApplicationMenu(menu);
})

複製程式碼

系統托盤

[electron]終極奧義 五千字教程丟給你

主要程式碼

const { app, Tray } = require('electron') 
/* 省略其他程式碼 */
let tray;
app.on('ready', () => {
    tray = new Tray(__dirname + '/build/icon.png');//系統托盤圖示
    
    const contextMenu = Menu.buildFromTemplate([ // 選單項
      {label: '顯示', type: 'radio', click: () => {win.show()}},
      {label: '隱藏', type: 'radio', click: () => {win.hide()}},
    ])
    
    // tray.on('click', () => { //  滑鼠點選事件最好和選單隻設定一種
    //   win.isVisible() ? win.hide() : win.show()
    // })
    
    tray.setToolTip('This is my application.') // 滑鼠放上時候的提示
    
    tray.setContextMenu(contextMenu) // 應用選單項
})

複製程式碼

模組一覽

Menu

MenuItem

globalShortcut

dialog

tray

開啟頁面

shell

主程式開啟頁面,會呼叫預設的瀏覽器開啟頁面,使用shell模組

const { app,shell } = require('electron')

app.on('ready', () => {
    shell.openExternal('github.com')
})
複製程式碼

應用啟動後就會使用預設瀏覽器開啟github頁面

window.open

渲染程式中使用,當前應用開啟頁面

window.open('github.com')
複製程式碼

webview

使用webview可以在一個獨立的 frame 和程式裡顯示外部 web 內容

[electron]終極奧義 五千字教程丟給你

直接在index.html中使用即可

<webview id="foo" src="https://www.github.com/" style="display:inline-flex; width:640px; height:480px"></webview>
複製程式碼

模組一覽

shell

window.open

webview

QA

其他應用模組和API請下載

演示應用程式

演示應用程式中文版

模版

electron-forge

electron-forge包含vuereactAngular等開箱即用的模版。

npm i -g electron-forge
electron-forge init my-app template=react
cd my-app
npm run start 
複製程式碼

目錄結構如下

[electron]終極奧義 五千字教程丟給你

.compilercelectron-compile的配置檔案,和.babelrc類似

electron-compile是一種轉換規則,支援在應用程式中編譯js和css

electron-react-boilerplate

如果你不希望任何工具,而想要簡單地從一個模板開始構建,react-electron模版可以瞭解下。

electron-react-boilerplate 是雙package.json配置專案, 目錄如下

[electron]終極奧義 五千字教程丟給你

electron-vue

electron-vue充分利用 vue-cli 作為腳手架工具,加上擁有 vue-loader 的 webpack、electron-packager 或是 electron-builder,以及一些最常用的外掛,如vue-router、vuex 等等。

目錄結構如下

[electron]終極奧義 五千字教程丟給你

中文教程

更多模版

更多模版請訪問

打包

怎麼將我們開發好的應用打包成.app.exe的執行檔案,這就涉及到了重要的打包環節, 這裡使用electron-quick-start專案進行打包

目前,主流的打包工具有兩個electron-packagerelectron-builder

Mac打包window安裝包需下載wine

brew install wine

如果有丟失元件,按照報錯資訊進行下載即可

electron-packager

electron-packager把你的electron打包成可執行檔案(.app, .exe, etc)

執行npm i electron-packager -D進行安裝

electron-packager . 快速打包

打包詳解

electron-packager <sourcedir> <appname> --platform=<platform> --arch=<arch> --out=out --icon=assets/app.ico --asar --overwrite --ignore=.git
複製程式碼
  • sourcedir 專案入口 根據package.json的目錄
  • appname 包名
  • platform 構建平臺 包含 darwin, linux, mas, win32 all
  • arch 構建架構 包含ia32,x64,armv7l,arm64
  • out 打包後的地址
  • icon 打包圖示
  • asar 是否生成app.asar, 不然就是自己的原始碼
  • overwrite 覆蓋上次打包
  • ignore 不進行打包的檔案

第一次打包需要下載二進位制的包耗時會久一些,以後走快取就快的多了。

打包後目錄為

[electron]終極奧義 五千字教程丟給你

其中electron-quick-start可以直接開啟執行

快取地址

  • Mac ~/Library/Caches/electron/
  • Windows $LOCALAPPDATA/electron/Cache or ~/AppData/Local/electron/Cache/
  • Linux $XDG_CACHE_HOME or ~/.cache/electron/

electron-builder

electron-builder是一個完整的解決方案,對於macos、windows、linux下的electron app,它可以提供打包及構建的相關功能。同時,它還提供開箱即用的“自動更新”功能支援

npm i electron-builder -D 下載

electron-builder打包

出現如下

[electron]終極奧義 五千字教程丟給你

同樣是第一次打包時間會比較長,彆著急,靜候佳音。

會預設打包在dist目錄,包含如下

[electron]終極奧義 五千字教程丟給你

mac檔案裡有執行時檔案,預設使用asar方式進行打包

electron-builder --dir 只會生成包檔案,對測試有用

[electron]終極奧義 五千字教程丟給你

坑坑坑

第一次打包的時候會比較慢,如果你和我手欠直接退出了,再次打包的時候,恭喜你,出錯了。報錯資訊如下

  • electron-builder version=20.28.2
  • loaded configuration file=package.json ("build" field)
  • description is missed in the package.json appPackageFile=/Users/shisan/Documents/self/you-app/package.json
  • writing effective config file=dist/builder-effective-config.yaml
  • no native production dependencies
  • packaging       platform=darwin arch=x64 electron=2.0.7 appOutDir=dist/mac
  • cannot unpack electron zip file, will be re-downloaded error=zip: not a valid zip file
  ⨯ zip: not a valid zip file

Error: /Users/shisan/Documents/self/you-app/node_modules/app-builder-bin/mac/app-builder exited with code 1
    at ChildProcess.childProcess.once.code (/Users/shisan/Documents/self/you-app/node_modules/builder-util/src/util.ts:254:14)
    at Object.onceWrapper (events.js:272:13)
    at ChildProcess.emit (events.js:180:13)
    at maybeClose (internal/child_process.js:936:16)
    at Process.ChildProcess._handle.onexit (internal/child_process.js:220:5)
From previous event:
    at unpack (/Users/shisan/Documents/self/you-app/node_modules/app-builder-lib/out/electron/ElectronFramework.js:191:18)
    at Object.prepareApplicationStageDirectory (/Users/shisan/Documents/self/you-app/node_modules/app-builder-lib/src/electron/ElectronFramework.ts:148:50)
    at /Users/shisan/Documents/self/you-app/node_modules/app-builder-lib/src/platformPackager.ts:179:21
    at Generator.next (<anonymous>)
From previous event:
    at MacPackager.doPack (/Users/shisan/Documents/self/you-app/node_modules/app-builder-lib/src/platformPackager.ts:166:165)
    at /Users/shisan/Documents/self/you-app/node_modules/app-builder-lib/src/macPackager.ts:88:63
    at Generator.next (<anonymous>)
From previous event:
    at MacPackager.pack (/Users/shisan/Documents/self/you-app/node_modules/app-builder-lib/src/macPackager.ts:80:95)
    at /Users/shisan/Documents/self/you-app/node_modules/app-builder-lib/src/packager.ts:376:24
    at Generator.next (<anonymous>)
    at xfs.stat (/Users/shisan/Documents/self/you-app/node_modules/fs-extra-p/node_modules/fs-extra/lib/mkdirs/mkdirs.js:56:16)
    at /Users/shisan/Documents/self/you-app/node_modules/graceful-fs/polyfills.js:287:18
    at FSReqWrap.oncomplete (fs.js:171:5)
From previous event:
    at Packager.doBuild (/Users/shisan/Documents/self/you-app/node_modules/app-builder-lib/src/packager.ts:344:39)
    at /Users/shisan/Documents/self/you-app/node_modules/app-builder-lib/src/packager.ts:314:57
    at Generator.next (<anonymous>)
    at /Users/shisan/Documents/self/you-app/node_modules/graceful-fs/graceful-fs.js:99:16
    at /Users/shisan/Documents/self/you-app/node_modules/graceful-fs/graceful-fs.js:43:10
    at FSReqWrap.oncomplete (fs.js:153:20)
From previous event:
    at Packager._build (/Users/shisan/Documents/self/you-app/node_modules/app-builder-lib/src/packager.ts:285:133)
    at /Users/shisan/Documents/self/you-app/node_modules/app-builder-lib/src/packager.ts:281:23
    at Generator.next (<anonymous>)
From previous event:
    at Packager.build (/Users/shisan/Documents/self/you-app/node_modules/app-builder-lib/src/packager.ts:238:14)
    at build (/Users/shisan/Documents/self/you-app/node_modules/app-builder-lib/src/index.ts:58:28)
    at build (/Users/shisan/Documents/self/you-app/node_modules/electron-builder/src/builder.ts:227:10)
    at then (/Users/shisan/Documents/self/you-app/node_modules/electron-builder/src/cli/cli.ts:42:48)
    at runCallback (timers.js:763:18)
    at tryOnImmediate (timers.js:734:5)
    at processImmediate (timers.js:716:5)
From previous event:
    at Object.args [as handler] (/Users/shisan/Documents/self/you-app/node_modules/electron-builder/src/cli/cli.ts:42:48)
    at Object.runCommand (/Users/shisan/Documents/self/you-app/node_modules/yargs/lib/command.js:237:44)
    at Object.parseArgs [as _parseArgs] (/Users/shisan/Documents/self/you-app/node_modules/yargs/yargs.js:1085:24)
    at Object.get [as argv] (/Users/shisan/Documents/self/you-app/node_modules/yargs/yargs.js:1000:21)
    at Object.<anonymous> (/Users/shisan/Documents/self/you-app/node_modules/electron-builder/src/cli/cli.ts:25:28)
    at Module._compile (internal/modules/cjs/loader.js:654:30)
    at Object.Module._extensions..js (internal/modules/cjs/loader.js:665:10)
    at Module.load (internal/modules/cjs/loader.js:566:32)
    at tryModuleLoad (internal/modules/cjs/loader.js:506:12)
    at Function.Module._load (internal/modules/cjs/loader.js:498:3)
    at Function.Module.runMain (internal/modules/cjs/loader.js:695:10)
    at startup (internal/bootstrap/node.js:201:19)
    at bootstrapNodeJSCore (internal/bootstrap/node.js:516:3)
npm ERR! code ELIFECYCLE
npm ERR! errno 1
npm ERR! you-app@1.0.0 dist: `electron-builder --mac --x64`
npm ERR! Exit status 1
npm ERR!
npm ERR! Failed at the you-app@1.0.0 dist script.
npm ERR! This is probably not a problem with npm. There is likely additional logging output above.

npm ERR! A complete log of this run can be found in:
npm ERR!     /Users/shisan/.npm/_logs/2018-08-22T06_28_55_102Z-debug.log
✖  Error building interactive interface
複製程式碼

問題是在下載.zip包的時候,中斷了操作,以後所有執行打包的時候,找不到那個檔案(或者是殘缺的檔案)就報錯了,需要手動清除下快取 快取路徑在~/Library/Caches/electron/

常用引數

electron-builder配置檔案寫在package.json中的build欄位中

"build": {
    "appId": "com.example.app", // 應用程式id 
    "productName": "測試", // 應用名稱 
    "directories": {
        "buildResources": "build", // 構建資源路徑預設為build
        "output": "dist" // 輸出目錄 預設為dist
    },
    "mac": {
        "category": "public.app-category.developer-tools", // 應用程式類別
        "target": ["dmg", "zip"],  // 目標包型別 
        "icon": "build/icon.icns" // 圖示的路徑
    },
    "dmg": {
        "background": "build/background.tiff or build/background.png", // 背景影像的路徑
        "title": "標題",
        "icon": "build/icon.icns" // 圖示路徑
    },
    "win": {
        "target": ["nsis","zip"] // 目標包型別 
    }
}
複製程式碼

應用程式類別

更多引數

使用electron-package或者electron-builder進行打包,就不需要asar手動打包了,這兩個工具已經整合進去了,以下內容只做瞭解即可

asar

簡介

asar是一種將多個檔案合併成一個檔案的類 tar 風格的歸檔格式。 Electron 可以無需解壓整個檔案,即可從其中讀取任意檔案內容。

使用

上面我們看到使用electron-package進行打包的時候,可以新增--asar引數, 它的作用就是將我們的原始碼打包成asar格式, 不使用的話就是我們的原始碼。

依舊使用electron-quick-start模版, 使用electron-package進行打包

打包後目錄中選擇應用,右鍵顯示包內容

[electron]終極奧義 五千字教程丟給你

依次開啟路徑 Contents -> Resources -> app

就可以看到我們的原檔案了

[electron]終極奧義 五千字教程丟給你

使用electron-package . --asar進行打包,再次檢視app中的原始檔已經打包成asar檔案了

[electron]終極奧義 五千字教程丟給你

常用命令

  • 打包
asar pack|p <dir> <ouput>
複製程式碼

asar ./ app.asar

  • 解壓
asar extract|e <archive> <output>
複製程式碼

asar e app.asar ./

事實上,就算使用了asar打包方式,我們的原始碼依舊是暴露的,使用asar解壓仍然能輕易解密,使用webpack將程式碼壓縮混淆才是王道。

自動更新

我們使用electron-builderelectron-updater來完成應用的自動更新, 並不是electron裡面的autoUpdater

流程

[electron]終極奧義 五千字教程丟給你

建立證照

無論使用哪種方式進行自動更新,程式碼簽名是必不可少的,我們使用建立本地的證照來進行測試自動更新

在其他中找到鑰匙串訪問

[electron]終極奧義 五千字教程丟給你

依次選擇 鑰匙串訪問 -> 證照助理 -> 建立證照,建立一個新的證照

[electron]終極奧義 五千字教程丟給你

起個響亮的名字,證照型別選擇程式碼簽名,然後點選建立

[electron]終極奧義 五千字教程丟給你

建立完成後在鑰匙串中找到它

[electron]終極奧義 五千字教程丟給你

右鍵,選擇更改信任設定,將信任設定成始終信任

[electron]終極奧義 五千字教程丟給你

程式碼簽名

  • 沒有程式碼簽名

    [electron]終極奧義 五千字教程丟給你

  • 程式碼簽名

    [electron]終極奧義 五千字教程丟給你

設定環境變數

執行sudo vi ~/.bash_profile, 新增變數

export CSC_NAME="electron_update"
複製程式碼

執行source ~/.bash_profile 過載變數檔案

執行echo $CSC_NAME 檢視變數有沒有生效,沒有生效的話,執行上面命令過載檔案

多次過載,依舊獲取不到變數,直接退出終端,再次登入終端即可

打包程式碼

使用electron-builder進行打包,專案使用electron-quick-start

我們先新增打包的build配置, 在package.json中新增

"build": {
    "appId": "org.electron.com",
    "publish": [
      {
        "provider": "generic",
        "url": "http://172.28.86.36:8080/" 
      }
    ],
    "productName": "我的",
    "directories": {
      "output": "dist"
    },
    "mac": {
      "target": [
        "dmg",
        "zip"
      ]
    },
    "win": {
      "target": [
        "nsis",
        "zip"
      ]
    },
    "dmg": {
      "backgroundColor": "red",
      "title": "made",
      "contents": [
        {
          "x": 400,
          "y": 128,
          "type": "link",
          "path": "/Applications"
        }
      ]
    }
}
複製程式碼

前面,我們說過了build欄位中各個引數的含義,這裡就不贅述了,這裡新增了publish欄位,它是配置我們自動更新的資訊,url是伺服器地址。

伺服器我們可以使用http-server模組, 快速搭建一個靜態伺服器

執行打包後,輸出

[electron]終極奧義 五千字教程丟給你

主要看下latest-mac.yml檔案

[electron]終極奧義 五千字教程丟給你

可以看到version欄位,用來表明當前應用的版本,那這個欄位是從哪來的呢?

沒錯就是我們的package.json檔案中version欄位

自動更新

在主程式中寫入以下程式碼

const {app, BrowserWindow, ipcMain} = require('electron')

const { autoUpdater } = require('electron-updater')

// 本地伺服器地址
const feedURL = `http://172.28.82.40:8080/`

let mainWindow

function createWindow () { // 建立視窗
  mainWindow = new BrowserWindow({width: 800, height: 600})
  mainWindow.loadFile('index.html')

  mainWindow.on('closed', function () {
    mainWindow = null
  })
}

app.on('ready', createWindow)

app.on('window-all-closed', function () {
  if (process.platform !== 'darwin') {
    app.quit()
  }
})

app.on('activate', function () {
  if (mainWindow === null) {
    createWindow()
  }
})

// 監聽自定義update事件
ipcMain.on('update', (e, arg) => {
  checkForUpdate()
})

const checkForUpdate = () => {
  // 設定檢查更新的 url,並且初始化自動更新
  autoUpdater.setFeedURL(feedURL)

 // 監聽錯誤 
  autoUpdater.on('error', message => {
    sendUpdateMessage('err', message)
  })
 // 當開始檢查更新的時候觸發
  autoUpdater.on('checking-for-update', message => {
    sendUpdateMessage('checking-for-update', message);
  })
 // 
  autoUpdater.on('download-progress', function(progressObj) {
    sendUpdateMessage('downloadProgress', progressObj);
  });
  // 更新下載完成事件
  autoUpdater.on('update-downloaded', function(event, releaseNotes, releaseName, releaseDate, updateUrl, quitAndUpdate) {
      
      ipcMain.on('updateNow', (e, arg) => {
          autoUpdater.quitAndInstall();
      });
      sendUpdateMessage('isUpdateNow');
  });
 // 向服務端查詢現在是否有可用的更新
  autoUpdater.checkForUpdates();
}

// 傳送訊息觸發message事件
function sendUpdateMessage(message, data) {
  mainWindow.webContents.send('message', { message, data });
}
複製程式碼

然後更改我們的渲染程式的程式碼

const {ipcRenderer} = require ('electron');
const button = document.querySelector('#button')


const ul = document.querySelector('ul')
button.onclick = () => {
  ipcRenderer.send('update')
}

ipcRenderer.on('message', (event, {message,data }) => {
  let li = document.createElement('li')
  li.innerHTML = message + " <br>data:" + JSON.stringify(data) +"<hr>";
  ul.appendChild(li)
  if (message === 'isUpdateNow') {
    if (confirm('是否現在更新?')) {
        ipcRenderer.send('updateNow');
    }
  }
})
複製程式碼

頁面程式碼

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>Hello World!</title>
  </head>
  <body>
    <h1>Hello World!</h1>
    <h1>當前版本是1.0.3</h1>

    <ul>
      <li>生命週期過程展示</li>
    </ul>

    <button id="button">點選我</button>
    <script>
      // You can also require other files to run in this process
      require('./renderer.js')
    </script>
  </body>
</html>
複製程式碼

這裡使用按鈕點選事件觸發更新操作。

直接打包,安裝這個1.0.3的版本。

然後將package.json的檔案中的version欄位更改成1.0.5,將index.html的更改成1.0.5,進行打包。

隨便找個地方建立個資料夾,如test

將打包的檔案移動到test中,執行http-server ./,搭建服務

當我們點選按鈕的時候,就可以進行更新應用了,如下圖

[electron]終極奧義 五千字教程丟給你

點選ok後,靜候片刻,更新完後就開啟了最新版本的應用

[electron]終極奧義 五千字教程丟給你

以上就完成了electron的自動更新。

最後

本文所有程式碼請戳github瞭解更多

ocr相關demo借(chao)鑑(xi)了Jarttoelectron-ocr,下文有連結。

參考

electron-packager命令常用引數大全

Electron 打包Mac安裝包程式碼簽名問題解決方案

蘇南大叔的electron

electron-ocr

Electron 打包Mac安裝包程式碼簽名問題解決方案文中所說簽名均需要是買了Apple開發者賬號的。

相關文章