Electron 打包爬坑指南

沒人懂還可以問神發表於2018-07-25
最近公司做的專案需要用到Electron進行打包,我就把我遇到的問題給記錄一下,也希望能夠幫到別人節省時間。 

下載檔案 

原先專案的下載檔案都是通過window.open的方式去進行下載,在electron裡預設開啟新視窗,不顯示任何東西,通過a標籤屬性設為download也是無效的。網上一查資料少的可憐,有一篇文章一直被複制來複制去的,配置的還有些麻煩。我這裡就通過簡單的配置就可以進行下載了。 事件執行下載的程式碼 在渲染程式裡寫下如下程式碼,類似於vue中的emit觸發下載事件去告知主程式,url是下載地址 

if (window.require) {
var ipcRenderer = window.require('electron').ipcRenderer 
ipcRenderer.send('download', url) 
} 複製程式碼

必須在執行中去require ipcRenderer模組,一開始引入的話會報錯。  

在main.js也就是主程式寫下如下程式碼 

 

const {ipcMain} = require('electron')
...
ipcMain.on('download', (event, args) => { //下面這句會觸發will-download事件 win.webContents.downloadURL(args)}) 複製程式碼

will-download應該是一個鉤子類似vue裡的before mouted函式(我這裡忽略了)。  

iframe的替換 

 在electron裡面不能用iframe標籤,會白屏卡死。這裡要用webview去替換 

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

複製程式碼

文件地址

自動更新 

這裡有個教程寫的很好。 轉自 簡書使用者elle0903 文章地址

從零開始

進入你的工作目錄,比如 d/workspace

# 目錄 d/workspace
mkdir electron-demo # 新建資料夾 electron-demo
cd electron-demo # 進入資料夾 electron-demo
npm init # 初始化 npm,一路回車即可
npm i electron --save-dev # 安裝 electron 依賴
touch main.js # 新建檔案 main.js
touch index.html # 新建檔案 index.html
複製程式碼

現在你的檔案結構如下:

|- electron-demo
  |- main.js # 空檔案
  |- index.html # 空檔案
  |- package.json
  |- package-lock.json # npm 自動生成的檔案
  |- node_modules
複製程式碼

確保 package.json 的name,version,description這三個欄位都不為空,main 欄位的取值為 main.js 。

{
  "name": "electron-demo",
  "version": "0.0.1",
  "description": "electron-demo",
  "main": "main.js"
}複製程式碼

主程式和渲染程式

Electron 應用分為主程式和渲染程式。渲染程式是直接與使用者產生互動的程式,也就是我們看到的一個個視窗頁面,而主程式主要負責控制應用的生命週期,負責各個渲染程式的通訊等。

我們的主程式程式碼寫在 main.js 中,所以首先在你心愛的程式碼編輯中開啟 main.js,輸入如下程式碼:

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

app.on('ready', () => {
    let win = new BrowserWindow({
        width: 800,
        height: 600,
        webPreferences: {
            devTools: true
        }
    });

    win.loadURL(
        url.format({
            pathname: path.join(__dirname, 'index.html'),
            protocol: 'file:',
            slashes: true
        })
    );
});

app.on('window-all-closed', () => app.quit());
複製程式碼

再開啟 index.html,輸入如下程式碼:

<!DOCTYPE html>
<html>

<head>
    <meta charset="UTF-8">
    <title>Hello World!</title>
</head>

<body>
    <h1>Hello World!</h1>
</body>

</html>
複製程式碼

之後執行

# 目錄 d/workspace/electron-demo
node_modules/.bin/electron .
複製程式碼

不出意外的話你會看到一個彈出框,像這樣:


Electron 打包爬坑指南
image.png

我們也可以把 node_modules/.bin/electron . 儲存成 npm 指令碼,方便以後呼叫。開啟 package.json,新增如下內容:

  "scripts": {
    "start": "electron ."
  }
複製程式碼

以後只需要呼叫 npm start 即可。

到這裡,我們已經有了一個最簡單的 Electron 應用,如果你對繼續開發應用本身更感興趣的話,請移步 Electron 官方文件,因為接下來我們會專注在怎麼讓這個應用實現自動更新。

自動更新

安裝依賴

自動更新功能的實現依賴 electron-builder 和 electron-updater,所以我們需要先安裝這兩個依賴。

# 目錄 d/workspace/electron-demo
npm i electron-builder --save-dev # 必須安裝為開發依賴,否則打包會出錯
npm i electron-updater --save # 必須安裝為執行依賴,否則執行會出錯
複製程式碼

配置 package.json

為了配合打包 package.json 需要新增欄位 build:

 "build": {
    "appId": "com.xxx.app",
    "publish": [
      {
        "provider": "generic",
        "url": "http://127.0.0.1:8080"
      }
    ]
  },
複製程式碼

同樣為了執行方便,我們需要新增一個 npm 指令碼,開啟 package.json,新增如下內容:

  "scripts": {
    "start": "electron .",
    "build": "electron-builder -w"
  }
複製程式碼

以後只需要呼叫 npm run build 即可。

主程式和渲染程式

開啟main.js,編輯後內容如下:

const path = require('path');
const url = require('url');
const {
    app,
    BrowserWindow,
    ipcMain 
} = require('electron');
const { autoUpdater } = require('electron-updater');
const feedUrl = `http://127.0.0.1:8080/${process.platform}`;

let webContents;

let createWindow = () => {
    let win = new BrowserWindow({
        width: 800,
        height: 600,
        webPreferences: {
            devTools: true
        }
    });

    webContents = win.webContents;

    win.loadURL(
        url.format({
            pathname: path.join(__dirname, 'src/index.html'),
            protocol: 'file:',
            slashes: true
        })
    );

    webContents.openDevTools();
};

let sendUpdateMessage = (message, data) => {
    webContents.send('message', { message, data });
};

let checkForUpdates = () => {
    autoUpdater.setFeedURL(feedUrl);

    autoUpdater.on('error', function (message) {
        sendUpdateMessage('error', message)
    });
    autoUpdater.on('checking-for-update', function (message) {
        sendUpdateMessage('checking-for-update', message)
    });
    autoUpdater.on('update-available', function (message) {
        sendUpdateMessage('update-available', message)
    });
    autoUpdater.on('update-not-available', function (message) {
        sendUpdateMessage('update-not-available', 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) => {
            //some code here to handle event
            autoUpdater.quitAndInstall();
        })
        sendUpdateMessage('isUpdateNow');
    });

    //執行自動更新檢查
    autoUpdater.checkForUpdates();
};

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

    setTimeout(checkForUpdates, 1000);
});

