不少人知道Vscode這一強大的編輯應用正是electron框架寫的,出於興趣和其方便性,我就把公司的項目進行了一個優化,使用electron 來把項目搞成桌面應用,廢話很少說,步驟以下:
html
一: 安裝Electronnode
electron 有點大,安裝起來有點久,多點耐心哦~react
npm install --save-dev electron複製代碼
二: 項目的啓動git
開發環境下,在根目錄下添加main.js,並附上github
const { app, BrowserWindow } = require('electron')
// 保持對window對象的全局引用,若是不這麼作的話,當JavaScript對象被
// 垃圾回收的時候,window對象將會自動的關閉
let win
function createWindow () {
// 建立瀏覽器窗口。
win = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
nodeIntegration: true
}
})
// 加載index.html文件 這裏不必定是index.html, 看你目錄結構,我react 項目就是'./public/index.html'
win.loadFile('index.html')
// 打開開發者工具
win.webContents.openDevTools()
// 當 window 被關閉,這個事件會被觸發。
win.on('closed', () => {
// 取消引用 window 對象,若是你的應用支持多窗口的話,
// 一般會把多個 window 對象存放在一個數組裏面,
// 與此同時,你應該刪除相應的元素。
win = null
})
}
// Electron 會在初始化後並準備
// 建立瀏覽器窗口時,調用這個函數。
// 部分 API 在 ready 事件觸發後才能使用。
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()
}
})
// 在這個文件中,你能夠續寫應用剩下主進程代碼。
// 也能夠拆分紅幾個文件,而後用 require 導入。複製代碼
而後在package.json 上添加命令web
"scripts": {
"start": "electron ."
}複製代碼
此時運行npm start / yarn start electron 應用就啓起來了,是否是很簡單;npm
三, 項目的打包json
electron 應用打包有兩種方式:electron-packager 和 electron-builder;segmentfault
一開始我是使用electron-packager,可是後面想作自動更新的功能,就轉electron-builder進行打包數組
"package": "electron-packager ./build/ electron-test --all --out ~/ --electron-version 1.0.0",複製代碼
後面使用electron-builder 進行打包,並結合electron-updater自動化更新;
3.1 首先就是安裝啦:
yarn add electron-builder --dev
yarn add electron-updater
npm install electron-updater --save
複製代碼
3.2 配置package.json 文件
注:nsis配置不會影響自動更新功能,可是能夠優化用戶體驗,好比是否容許用戶自定義安裝位置、是否添加桌面快捷方式、安裝完成是否當即啓動、配置安裝圖標等
"build": { "appId": "****.app", "copyright": "back-manage", "productName": "back-manage", "nsis": { "oneClick": true, "perMachine": true, "allowElevation": true, "allowToChangeInstallationDirectory": true, "createDesktopShortcut": true, "runAfterFinish": true, "installerIcon": "public/favicon1.png", "uninstallerIcon": "public/favicon1.png" }, "publish": [ { "provider": "generic", "url": "https://***" } ], "dmg": { "contents": [ { "x": 410, "y": 150, "type": "link", "path": "/Applications" }, { "x": 130, "y": 150, "type": "file" } ] }, "mac": { "icon": "public/favicon1.png", "artifactName": "${productName}_setup_${version}.${ext}", "target": [ "dmg", "zip" ] }, "win": { "icon": "public/favicon1.png", "target": [ "nsis", "zip" ] } },複製代碼
3.2 修改主進程main.js 文件(引入 electron-updater 文件,添加自動更新檢測和事件監聽)
注意,我這裏使用的是react,打包這裏使用的主進程文件再也不是main.js, 而是public下面的electron.js
warn: public/electron.js not found. Please see https://medium.com/@kitze/%EF%B8%8F-from-react-to-an-electron-app-ready-for-production-a0468ecb1da3複製代碼
我修改後的eletron.js
// 引入electron並建立一個Browserwindowconst { app, BrowserWindow,Menu,ipcMain } = require('electron');const { autoUpdater } = require('electron-updater');const path = require('path');const url = require('url');const uploadUrl = 'https://***/'; (這個地址是自動更新會去對比這個服務器上的latest.yml,檢測
最新版本)// 保持window對象的全局引用,避免JavaScript對象被垃圾回收時,窗口被自動關閉.let mainWindow;// 檢測更新,在你想要檢查更新的時候執行,renderer事件觸發後的操做自行編寫function updateHandle() { const message = { error: '檢查更新出錯', checking: '正在檢查更新……', updateAva: '檢測到新版本,正在下載……', updateNotAva: '如今使用的就是最新版本,不用更新' }; const os = require('os'); autoUpdater.setFeedURL(uploadUrl); autoUpdater.on('error', error => { sendUpdateMessage(message.error); // sendUpdateMessage(error); }); autoUpdater.on('checking-for-update', () => { sendUpdateMessage(message.checking); }); autoUpdater.on('update-available', info => { console.log(info) mainWindow.webContents.send('updateAvailable', '<h3>檢測到新版本' + info.version + ',須要升級?</h3>' + info.releaseNotes); // sendUpdateMessage(message.updateAva); }); autoUpdater.on('update-not-available', info => { sendUpdateMessage(message.updateNotAva); }); // 更新下載進度事件 autoUpdater.on('download-progress', progressObj => { console.log(progressObj) const winId = BrowserWindow.getFocusedWindow().id; let win = BrowserWindow.fromId(winId); win.webContents.send('downloadProgress', progressObj); }); autoUpdater.on('update-downloaded', ( event, releaseNotes, releaseName, releaseDate, updateUrl, quitAndUpdate ) => { console.log(event, releaseNotes, releaseName, releaseDate, updateUrl, quitAndUpdate) console.log('update-downloaded'); ipcMain.on('isUpdateNow', (e, arg) => { console.log(arguments); console.log('開始更新'); // some code here to handle event autoUpdater.quitAndInstall(); }); mainWindow.webContents.send('isUpdateNow'); }); ipcMain.on("isDownload", () => { autoUpdater.downloadUpdate();}) ipcMain.on('checkForUpdate', () => { // 執行自動更新檢查 autoUpdater.checkForUpdates(); });}// 經過main進程發送事件給renderer進程,提示更新信息function sendUpdateMessage(text) { mainWindow.webContents.send('message', text);}function createWindow() { // 建立瀏覽器窗口,寬高自定義具體大小你開心就好 mainWindow = new BrowserWindow({ width: 800, height: 600, webPreferences: {webSecurity: false, allowDisplayingInsecureContent: true, allowRunningInsecureContent: true, nativeWindowOpen: true, webSecurity: false, nodeIntegration: true, // 是否完整的支持 node. 默認值爲true nodeIntegrationInWorker: true, // 是否在Web工做器中啓用了Node集成 preload: path.join(__dirname, './renderer.js') }}); /* * 加載應用----- electron-quick-start中默認的加載入口 mainWindow.loadURL(url.format({ pathname: path.join(__dirname, 'index.html'), protocol: 'file:', slashes: true })) */ // 加載應用----適用於 react 項目 // mainWindow.loadURL('http://localhost:3000/'); // 加載應用----react 打包 mainWindow.loadURL( url.format({ pathname: path.join(__dirname, './index.html'), protocol: 'file:', slashes: true }) // 'http://192.168.0.11:8082' ); // 打開開發者工具,默認不打開 // mainWindow.webContents.openDevTools() // 關閉window時觸發下列事件. mainWindow.on('closed', () => { mainWindow = null; }); ipcMain.on('openDev', () => { mainWindow.openDevTools(); }); updateHandle()}// 當 Electron 完成初始化並準備建立瀏覽器窗口時調用此方法app.on('ready', createWindow);// 全部窗口關閉時退出應用.app.on('window-all-closed', () => { // macOS中除非用戶按下 `Cmd + Q` 顯式退出,不然應用與菜單欄始終處於活動狀態. if (process.platform !== 'darwin') { app.quit(); }});app.on('activate', () => { // macOS中點擊Dock圖標時沒有已打開的其他應用窗口時,則一般在應用中重建一個窗口 if (mainWindow === null) { createWindow(); }});// 你能夠在這個腳本中續寫或者使用require引入獨立的js文件.複製代碼
3.3 在項目啓動app.js添加相應的檢測更新的代碼
由於在app.js 中是獲取不到electron 的,咱們須要在index.html中注入electron
window.electron = require('electron');複製代碼
而後在app.js
const ipcRenderer = window.electron && window.electron.ipcRenderer;
而後在componentDidMount 或者useEffect 函數中,添加: const self = this; if (ipcRenderer) { ipcRenderer.send('checkForUpdate'); ipcRenderer.on('message', (event, message) => { console.log(message); }); // 注意:「downloadProgress」事件可能存在沒法觸發的問題,只須要限制一下下載網速就行了 ipcRenderer.on('downloadProgress', (event, progressObj) => { console.log('下載', progressObj); this.downloadPercent = progressObj.percent || 0; }); ipcRenderer.on('isUpdateNow', () => { console.log('是否如今更新'); ipcRenderer.send('isUpdateNow'); }); // 檢測到新版本 ipcRenderer.on('updateAvailable', (event, message) => { console.log(event, message); self.$notification.open({ message: 'Notification', description: '檢測到新版本,正在更新中……', onClick: () => { console.log('Notification Clicked!'); } }); ipcRenderer.send('isUpdateNow'); }); }
在componentWillUnmount 鉤子函數中移除事件在componentWillUnmount 鉤子函數中移除事件 componentWillUnmount() { if (ipcRenderer) { ipcRenderer.removeAll([ 'message', 'downloadProgress', 'isUpdateNow', 'updateAvailable' ]); } }複製代碼
萬事具有,只差一個執行命令的趕腳了有木有,走你……
可是,window 系統的包是打包好了,可是macOs 的出現了簽名問題,若是沒有自動更新的問題,用packager 打包出來的是不須要簽名的,可是要實現自動更新,簽名是必須的,
cannot find valid "Developer ID Application" identity or custom non-Apple code signing certificate複製代碼
首先說一下,要處理這個問題,首先你要有個蘋果開發者帳號,我的或者公司的都行,我當時花了99美圓買了一個我的開發者帳號,須要裏面的cer 證書來生成p12文件
具體能夠參考連接: segmentfault.com/a/119000001…
好了,通過這一系列操做事後,你已經很夠打包出一個可以自動更新的桌面應用,是否是頗有成就感?
可是,問題,又來了,打包出來的應用沒法進行復制黏貼,咋整?
解決:
1. 安裝 electron-localshortcut
yarn add electron-localshortcut複製代碼
2. 在主進程文件createWindow 函數添加以下代碼:
if (process.platform === 'darwin') { Menu.setApplicationMenu(Menu.buildFromTemplate([])); const contents = mainWindow.webContents; const localShortcut = require('electron-localshortcut'); localShortcut.register(mainWindow, 'CommandOrControl+A', () => { contents.selectAll(); }); localShortcut.register(mainWindow, 'CommandOrControl+C', () => { contents.copy(); }); localShortcut.register(mainWindow, 'CommandOrControl+V', () => { contents.paste(); }); }複製代碼
最後附上相應的git地址
refer git address: https://github.com/catherine201/electron-example.git
多謝閱讀~~