不少人知道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
多谢阅读~~