app.on('window-all-closed', () => app.quit());
複製程式碼

index.html:

<!DOCTYPE html>
<html>

<head>
    <meta charset="UTF-8">
    <title>Hello World!</title>
</head>

<body>
    <h1>Hello World!</h1>
    <script>
        const { ipcRenderer } = require('electron');

        ipcRenderer.on('message', (event, { message, data }) => {
            console.log(message, data);
            switch (message) {
                case 'isUpdateNow':
                    if (confirm('現在更新?')) {
                        ipcRenderer.send('updateNow');
                    }
                    break;
                default:
                    document.querySelector('h1').innerHTML = message;
                    break;
            }
        });
    </script>
</body>

</html>
複製程式碼

打包

npm run build
複製程式碼

第一次執行會比較慢,執行結束後會在當前目錄下新增一個 dist 資料夾,dist 的目錄結構如下:

|- dist
  |- win-unpacked
  |- electron-autoupdate-scaffold Setup.exe
  |- electron-autoupdate-scaffold Setup.exe.blockmap
  |- electron-builder-effective-config.yaml
  |- latest.yml
複製程式碼

win-unpacked 下是可執行檔案,但是先彆著急執行,我們還缺一個後臺。

自動更新後臺

聰明的你一定注意到,前面程式碼中我們有一個:

const feedUrl = `http://127.0.0.1:8080/${process.platform}`;
複製程式碼

所以我們現在要做的就是一個可以接受這個請求的服務。

回到你的工作目錄(d/workspace

# 目錄 d/workspace
mkdir electron-server
cd electron-server
npm init
npm i koa --save
npm i koa-static --save
touch server.js
複製程式碼

開啟 server.js,輸入如下內容:

// server.js
let Koa = require('koa');
let app = new Koa();
let path = require('path');

app.use(require('koa-static')(path.resolve(__dirname + '/packages')));

let server = app.listen(8080, () => {
    let { address, port } = server.address();

    console.log("應用例項,訪問地址為 http://%s:%s", address, port);
});
複製程式碼

將之前打包出來的 dist 目錄下的 4 個檔案(除了 win-unpacked)拷貝到這邊的 packages/win32 下(新建目錄 packages/win32),之後

# 目錄 d/workspace/electron-server
npm start
複製程式碼

到此為止,我們的自動更新服務就搭建完成了,現在來一波測試吧。

測試

  1. 進入 electron-demo/dist/win-unpacked 找到可執行檔案,雙擊執行,看到開啟視窗的控制檯中依次輸出:

    checking-for-update
    update-not-available
    複製程式碼
  2. 進入 electron-demo,開啟 package.json,把版本號改為0.0.2,重新打包後拷貝打包檔案到自動更新後臺目錄(d/workspace/electron-server/packages/win32)。

  3. 進入 electron-demo,開啟 package.json,把版本號改為0.0.1,重新打包後再次進入 dist/win-unpacked 目錄,執行 exe,看到開啟視窗的控制檯中依次輸出:

    checking-for-update
    update-available
    複製程式碼

    並且出現彈窗提示「現在更新?」



相關文章