Electron

简介

Electron用于通过Javascript、HTML、CSS来编写运行于Windows、macOS、Linux跨平台的桌面应用。

简单的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
  • 对于打包后的项目,只有errorwarn会进入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判断moduleglobal"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进行解码。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,794评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,050评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,587评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,861评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,901评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,898评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,832评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,617评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,077评论 1 308
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,349评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,483评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,199评论 5 341
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,824评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,442评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,632评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,474评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,393评论 2 352

推荐阅读更多精彩内容