Electron装置
装置问题
npm或者yarn装置electron就算是配置了淘宝源还是会呈现超时。所以我的解决方案是装置cnpm,应用cnpm去装置。
全局装置cnpm
npm i cnpm -G
复制代码
新建我的项目
cnpm init // 一路Enter而后到最初一步输出yes
// 装置dev相干依赖
cnpm i electron -D //装置electron
cnpm i electron-builder -D // 用来打包客户端安装包 -- 须要下一步下一步装置来实现点击关上
cnpm i electron-packager -D // 用来打包客户端可执行文件 -- 间接点击打包后的可执行文件即可运行
// 装置生产相干依赖
cnpm i electron-log // 用于调试时的log输入,dev环境会间接在终端打印日志同时会在我的项目跟目录的logs文件夹生成log
cnpm i electron-updater //用户我的项目自动更新
cnpm i express // 因为应用的是history路由模式所以咱们应用node来启动前端我的项目
cnpm i http-proxy-middleware // 用于代理前端我的项目拜访服务器接口
复制代码
相干依赖的版本如下
生产
"electron-log": "^4.4.8",
"electron-updater": "^5.0.5",
"express": "^4.18.1",
"http-proxy-middleware": "^2.0.6"
复制代码
开发
"electron": "^19.0.6",
"electron-builder": "^23.1.0",
"electron-packager": "^15.5.1"
复制代码
我的项目架构详解
├── build // 用于寄存前端打包后的文件
├── desk // 用于寄存打包后的exe安装文件或者dmg
├── logs // 用于寄存我的项目调试log文件
├── main.js // electron的主过程文件
├── media // 我的项目的多媒体文件诸如.mp3 .mp4 .ico .icns文件
├── node_modules // 我的项目依赖
├── package.json // 配置文件
├── preload.js
├── renderer.js
└── server // 须要打包进我的项目的后端可执行文件
复制代码
对于preload.js 和 renders.js的详解
话说,在传统的electron程序中,大量的逻辑是写在renderer.js文件中的。然而,起初随着electron的版本倒退,逐步进去了一种呼声:就是要将node能力从renderer.js中分离出来。让renderer.js回归传统js的性能。这个时候,呈现的新概念就是preload.js。
本文的测试环境:electron@13.0.1,win10。本文探讨preload.js在browserWindow中的利用,当然,preload.js在webview中也有应用到。然而临时不在本文的探讨范畴内。本文次要命题是:preload.js的作用范畴,以及如何辨别以后作用的页面。
原文链接
我的项目启动
首先配置package.json文件的main字段为我的项目中的main.js
配置script字段增加如下
"start": "chcp 65001 && electron .", // chcp 65001是为了解决Windows平台在启动后许可的log中文乱码问题
"macpack": "electron-builder build --mac", // 用于打包dmg安装包
"winpack": "electron-builder build --win" // 用于打包exe安装包
复制代码
在electron启动前端我的项目
首先须要将打包后的前端代码放到我的项目build文件夹下,留神是放到build文件夹根目录而不是将诸如dist(vue打包后)或者build(react打包后)文件间接拷贝到我的项目的build文件夹。build文件夹下的文件目录如果是react就应该如下
├── asset-manifest.json
├── favicon.ico
├── files
├── index.html
├── manifest.json
├── robots.txt
└── static
复制代码
开始编写main.js
间接贴出代码如下
const { app, BrowserWindow, Menu, dialog } = require('electron');
const path = require('path');
const isDev = !app.isPackaged;
const cp = require('child_process');
const { createProxyMiddleware } = require('http-proxy-middleware');
const express = require('express');
const application = express();
const START_PORT = 50001;
const DOMAIN = 'http://xxx';
const enviroment = process.platform == 'darwin' ? 'mac' : 'win';
const log = require('electron-log');
// 获取我的项目资源目录留神辨别打包前和打包后的区别
const appPath = app.isPackaged
? path.dirname(app.getPath('exe')) // 打包后: app.getAppPath(); // 打包前
const { autoUpdater } = require('electron-updater');
if (isDev) {
// 判断如果是dev环境就将log存储在我的项目根目录的logs文件夹log.transports.file.resolvePath = () => path.join(__dirname, `logs/${new Date().toLocaleDateString()}.log`);
}
// 设置log日志的格局能够去electron-log官网文档查看更多格式化
log.transports.file.format = '[{y}-{m}-{d} {h}:{i}:{s}.{ms}] [{level}] {text}';
let localServer; // node服务的实例,这里定义是为了前面不便在敞开窗口的时候杀掉它
function createWindow() {
// 主过程开启一个尺寸为1920*1000的窗口const mainWindow = new BrowserWindow({ width: 1920, height: 1000, webPreferences: { preload: path.join(__dirname, 'preload.js'), },});// 生命一个meuconst menu = [ { label: '帮忙', submenu: [ { label: '控制台', click: () => { mainWindow.webContents.openDevTools({ mode: 'bottom' }); }, }, { label: '查看更新', click: () => { autoUpdater.checkForUpdates(); }, }, { label: '对于', click: () => { dialog.showMessageBoxSync({ title: '对于', message: `${app.getName()}V${app.getVersion()}`, type: 'info', icon: path.resolve( __dirname, 'media/images/logo.png' ), buttons: ['好的'], }); }, }, ], },];// 启动一个node服务也就是用node部署打包后的文件proxys().then((res) => { const m = Menu.buildFromTemplate(menu); // 设置顶部菜单 Menu.setApplicationMenu(m); // 窗口显示咱们部属的前端我的项目 mainWindow.loadURL(`http://127.0.0.1:${START_PORT}`); // 判断如果是dev环境将devTool关上 isDev && mainWindow.webContents.openDevTools();});// 启动后端服务startServer();
}
function checkUpdate() {
if (enviroment === 'win') { // 本地模仿更新的端口 autoUpdater.setFeedURL('http://127.0.0.1:9005/win32');} else { // mac系統更新}autoUpdater.checkForUpdates();//监听'error'事件autoUpdater.on('error', (err) => { logMsg(`autoUpdater谬误${err}`);});//监听'update-available'事件,发现有新版本时触发autoUpdater.on('update-available', () => { logMsg('发现更新-----------------------------');});autoUpdater.on('update-not-available', () => { dialog .showMessageBox({ type: 'info', title: '利用更新', message: '未发现新版本' })})//监听'update-downloaded'事件,新版本下载实现时触发autoUpdater.on('update-downloaded', () => { // 如果有更新提醒用户并后盾下载安装 dialog .showMessageBox({ type: 'info', title: '利用更新', message: '发现新版本,是否更新?', buttons: ['是', '否'], }) .then((buttonIndex) => { if (buttonIndex.response == 0) { //抉择是,则退出程序,装置新版本 autoUpdater.quitAndInstall(); app.quit(); } });});
}
function logMsg(msg) {
log.info(msg);
}
function startServer() {
// 启动后盾打包后的可执行文件logMsg('开始执行-----------------------------');let shellCode;if (enviroment === 'win') { logMsg(`程序安装目录: ${appPath}`); // serverPath = path.resolve(__dirname, 'server/python'); const serverPathSplit = appPath.split(':'); shellCode = `${serverPathSplit[0]}: && cd ${serverPathSplit[1]}${ isDev ? '' : '\\resources' }\\server\\python && ${enviroment === 'win' ? 'main.exe' : 'test'}`; logMsg(`行将执行脚本:${shellCode}`);}// 子过程运行后端可执行文件cp.exec(shellCode, (error, stdout, stderr) => { if (error) { logMsg(`脚本执行谬误: ${error}`); return; } logMsg('执行胜利'); logMsg(`stdout: ${stdout}`); log.error(`stderr: ${stderr}`);});logMsg('完结执行-----------------------------');
}
function proxys() {
return new Promise((resolve, reject) => { application.use( createProxyMiddleware('/api', { target: DOMAIN, changeOrigin: true, secure: false, }) ); application.use( createProxyMiddleware('/v1', { target: DOMAIN, changeOrigin: true, secure: false, }) ); application.use( createProxyMiddleware('/icons', { target: DOMAIN, changeOrigin: true, secure: false, }) ); application.use( createProxyMiddleware('/apks', { target: DOMAIN, changeOrigin: true, secure: false, }) ); application.use( createProxyMiddleware('/zip', { target: DOMAIN, changeOrigin: true, secure: false, }) ); application.use( createProxyMiddleware('/img_avatar', { target: DOMAIN, changeOrigin: true, secure: false, }) ); application.use( createProxyMiddleware('/screenshot', { target: DOMAIN, changeOrigin: true, secure: false, }) ); application.use( createProxyMiddleware('/data', { target: DOMAIN, changeOrigin: true, secure: false, }) ); application.use( createProxyMiddleware('/android', { target: DOMAIN, changeOrigin: true, secure: false, }) ); application.use( createProxyMiddleware('/ipa_icons', { target: DOMAIN, changeOrigin: true, secure: false, }) ); application.use( createProxyMiddleware('/ipas', { target: DOMAIN, changeOrigin: true, secure: false, }) ); application.use( createProxyMiddleware('/admin', { target: DOMAIN, changeOrigin: true, secure: false, }) ); application.use( createProxyMiddleware('/ws', { target: DOMAIN, changeOrigin: true, secure: false, }) ); application.use( createProxyMiddleware('/desktop', { target: 'http://127.0.0.1:29096', changeOrigin: true, secure: false, }) ); // 这一步是用户前端我的项目是history路由比方写的相干配置 application.use(express.static(path.resolve(__dirname, 'build'))); application.get('*', function (request, response) { response.sendFile(path.resolve(__dirname, 'build', 'index.html')); }); localServer = application.listen(START_PORT, () => { resolve(); });});
}
app.whenReady().then(() => {
createWindow();// 判断窗口ready之后检测更新checkUpdate();app.on('activate', function () { if (BrowserWindow.getAllWindows().length === 0) createWindow();});
});
app.on('window-all-closed', function () {
// 敞开窗口之后须要杀掉node启动的服务localServer.close();logMsg(`node服务已停止---------------------`);if (enviroment === 'win') { cp.exec(`taskkill /f /t /im main.exe`, (error, stdout, stderr) => { if (error) { logMsg(`杀死过程执行谬误: ${error}`); return; } logMsg(`stdout: ${stdout}`); log.error(`stderr: ${stderr}`); logMsg('后盾服务程序已被杀死---------------------'); });}if (process.platform !== 'darwin') app.quit();
});
复制代码
对于package.json的编写
因为应用的是electron-builder故能够去到该插件官网查看相干字段的文档。因为业务要求咱们只须要打包.exe所以以下是对于打包exe利用的相干配置。
以下我用到的字段我尽量正文写进去
"build": {
"appId": "9928c2b60725cde286468f0696df8b30","productName": "打包后的利用名称","icon": "./media/images/logo.png", // 打包后的利用logo"asar": true, // 是否应用asar加密源码"nsis": { "oneClick": false, // 是否一键装置 "allowElevation": true, "allowToChangeInstallationDirectory": true, // 是否能够自定义装置目录 "installerIcon": "./media/images/app.ico", // 装置时候的icon图标,留神图标格局是.con "uninstallerIcon": "./media/images/app.ico", // 卸载时候的icon图标 "installerHeaderIcon": "./media/images/app.ico", // 装置时候的头icon "createDesktopShortcut": true, // 是否创立桌面快捷方式 "createStartMenuShortcut": true, "shortcutName": "星源", "include": "script/installer.nsh" // 装置实现执行的nsh脚本},"directories": { "output": "desk/win" // 打包实现输入的目录如果该目录不存在会帮你在以后我的项目创立},"files": [ // 须要打包的文件 "main.js", "build", "preload.js", "media", "script", "package.json", "server"],"extraResources": [ // 不须要打包的额定资源,比我我这里就寄存了后端的可执行.exe文件 { "from": "server", "to": "server" }],"publish": { // 自动更新--这里前面会讲到 "provider": "generic", "url": "http://127.0.0.1:9005/"},"win": { "icon": "media/images/logo.png", "target": [ { "target": "nsis", "arch": [ "ia32" ] } ]}
}
复制代码
对于自动更新
如何编写自动更新的配置
先阐明应用到的依赖是electron-updater点击查看官网文档
上文中main.js文件中的如下代码块的作用就是用来自动更新的, 如下代码正文都写了进去
function checkUpdate() {
if (enviroment === 'win') { // 本地模仿更新的端口 autoUpdater.setFeedURL('http://127.0.0.1:9005/win32');} else { // mac系統更新}autoUpdater.checkForUpdates();//监听'error'事件autoUpdater.on('error', (err) => { logMsg(`autoUpdater谬误${err}`);});//监听'update-available'事件,发现有新版本时触发autoUpdater.on('update-available', () => { logMsg('发现更新-----------------------------');});autoUpdater.on('update-not-available', () => { dialog .showMessageBox({ type: 'info', title: '利用更新', message: '未发现新版本' })})//监听'update-downloaded'事件,新版本下载实现时触发autoUpdater.on('update-downloaded', () => { // 如果有更新提醒用户并后盾下载安装 dialog .showMessageBox({ type: 'info', title: '利用更新', message: '发现新版本,是否更新?', buttons: ['是', '否'], }) .then((buttonIndex) => { if (buttonIndex.response == 0) { //抉择是,则退出程序,装置新版本 autoUpdater.quitAndInstall(); app.quit(); } });});
}
复制代码
搭建本地公布平台
我本人搭建的一个本地更新服务应用node写的 仓库我的项目地址
该代码的应用如下
首先在我的项目根目录创立static文件夹,实践上该目录下内容如下
├── builder-debug.yml
├── builder-effective-config.yaml
├── latest.yml
├── win-ia32-unpacked
├── �\230\237�\220�\214�\235��\211\210\ Setup\ 1.0.0.exe
└── �\230\237�\220�\214�\235��\211\210\ Setup\ 1.0.0.exe.blockmap
复制代码
将打包后的exe以及一堆相干配置文件丢到该目录
启动我的项目npm run start
Electron我的项目的package.json配置如下
"publish": { // 自动更新--这里前面会讲到
"provider": "generic", "url": "http://127.0.0.1:9005/"},
复制代码
遇到的问题
前端我的项目dev环境启动能够失常看,然而打包之后始终报css/js门路加载问题。打包后的代码的门路指定
// 这一步是用户前端我的项目是history路由比方写的相干配置 application.use(express.static(path.resolve(__dirname, 'build'))); // 这里肯定要应用path来resole到以后打包目录的根目录要不然会呈现资源加载问题 application.get('*', function (request, response) { response.sendFile(path.resolve(__dirname, 'build', 'index.html')); });
复制代码
打包进去会呈现有些包找不到。解决方案是如果你确定你在打包后须要用到的包,在应用cnpm装置的时候不要加-D后缀,即便该包变成我的项目依赖而非开发环境依赖。因为打包会打包dependencies而不会打包devDependencies
打包的时候会呈现打包出错,记得认真查看终端谬误日志。我遇到的就是icon的格局不对。留神以下三个字段的文件格式是ico而非png
"installerIcon": "./media/images/app.ico",
"uninstallerIcon": "./media/images/app.ico",
"installerHeaderIcon": "./media/images/app.ico",
复制代码
启动后盾给到的可执行文件实现不联网本地数据入库。
"extraResources": [
{ "from": "server", "to": "server" }],
复制代码
如上我将后端给到的可执行文件放在我的项目的server目录,而后应用extraResources字段将打包后的文件放到了server目录。
在本地和打包后的门路会有很大出入。应用app.isPackaged判断是否是打包后。如下来获取该目录正确地址来执行后端打包后的可执行文件。
const appPath = app.isPackaged
? path.dirname(app.getPath('exe')) // 打包后: app.getAppPath(); // 打包前