IPC
Webview内被动触发
异步音讯告诉
// preload.js - 在畫面轉譯處理序中 (網頁)。const { ipcRenderer } = require('electron')ipcRenderer.on('need-clean-reply', (event, arg) => { console.log(arg) // 印出 "貓咪肚子餓"})ipcRenderer.send('take-cat-home-message', '帶小貓回家')// main.js - 在主處理序裡。const { ipcMain } = require('electron')ipcMain.on('take-cat-home-message', (event, arg) => { console.log(arg) // prints "帶小貓回家" event.reply('need-clean-reply', '貓咪肚子餓')})
// preload.js - 在畫面轉譯處理序中 (網頁)。const { ipcRenderer } = require('electron')ipcRenderer.invoke('take-cat-home-handle', '帶小貓回家') // then 回傳前能够做其余事,例如打掃家裡 .then(msg => console.log(msg)) // prints "小貓肚子餓,喵喵叫"// main.js - 在主處理序裡。const { ipcMain } = require('electron')ipcMain.handle('take-cat-home-handle', async (event, arg) => { console.log(arg) // prints "帶小貓回家" return '小貓肚子餓,喵喵叫'})
同步音讯告诉
// preload.js - 在畫面轉譯處理序中 (網頁)。const { ipcRenderer } = require('electron')const message = ipcRenderer.sendSync('take-cat-home-message', '帶小貓回家')console.log(message) // prints "小貓肚子餓"// main.js - 在主處理序裡。const { ipcMain } = require('electron')ipcMain.on('take-cat-home-message', (event, arg) => { console.log(arg) // prints "帶小貓回家" // event 回傳前你始终關注著小貓 event.returnValue = '小貓肚子餓'})
主线程告诉
// main.js - 在主處理序裡。mainWindow.webContents.send('switch-cat', number);// preload.js - 在畫面轉譯處理序中 (網頁)。const { ipcRenderer } = require('electron')ipcRenderer.on('switch-cat', (event, args) => switchCat(args));
获取webContents的形式
主线程
ipcMain.on('notify:new-msg', (event, chat) => { const mainWindow = BrowserWindow.fromWebContents(event.sender); // 利用 event.sender 获得 currentWindow const isFocused = mainWindow.isFocused(); // 確認 mainWindow 是否在最下面 const myNoti = new Notification({ title: `${chat.name}有新的對話`, subtitle: chat.msg }); myNoti.on('click', () => mainWindow.show()); // 將 mainWindow 帶到最下面 myNoti.on('close', () => mainWindow.show()); // 將 mainWindow 帶到最下面 myNoti.show(); if (!isFocused) { // 工作列按鈕閃爍 mainWindow.flashFrame(true); }});
const mainWindow = BrowserWindow.fromWebContents(event.sender); // 利用 event.sender 获得 currentWindow
BrowserWindow.fromId(this.winId).webContents.send('update-badge', this.badgeCount);
渲染线程
// preload.js https://www.npmjs.com/package/@electron/remoteimport { remote } from "electron"remote.getCurrentWindow()remote.getCurrentWebContents()
Clipboard
// @/utils/clipboardUtils.jsconst read = clipboard => { const aFormats = clipboard.availableFormats(); const isImageFormat = aFormats.find(f => f.includes('image')); const isHtmlFormat = aFormats.find(f => f.includes('text/html')); const isTextFormat = aFormats.find(f => f.includes('text/plain')); const isRtfFormat = aFormats.find(f => f.includes('text/rtf')); if (isImageFormat) { const nativeImage = clipboard.readImage(); // 获得 clipboard 中的圖片 return nativeImage.toDataURL(); // data:image/png; } // 获得 clipboard 中的文字 else if (isTextFormat) return clipboard.readText(); // 获得 clipboard 中的 html 文字 else if (isHtmlFormat) return clipboard.readHTML(); // 获得 clipboard 中的 rtf 文字 else if (isRtfFormat) return clipboard.readRTF(); else return null;}module.exports = { read,}
下载
Electron内置模块实现
在 electron 中的下载行为,都会触发 session 的 will-download 事件。在该事件外面能够获取到 downloadItem 对象,通过 downloadItem 对象实现一个简略的文件下载管理器
拿到 downloadItem 后,暂停、复原和勾销别离调用 pause
、resume
和 cancel
办法。当咱们要删除列表中正在下载的项,须要先调用 cancel 办法勾销下载。
在 downloadItem 中监听 updated 事件,能够实时获取到已下载的字节数据,来计算下载进度和每秒下载的速度。
// 计算下载进度
const progress = item.getReceivedBytes() / item.getTotalBytes()
// 下载速度:
已接管字节数 / 耗时
Notes:
- Electron本身的下载模块疏忽headers信息,无奈满足断点续下载,须要调研request模块本身实现下载。
Request模块实现
https://github.com/request/re... Request模块申请配置
import request, { Headers } from "request";interface DownLoadFileConfiguration { remoteUrl: string; localDir: string; name: string; onStart: (headers: Headers) => void; onProgress: (received: number, total: number) => void; onSuccess: (filePath: string) => void; onFailed: () => void;}function downloadFile(configuration: DownLoadFileConfiguration) { let received_bytes = 0; let total_bytes = 0; const { remoteUrl, localDir, name, onStart = noop, onFailed = noop, onSuccess = noop, onProgress = noop, } = configuration; const req = request({ method: "GET", uri: remoteUrl, headers: { "Content-Type": "application/octet-stream", "Cache-Control": "no-cache", Connection: "keep-alive", Pragma: "no-cache", Range: `bytes=${0}-`, }, }); function abort(this: any, filepath: string) { this.abort(); // 文件操作https://ourcodeworld.com/articles/read/106/how-to-choose-read-save-delete-or-create-a-file-with-electron-framework removeFile(filepath); } const absolutePath = path.resolve(localDir, name); const out = fs.createWriteStream(absolutePath); req.pipe(out); req.on("response", function (data) { total_bytes = parseInt(data.headers["content-length"] || ""); const id = uuidv4(); // ⇨ '9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d' onStart(id, data.headers); }); if (Object.prototype.hasOwnProperty.call(configuration, "onProgress")) { req.on("data", function (chunk) { received_bytes += chunk.length; onProgress(received_bytes, total_bytes); }); } else { req.on("data", function (chunk) { received_bytes += chunk.length; }); } req.on("end", function () { onSuccess(absolutePath); }); req.on("error", function (err) { onFailed(); }); return { abort: abort.bind(req, absolutePath), pause: req.pause.bind(req), resume: req.resume.bind(req), }}
Notes:
- Mac环境下,针对大文件,该包默认只能下载8s左右,猜想是
timeout
超时工夫问题,应用keep-alive: true
就没有问题 request
模块的pause
、resume
、abort
办法在文档中没有申明,别离对应暂停、持续、勾销操作。
断点续下载
前置条件
- 须要服务端反对
Accept-Ranges:bytes
可标识以后资源是反对范畴申请的。
下载的细节解决
- 申请
Headers
退出Range
fetch( "http://nginx.org/download/nginx-1.15.8.zip", { method: "GET", //申请形式 mode: 'cors', headers: { //申请头 "Cache-Control": "no-cache", Connection: "keep-alive", Pragma: "no-cache", Range: "bytes=0-1" // Range设置https://www.cnblogs.com/nextl/p/13885225.html } })
- 文件合并
fs.createWriteStream创立文件写入流
- flags:如果是追加文件而不是笼罩它,则
flags
模式需为a
模式而不是默认的w
模式。
- flags:如果是追加文件而不是笼罩它,则
const absolutePath = path.resolve(localDir, name); const out = fs.createWriteStream(absolutePath, { flags: resume ? "a" : "w", }); req.pipe(out);
- 追加(fs.appendFileSync)的形式
https://www.jianshu.com/p/934...
分片并发下载
装置第三方利用
exe安装包不像msi安装包有装置标准,且装置后会主动启动应用程序,而不是装置、启动分为两步。
https://learn.microsoft.com/e...
https://www.pdq.com/blog/inst...
"Notion Setup 2.0.34.exe" -s /S
启动三方利用
实质是通过子过程,调用不同零碎环境的命令行唤起利用。
// /src/classes/App.cls.tsimport { spawn } from "child_process";class App { process: any; pid: number; constructor(process: any) { this.process = process; this.pid = process.pid; } private killApp(pid: number) { // 检测程序是否启动 // wmic process where caption=”XXXX.exe” get caption,commandline /value // 依据过程名杀死过程 // taskkill /F /IM XXX.exe spawn("cmd", ["/c", "taskkill", "/pid", "/IM", pid.toString()], { shell: true, // 隐式调用cmd stdio: "inherit", }); } on(eventType: "error" | "close" | "exit", listener: (...args: any) => void) { this.process.on(eventType, (code: number) => { console.log(`spawn error: ${code}`); listener(code); }); } close() { this.killApp(this.pid); }}export default App;
// /src/preload/index.tsimport { spawn } from "child_process";import App from "../classes/App.cls";function openApp(path: string): any { const childProcess = spawn("cmd", ["/c", "start", path], { shell: true, // 隐式调用cmd stdio: "inherit", }); console.log("--------childProcess------", childProcess); const app = new App(childProcess); /** * Notes: 这里没有返回app,而是从新组装了对象 —— preload中无奈返回简单类型 * https://stackoverflow.com/questions/69203193/i-passed-a-class-to-my-front-end-project-through-electrons-preload-but-i-could * https://www.electronjs.org/docs/latest/api/context-bridge#parameter--error--return-type-support */ return { on: app.on.bind(app), close: app.close.bind(app) };}contextBridge.exposeInMainWorld("JSBridge", { openApp,});
// index.vueconst app = window.JSBridge.openApp(absolutePath);app.on("error", (code: number) => { console.log("---------spawn error----------", code, absolutePath);});app.on("close", (code: number) => { console.log("---------spawn close----------", code, absolutePath);});app.on("exit", (code: number) => { console.log("---------spawn exit----------", code, absolutePath);});setTimeout(() => { app.close();}, 10000);
window命令cmd
Window命令行装置软件 —— winget须要装置
打包
通用计划
electron打包工具有两个:electron-builder,electron-packager,官网还提到electron-forge,其实它不是一个打包工具,而是一个相似于cli的工具集,目标是简化开发到打包的一整套流程,外部打包工具仍然是electron-packager。
electron-builder与electron-packager相比各有优劣,electron-builder配置项较多,更加灵便,打包体积绝对较小,同时上手难度大;而electron-packger配置简略易上手,然而打进去的利用程序包体积绝对较大。
打包优化分为打包工夫优化和体积优化
双package.json构造
减少打包配置:装置门路抉择,icon等信息
平台
在 Windows 平台上能够打包成 NSIS 應用與 Portal 應用兩種類型:
类型 | 性能特点 | 更新形式 |
---|---|---|
Portal | 绿色程序,启动exe即可应用 | 下載新執行檔案 , 關閉並刪除舊執行檔案 => 更新实现 |
NSIS | 安装程序,装置后能力应用 | 下載新安裝檔 , 安裝後重開應用程式 => 更新实现 |
- Portal 版本
如果你是 Portal 版的程式 , 只有下載新的 exe , 並覆蓋掉舊的應用程式 , 就算实现更新了 ! - NSIS 版本
當你用 electron-builder 生成一個 windows 安裝檔時 , 那個安裝檔就是 NSIS 版本
应用 electron-builder 產出的 NSIS 安裝檔 , 他具體的安裝步驟如下
- 執行安裝檔
- 安裝檔比對 Windows 內有沒有 appId 雷同的 Electron 程式
- 如果有 appId 雷同的 Electron 程式 , 執行 old-uninstaller.exe 將舊版解安裝
- 安裝目前版本的 Electron 程式 , 並將其開啟
因而如果你要更新應用程式 , 你只有拿到新版的 NSIS 安裝檔並執行它 , 安裝实现後你就能够享受更新後的應用程式了 !
https://ithelp.ithome.com.tw/...
Q&A
Q:
dialog
模块undefined
- A:
import { remote } from "electron";
通过remote.dialog
调用
- A:
Q:
An Object could not be cloned?
- A:检测IPC通信中是否蕴含响应式数据
Q:下载过程中,定义对象,能够继续更新么?
- A:尝试过,能够。
参考文档
文中有...