简介
Electron用于通过Javascript、HTML、CSS来编写运行于Windows、macOS、Linux跨平台的桌面应用。
- 迅雷和VSCode就是electron写的
- 我的electron项目演示
简单的Electron应用
npm init -y
npm i --save-dev electron
npm start
其中,package.json
中的main
字段要指向主进程的js
打包
通过electron-packager打包
打包为带有源码的可执行文件(不加密)
npx electron-packager <项目所在路径> <打包后的项目名称> <配置信息>
如下,将当前目录下的内容打包到dist目录下
npx electron-packager ./ myapp --out=dist
通过electron-builder打包(推荐)
可以打包为一个编译后的安装包
- 可以结合
electron-updater
进行自动更新 - 无法编译一些模块,如
ffi-napi
- 可以在package.json中通过
build
属性进行配置
"build": {
"appId": "TK.Troncell",
"productName": "Troncell",
"asar":false,//是否加密代码 默认为true
"directories": {
"output": "build_file"
},
"files":[//将哪些文件打包,默认打包所有
"app_normal/*"
],
"electronDownload": {
"mirror": "https://npm.taobao.org/mirrors/electron/"
},
"nsis": {
"oneClick": false,
"perMachine": true,
"allowToChangeInstallationDirectory": true,
"runAfterFinish": true
},
"publish": [
{
"provider": "generic",
"url": "http://192.168.2.154:8080/"
}
]
}
- --dir 打包为编辑后的直接执行文件(非安装包)
- --mac、--windows、--linux 指定打包的平台类型,默认为当前平台
- -mwl 编辑所有平台打包类型
asar加密与解密
asar仅仅是对resource/app目录的封装,且加密效果一般,只能防外行。
- 安装asar
npm install asar -g
asar --help
- 加密
asar p <被打包的目录> app.asar
asar pack <被打包的目录> app.asar
- 解密
asar e app.asar <解压后的目录>
asar extract app.asar <解压后的目录>
electron-forge
Electron Forge 是官方推荐的Electron构建工具,整合了应用的创建、打包(electron-packager、 @electron/osx-sign、electron-winstaller)、分发。
- 注意:只能打包成当前系统对应的包(比如需要生成 macOS M1 的可执行文件,就只能去 macOS M1 中生成)。此外生成的安装包安装时无法自定义路径,并不方便
操作指令
- 安装
npm install --save-dev @electron-forge/cli
- 创建新项目
electron-forge init 新项目名
- 应用到已有Electron项目(在package.json中添加一些指令和包)
npx electron-forge import
- npm start:运行
- npm run package:生成可执行包到 /out 下
- npm run make:自动执行 npm run package(可配置
--skip-package
避免重复执行) 并打包为安装包到 /out/make - npm run publish:打包并发布到配置文件中指定的发包平台
在线升级
- electron-forge + electron自带autoUpdater + GitHub或自己部署服务器(electron-release-server)
- electron-builder + electron-updater的autoUpdater + 自己部署服务器
主进程和渲染器进程
- 主进程就是main.js,一个普通的Node.js脚本
- 每个页面就是一个渲染进程
主进程与渲染进程通讯
-
webContents.send()
主进程主动发送消息 -
webContents.executeJavascript()
主进程直接控制渲染器进程 - 在主进程中通过
ipcMain
通讯-
ipcMain.on()
接受消息 -
event.returnValue
回复同步消息 -
event.reply()
和event.sender.send()
回复异步消息,其中event.reply()
可以发给iframe而event.sender.send()
总是发给页面本身
-
const { ipcMain } = require('electron')
ipcMain.on('asynchronous-message', (event, arg) => {
console.log(arg) // prints "ping"
event.reply('asynchronous-reply', 'pong')
})
ipcMain.on('synchronous-message', (event, arg) => {
console.log(arg) // prints "ping"
event.returnValue = 'pong'
})
- 在渲染器进程中通过
ipcRenderer
通讯-
ipcRenderer.on()
接受消息 -
ipcRenderer.sendSync()
发送同步消息 -
ipcRenderer.send()
发送异步消息 -
ipcRenderer.sendTo()
发送到指定webContent
-
const { ipcRenderer } = require('electron')
console.log(ipcRenderer.sendSync('synchronous-message', 'ping')) // prints "pong"
ipcRenderer.on('asynchronous-reply', (event, arg) => {
console.log(arg) // prints "pong"
})
ipcRenderer.send('asynchronous-message', 'ping')
- 渲染器进程使用
remote
模块直接调用主进程(不推荐)
BrowserWindow的enableRemoteModule
需要为true
const { BrowserWindow } = require('electron').remote
const win = new BrowserWindow({ width: 800, height: 600 })
win.loadURL('https://github.com')
API
- Electron API
是根据流程类型分配的。这意味着某些模块可以在主进程或渲染进程中使用,有些模块两者中皆可使用。 - Node.js API
主进程和渲染进程都可以直接访问Node.js API
要从渲染过程中访问Node.js API,需设置nodeIntegration:true
。
electron-log
用于将log保存到本地文件
- windows下的默认保存路径
%USERPROFILE%\AppData\Roaming\<app name>\log.log
- 对于打包后的项目,只有
error
和warn
会进入log
npm i electron-log --save
var log = require('electron-log');
log.transports.file.file = "./hello.log";//改变输出log文件位置
log.error("hello world");
webContents
官方文档
是BrowserWindow
/BrowserView
实例的一个属性,具有多种回调、方法、属性用于监听及控制页面
const { BrowserWindow } = require('electron')
const win = new BrowserWindow({ width: 800, height: 1500 })
win.loadURL('http://github.com')
const contents = win.webContents
console.log(contents)
- contents.executeJavaScript(code[, userGesture])
向页面中执行js,userGesture为true时认为是用户触发,hack触发一些必须user gesture
的H5特性 - contents.sendInputEvent(Mouse/KeyboardEvent)
模拟进行的鼠标/键盘操作
mainWindow.webContents.on("did-finish-load", function () {
mainWindow.webContents.executeJavaScript(`clickMe();`, true);
mainWindow.webContents.sendInputEvent({
type: "mouseDown",
x: 10,
y: 10,
button: "left",
clickCount: 1,
});
mainWindow.webContents.sendInputEvent({
type: "mouseUp",
x: 10,
y: 10,
button: "left",
clickCount: 1,
});
});
一些注意点
在渲染进程中启用node
const mainWindow = new BrowserWindow({
width: 1920,//fullscreen:true时该配置失效
height: 1080,//fullscreen:true时该配置失效
fullscreen: true, //默认false,当显式设为false时全屏按钮会被隐藏
frame:false,//窗口是否有边框 默认true
icon: path.join(__dirname, "favicon.ico"), //图标
webPreferences: {
nodeIntegration: true, //允许页面中使用nodejs语法
nodeIntegrationInWorker: true,
webSecurity: false, //禁用同源策略允许跨域
allowRunningInsecureContent: true, //允许在https网站中加载http的css和js
preload: path.join(__dirname, "preload.js"), //界面的其它脚本运行之前预先加载一个指定脚本
},
show: false, //一开始不显示
backgroundColor: "#FCF7ED", //背景色
});
其中nodeIntegration:true
允许在渲染进程(即每个页面)中写node脚本。由于module的差异性,此时引入JQuery等插件会导致出错,可通过如下方法修复
<script>if (typeof module === 'object') { window.module = module; module = undefined; }</script>
<script src="/js/jquery.min.js" type="text/javascript"></script>
<script>if (window.module) { module = window.module };</script>
判断页面是否处于electron的node环境中
通过typeof
判断module
或global
是"undefined"
还是"object"
获取设备标识信息
mac地址
window.require("os").networkInterfaces();
(注意html中要使用window.require
,否则因webpack环境中也有require("os")
,导致未使用electron中的os模块)
返回值是一个包含所有网络连接方式的对象,同一设备连无线和有线时返回内容不同,且不同系统版本的key命名也不同,可使用node-getmac
包获取(存疑)
硬盘/CPU序列号
通过调用系统命令获取,本质上node-getmac
也是如此
require('child_process')
.exec('wmic diskdrive get SerialNumber',function(error, stdout, stderr){
console.log("stdout",stdout)
})
require('child_process')
.execSync('wmic CPU get ProcessorID').toString()
executeJavaScript时注意变量的使用
错误用法中等于console.log(hello world)
,显然无法执行
//错误用法
var str = "hello world";
executeJavaScript(`console.log(${str})`);
//正确用法
var str = JSON.stringify("hello world");
executeJavaScript(`console.log(${str})`);
读取本地图片
chromium为了安全考虑,禁用了file://协议读取文件,会报错net::ERR_UNKNOWN_URL_SCHEME
。
可以通过 protocol 模块注册自定义协议并拦截,比如我们实现一个atom://协议加载音乐文件进行播放:
主进程:
const protocol = require("electron")
app.whenReady().then(() => {
// 这个需要在app.ready触发之后使用
protocol.registerFileProtocol('atom', (request, callback) => {
const url = request.url.substr(7)
callback(decodeURI(path.normalize(url)))
})
})
渲染进程:
<audio src="atom:///C:\Users\Administrator\Downloads\1.png"></audio>
当匹配到 atom 时,就会拦截这个协议请求,返回一个本地路径。
注意:如果路径有中文名,那么获取的url是encodeURI编码后的,在callback回调时需要用decodeURI进行解码。