前端web项目使用electron和electron-builder生成桌面应用
背景
需求推进技术探索,技术实现需求设计。刚开始本来是用 vue 做一个 web 项目,随着开发推进,产品要求做成桌面端应用。。。
想到了用 Electron 框架构建桌面应用程序,查了一下大多都是使用 Electron 一步步构建项目,把已有 web 项目打包成桌面应用文档比较少,不过也找到一篇不错的文档,下面记录一下踩坑过程。
参考文章
Electron 文档
Electron API 文档
利用electron和electron-builder把前端web项目生成桌面程序
实现过程
准备工作
- 安装
Node.js
,使用最新的LTS版本 - 打包后的 web 项目
- 开发之前最好把 Electron 官方文档看一下,官方文档还是最权威的
配置淘宝镜像
原镜像下载可能太慢或下载失败,可将 npm 和 electron 的镜像地址都切换为淘宝镜像,cmd 命令输入:
npm config edit
将下面的地址复制、粘贴、保存、关闭
registry=http://registry.npm.taobao.org/
electron_mirror=https://npm.taobao.org/mirrors/electron/
electron-builder-binaries_mirror=https://npm.taobao.org/mirrors/electron-builder-binaries/
Electron 项目结构
.
├─icons # icon 图标目录
├─node_modules
├─h5 # vue 资源
├─gulpfile.js # gulp 配置文件
├─main.js # 应用程序的入口文件
├─package.json
├─README.md
创建应用程序
新建目录并进入
# windows
mkdir my-electron-app && cd my-electron-app
复制的官方文档,运行居然报错,PowerShell 提示:标记 “&&” 不是此版本中的有效语句分隔符
将命令行语句中的 && 改为分号 ; 就好了
# windows
mkdir my-electron-app; cd my-electron-app
创建 package.json 文件
{"name": "my-electron-app","version": "1.0.0","author": "freeman","description": "electron app","private": false,"scripts": {"view": "cross-env ELECTRON_ENV=dev electron .","start": "cross-env ELECTRON_ENV=dev .\\node_modules\\.bin\\gulp watch:electron","build": "cross-env ELECTRON_ENV=dep electron-builder"},"main": "main.js","build": {"productName": "my electron app","appId": "com.wss.app","directories": {"output": "builder"},"nsis": {"oneClick": false,"allowElevation": true,"allowToChangeInstallationDirectory": true,"installerIcon": "./h5/favicon.ico","uninstallerIcon": "./h5/favicon.ico","installerHeaderIcon": "./h5/favicon.ico","createDesktopShortcut": true,"createStartMenuShortcut": true},"win": {"target": ["nsis","zip"]},"files": ["h5/**/*","main.js"]},"devDependencies": {"cross-env": "^7.0.3","electron": "^16.0.7","electron-builder": "^22.14.5","electron-connect": "^0.6.3","gulp": "^4.0.2"}
}
package.json 代码解析
- 启动命令设置
这里我安装了cross-env
,运行开发环境和打包时方便指定环境变量,同时引入了gulp
配置热更新,编辑代码自动刷新应用
热更新参考
"scripts": {"view": "cross-env ELECTRON_ENV=dev electron .","start": "cross-env ELECTRON_ENV=dev .\\node_modules\\.bin\\gulp watch:electron","build": "cross-env ELECTRON_ENV=dep electron-builder"
},
设置入口文件为
main.js
打包相关配置
"build": {"productName": "my electron app", // 项目名 这也是生成的exe文件的前缀名"appId": "com.wss.app", // 包名 "directories": { // 输出文件夹"output": "builder"}, "nsis": {"oneClick": false, // 是否一键安装"allowElevation": true, // 允许请求提升。 如果为false,则用户必须使用提升的权限重新启动安装程序。"allowToChangeInstallationDirectory": true, // 允许修改安装目录"installerIcon": "./h5/favicon.ico", // 安装图标"uninstallerIcon": "./h5/favicon.ico", // 卸载图标"installerHeaderIcon": "./h5/favicon.ico", // 安装时头部图标"createDesktopShortcut": true, // 创建桌面图标"createStartMenuShortcut": true // 创建开始菜单图标},"win": {"target": ["nsis","zip"]},"files": ["h5/**/*", // 要打包的目录"main.js"]
}
关于
Electron
项目出现白屏,控制台输出 Failed to load resource: net::ERR_FILE_NOT_FOUND 问题
npm run start
运行时出现:
执行npm run start
,窗口出现白屏,控制台输出找不到资源,可能是 vue 项目的publicPath
路径配置不对(vue 项目通过 npm run build 打包以后页面没有内容)npm run build
打包之后出现白屏:
files
里面要打包的目录可能指定错了,没有把我们要打包的目录指定对,electron-build 就不会将build文件夹打包进去 app.asar 文件里,这样安装完.exe
文件之后,控制台可能输出Not allowed to load local resource:
,桌面程序打开白屏。
参考:electron-builder 打包后 出现Not allowed to load local resource:
- 安装 npm 包
我是安装的最新版Node.js
,npm
依赖我都是安装在devDependencies
里,如果刚开始安装在dependencies
,打包的时候也会提示放到devDependencies
"devDependencies": {"cross-env": "^7.0.3","electron": "^16.0.7","electron-builder": "^22.14.5","electron-connect": "^0.6.3","gulp": "^4.0.2"
}
创建 gulpfile.js
因为想要实现热更新,安装了gulp
,还需要创建gulpfile.js
配置文件,这里只监听了main.js
const gulp = require('gulp');
const electron = require('electron-connect').server.create();gulp.task('watch:electron', function () {electron.start();gulp.watch(['./main.js'], electron.restart);
});
引入 web 项目
vue 项目路由模式使用hash
,将打包后的 vue 资源放到 electron 项目根目录
创建 main.js 文件
const {electron, app, BrowserWindow, Menu, screen
} = require('electron')
const path = require('path')// app.isPackaged 如果应用已打包,则返回的属性,此属性可用于区分开发和生产环境
const isDevelopment = !app.isPackaged;
if(isDevelopment) {try {const client = require('electron-connect').client;} catch (err) {}
}// 屏蔽安全告警在console控制台的显示
process.env['ELECTRON_DISABLE_SECURITY_WARNINGS'] = 'true'if (process.mas) app.setName('My Electron')let mainWindowfunction createWindow() {const displayWorkAreaSize = screen.getAllDisplays()[0].workAreaconst windowOptions = {x: displayWorkAreaSize.x, // 窗口相对于屏幕的左偏移位置.默认居中y: displayWorkAreaSize.y, // 窗口相对于屏幕的顶部偏移位置.默认居中width: displayWorkAreaSize.width,height: displayWorkAreaSize.height,minWidth: 1220,minHeight: 780,show: false,// movable: false, // 窗口是否可以拖动// maximizable: false, // 窗口是否可以最大化// titleBarStyle: 'hidden',frame: true, // 指定 false 来创建一个 Frameless Windowtitle: app.getName(),icon: path.join(__dirname, '/icons/icon.png'), // 托盘图标hasShadow: false, // 窗口是否有阴影autoHideMenuBar: true, // 自动隐藏菜单栏webPreferences: {devTools: process.env.ELECTRON_ENV == "dev",// 是否开启 DevToolswebSecurity: true, // //允许跨域plugins: true, // 是否开启插件支持experimentalFeatures: true, // 开启 Chromium 的 可测试 特性experimentalCanvasFeatures: true, // 开启 Chromium 的 canvas 可测试特性minimumFontSize: 10,},}if (process.platform === 'linux') {windowOptions.icon = path.join(__dirname, '/h5/favicon.ico')}mainWindow = new BrowserWindow(windowOptions)mainWindow.loadURL(path.join('file://', __dirname, '/h5/index.html'))// 设置窗口默认最大化mainWindow.maximize()// 设置用户是否可以调节窗口尺寸// mainWindow.setResizable(false)mainWindow.show()mainWindow.on('closed', function () {mainWindow = null})process.env.ELECTRON_ENV == "dev" && client.create(mainWindow);
}app.on('ready', function () {const menu = Menu.buildFromTemplate(template)Menu.setApplicationMenu(menu) // 设置菜单部分createWindow()
})app.on('window-all-closed', function () {if (process.platform !== 'darwin') app.quit()
})app.on('activate', function () {if (mainWindow === null) createWindow()
})app.on('browser-window-created', function () {let reopenMenuItem = findReopenMenuItem()if (reopenMenuItem) reopenMenuItem.enabled = false
})app.on('window-all-closed', function () {let reopenMenuItem = findReopenMenuItem()if (reopenMenuItem) reopenMenuItem.enabled = trueapp.quit()
})/*** 注册键盘快捷键*/
let template = [{label: '操作',submenu: [{label: '回到系统入口',accelerator: 'CmdOrCtrl+Q',click: function (item, focusedWindow) {if (focusedWindow.id === 1) {BrowserWindow.getAllWindows().forEach(function (win) {if (win.id > 1) {win.close()}})}focusedWindow.loadURL(path.join('file://', __dirname, '/h5/index.html'))}}, {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 windowsif (focusedWindow.id === 1) {BrowserWindow.getAllWindows().forEach(function (win) {if (win.id > 1) {win.close()}})}focusedWindow.reload()}}}]},{label: '窗口',role: 'window',submenu: [{label: '切换全屏',accelerator: 'F11',role: 'togglefullscreen',// click: function (item, focusedWindow) { // 自定义 click 和使用 role 效果是一样的// if (focusedWindow) {// focusedWindow.setFullScreen(!focusedWindow.isFullScreen());// }// }}, {label: '最小化',accelerator: 'CmdOrCtrl+M',role: 'minimize'}, {label: '关闭',accelerator: 'CmdOrCtrl+W',role: 'close'}]},{label: '帮助',role: 'help',submenu: [{label: '意见反馈',click: function () {electron.shell.openExternal('https://www.electronjs.org/zh/docs/latest/api/app')}}]}
]function findReopenMenuItem() {const menu = Menu.getApplicationMenu()if (!menu) returnlet reopenMenuItemmenu.items.forEach(function (item) {if (item.submenu) {item.submenu.items.forEach(function (item) {if (item.key === 'reopenMenuItem') {reopenMenuItem = item}})}})return reopenMenuItem
}// 设置是否显示 DevTools
if(process.env.ELECTRON_ENV == "dev") {template[1].submenu.push({label: '切换开发者工具',role: 'toggleDevTools'})
}// 针对Mac端的一些配置
if (process.platform === 'darwin') {const name = electron.app.getName()template.unshift({label: name,submenu: [{label: '退出',accelerator: 'Command+Q',click: function () {app.quit()}}]})// Window menu.template[3].submenu.push({type: 'separator'}, {label: 'Bring All to Front',role: 'front'})// addUpdateMenuItems(template[0].submenu, 1)
}// 针对Windows端的一些配置
if (process.platform === 'win32') {const helpMenu = template[template.length - 1].submenu// addUpdateMenuItems(helpMenu, 0)
}
main.js 代码解析
main.js
配置项可对照 Electron API 文档设置。本项目是把已有的 vue 项目打包,不是完全开发 Electron 应用,窗口菜单选项只能配置,不方便完全自定义。
- 运行时启动热更新
// main.js
process.env.ELECTRON_ENV == "dev" && client.create(mainWindow);
- 配置程序托盘图标
不配置图标将使用系统默认的
function createWindow() {const windowOptions = {// ...icon: path.join(__dirname, '/icons/icon.png'), // 托盘图标// ...}
}
配置图标这里需要注意,根据官方文档 nativeImage说明,eletron 可以设置大小为 32x32、64x64 。。。等一些尺寸的图标,但是同样指定一个 6464 的
png
,通过npm run build
打包发现报错 (必需为256256),同时还发现只添加一张照片也不行,会报错,于是指定了三张不同尺寸的 icon 图片。
build 报错截图:
icons 目录截图:
- 指定
index.html
位置
mainWindow.loadURL(path.join('file://', __dirname, '/h5/index.html'))
启动和打包应用程序
预览
npm run start
打包,打包输入目录在package.json
中配置
npm run build
记录
打包之后,报错 A JavaScript error occurred in the main process
查了好久没有发现相关的问题,那就一定是项目中的配置存在问题,
通过图片发现是打包之后electron-connect
报错,报错原因是因为electron-connect
在开发环境安装的包,打包之后是不会安装的,生产引入未安装的包会报这种错,所以引入时要先判断下环境。
// main.js// app.isPackaged 如果应用已打包,则返回的属性,此属性可用于区分开发和生产环境
const isDevelopment = !app.isPackaged;
if(isDevelopment) {try {const client = require('electron-connect').client;} catch (err) {}
}
前端web项目使用electron和electron-builder生成桌面应用相关推荐
- 在HBuilder创建前端web项目时,图片加载不出来,怎么解决?
在HBuilder创建前端web项目时,图片加载不出来,如下: 解决方法: 将图片文件路径减少,可正常显示,如图:
- 一文搞懂使用Nginx服务器部署前端Web项目
1 Nginx简介与安装 概要 Nginx 简介 Nginx 架构说明 Nginx 基础配置与使用 1.1 Nginx 简介 Nginx是一个高性能WEB服务器,除它之外Apache.Tomcat.J ...
- 08 Tomcat+Java Web项目的创建和War的生成
1.web服务器软件: 服务器:安装了服务器软件的计算机 服务器软件:接收用户的请求,处理请求,做出响应 * web服务器软件:接收用户的请求,处理请求,做出响应. 在web服务器软件中,可以部署we ...
- 使用Electron将Web项目打包成windows桌面应用
1.从官网下载node.js并安装. 2.有不少人反映使用此命令安装Electron不成功,可能是国外镜像的问题. npm install --save-dev electron-prebuilt 3 ...
- 【Electron】使用Electron将web项目打包成桌面应用程序
目录 一.所需环境&打包前准备 1.安装node.js 2.安装electron 3.web项目 二.打包过程 1.打包配置 2. 安装打包器 3.执行打包命令: Electron是由GitH ...
- electron打包web项目之stackedit实战——反面教材
很抱歉,误导大家了.electron只支持客户端web项目,package.json里的scripts字段太误导人了.让我傻傻地以为真的可以在里面写一个 node server.js呢.辛亏我照着自己 ...
- 前端趋势榜:上周最有意思、又实用的 10 大 Web 项目 - 210821
大家好,我是你们的 猫哥,那个不喜欢吃鱼.又不喜欢喵 的超级猫 ~ 平时如何发现好的开源项目,可以看看这篇文章:GitHub 上能挖矿的神仙技巧 - 如何发现优秀开源项目 1. shiki Shiki ...
- 前端趋势榜:上周最有意思、又实用的 10 大 Web 项目 - 210924
大家好,我是你们的 猫哥,那个不喜欢吃鱼.又不喜欢喵 的超级猫 ~ 平时如何发现好的开源项目,可以看看这篇文章:GitHub 上能挖矿的神仙技巧 - 如何发现优秀开源项目 1. Thief Thief ...
- 《从零构建前后分离的web项目》准备 - 前端了解过关了吗?
前端基础架构和硬核介绍 技术栈的选择 首先我们构建前端架构需要对前端生态圈有一切了解,并且最好带有一定的技术前瞻性,好的技术架构可能日后会方便的扩展,减少重构的次数,即使重构也不需要大动干戈,我通常选 ...
最新文章
- 13接口登记案例 把对象赋值给接口
- 1040 Longest Symmetric String (25 分)【难度: 一般 / 知识点: 最长回文子串】
- 在ABAP中将负号提前的方法
- 娃哈哈困境:做好了生意,没做好品牌
- 过拟合与模型调优(part2)--重抽样技术
- ElementUI+VUE 日期控件禁用用法
- python到底能干啥-Python到底可以干什么?主要应用领域
- adb ps shell 查看进程,如何使用ADB命令检测正在运行的应用程序
- 应用RSACryptoServiceProvider类轻松实现RSA算法
- 冲刺高阶ADAS市场,毫米波雷达多元侦测整合成主流趋势
- 阿里云OSS 使用内网地址上传文件
- als算法参数_协调过滤算法之ALS
- 尚硅谷JAVA基础笔记吐血整理
- shell 004 __小斌文档 |数组+函数
- python 多线程 代理 爬取 豆果美食app
- DSPE-PEG1-COOH可与伯胺基反应的反应性磷脂PEG共轭物之一
- 无招胜有招——面向对象设计模式随笔,
- AI经典书单:入门人工智能该读哪些书?
- Ubuntu Linux 查看、编辑、比较二进制文件
- 分享 10 个常见的 CSS 页面布局代码片段