electron 起步
electron 起步
為什麼要學 Electron,因為公司需要調試 electron 的應用。
Electron 是 node
和 chromium
的結合體,可以使用 JavaScript,HTML 和 CSS 等 Web 技術創建桌面應用程序
,支持 Mac、Window 和 Linux 三個平台。
electron 的成功案例有許多,比如大名鼎鼎的 vscode
。
hello-world
官網有個快速啟動
的應用程序,我們將其下載到本地運行看一下。
# Clone this repository
git clone //github.com/electron/electron-quick-start
# Go into the repository
cd electron-quick-start
# Install dependencies
npm install
# Run the app
npm start
注:運行 npm i
時卡在> node install.js
,許久後報錯如下
$ npm i
> [email protected] postinstall electron\electron-quick-start\node_modules\electron
> node install.js
RequestError: read ECONNRESET
at ClientRequest.<anonymous> (electron\electron-quick-start\node_modules\got\source\request-as-event-emitter.js:178:14)
at Object.onceWrapper (events.js:422:26)
at ClientRequest.emit (events.js:327:22)
at ClientRequest.origin.emit (electron\electron-quick-start\node_modules\@szmarczak\http-timer\source\index.js:37:11)
at TLSSocket.socketErrorListener (_http_client.js:432:9)
at TLSSocket.emit (events.js:315:20)
at emitErrorNT (internal/streams/destroy.js:84:8)
at processTicksAndRejections (internal/process/task_queues.js:84:21)
npm ERR! code ELIFECYCLE
npm ERR! errno 1
npm ERR! [email protected] postinstall: `node install.js`
npm ERR! Exit status 1
npm ERR!
npm ERR! Failed at the [email protected] postinstall script.
npm ERR! This is probably not a problem with npm. There is likely additional logging output above.
網上搜索 postinstall: node install.js
的解決辦法是將 electron 下載地址指向 taobao 鏡像:
// 將electron下載地址指向taobao鏡像
$ npm config set electron_mirror "//npm.taobao.org/mirrors/electron/"
新建項目 electron-demo
並參考 electron-quick-start
:
// 新建文件夾
$ mkdir electron-demo
// 進入項目
$ cd electron-demo
// 初始化項目
$ npm init -y
// 安裝依賴包
$ npm i -D electron
新建 electron 入口文件 index.js
:
// electorn-demo/index.js
const { app, BrowserWindow } = require('electron')
const path = require('path')
function createWindow() {
const mainWindow = new BrowserWindow({
width: 800,
height: 600,
})
// 加載html頁面
mainWindow.loadFile('index.html')
}
app.whenReady().then(() => {
createWindow()
})
新建一個 html 頁面(例如 index.html
):
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Electron 網頁</title>
</head>
<body>
hello world
</body>
</html>
在 package.json 中增加啟動 electron 的命令:
{
"name": "electron-demo",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
// 會運行 main 字段指向的 indx.js 文件
+ "start": "electron ."
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"electron": "^20.1.1"
}
}
啟動 electron 應用:
$ npm run start
Tip:windows 下通過 ctrl+shift+i
可以打開調試界面。或使用 mainWindow.webContents.openDevTools()
也可以打開。
// 加載html頁面
mainWindow.loadFile('index.html')
// 默認打開調試工具
+ mainWindow.webContents.openDevTools()
熱加載
現在非常不利於調試:修改 index.js
或 index.html
需要新運行 npm run start
才能看到效果。
可以 electron-reloader
來解決這個問題。只要已修改代碼,無需重啟就能看到效果。
首先安裝依賴包,然後修改 electron 入口文件即可:
$ npm i -D electron-reloader
npm WARN optional SKIPPING OPTIONAL DEPENDENCY: fsevents@~2.3.2 (node_modules\chokidar\node_modules\fsevents):
npm WARN notsup SKIPPING OPTIONAL DEPENDENCY: Unsupported platform for [email protected]: wanted {"os":"darwin","arch":"any"} (current: {"os":"win32","arch":"x64"})
npm WARN [email protected] No description
npm WARN [email protected] No repository field.
+ [email protected]
added 30 packages from 19 contributors and audited 109 packages in 26.217s
20 packages are looking for funding
run `npm fund` for details
found 1 moderate severity vulnerability
run `npm audit fix` to fix them, or `npm audit` for details
electron 入口文件增加如下代碼:
$ git diff index.js
const { app, BrowserWindow } = require('electron')
const path = require('path')
// 參考 npmjs 包
+try {
+ require('electron-reloader')(module);
+} catch {}
+
重啟服務後,再次修改 index.js
、index.html
等文件,只要保存
就會自動看到效果。
主進程和渲染進程
官方api中有Main Process 模塊
和 Renderer Process 模塊
,比如我們在 hello-world 示例中使用的 BrowserWindow
標記了主進程
,什麼意思?
- 主進程,通常是指
main.js
文件,是每個 Electron 應用的入口文件。控制着整個應用的生命周期,從打開到關閉。主進程負責創建 APP 的每一個渲染進程。一個 electron 應用有且只有一個
主線程。 - 渲染進程是應用中的瀏覽器窗口。 與主進程不同,渲染進程可能同時存在
多個
。 - 如果一個api同時屬於兩個進程,這裡會歸納到
Main Process 模塊
。 - 如果在渲染進程中需要使用主進程的 api,需要通過
remote
的方式(下面會用到)。
菜單
通過 Menu.setApplicationMenu
新增菜單。代碼如下:
// index.js
require('./menu.js')
// menu.js
const { BrowserWindow, Menu } = require('electron')
const template = [
{
id: '1', label: 'one',
submenu: [
{
label: '新開窗口',
click: () => {
const ArticlePage = new BrowserWindow({
width: 500,
height: 500,
})
ArticlePage.loadFile('article.html')
}
},
]
},
{ id: '2', label: 'two' },
]
const menu = Menu.buildFromTemplate(template)
Menu.setApplicationMenu(menu)
效果如下圖所示:
注:新增窗口的菜單和主窗口的菜單相同。
自定義菜單
比如酷狗音樂,導航是比較好看。做法是隱藏原生菜單,用自己的 html 代替。
下面我們將原生菜單功能改為自定義菜單。
首先通過 frame
將應用的邊框去除,原始菜單也不再可見:
const mainWindow = new BrowserWindow({
// frame boolean (可選) - 設置為 false 時可以創建一個無邊框窗口 默認值為 true。
// 去除邊框,菜單也不可見了
+ frame: false,
width: 1500,
height: 500,
})
Tip: frame boolean (可選) – 設置為 false 時可以創建一個無邊框窗口 默認值為 true
接着在 html 頁面實現自己的菜單。例如:
<p class="nav">
<span class="j-new">新建窗口</span>
<span>菜單2</span>
</p>
原始的導航是可以通過鼠標拖動。
我們可以使用 -webkit-app-region
來增加可拖拽效果:
<style>
.nav{-webkit-app-region: drag;}
.nav span{
-webkit-app-region: no-drag;
background-color:pink;
cursor: pointer;
}
</style>
注:需要給菜單關閉拖拽效果,否則 cursor: pointer
會失效,點擊也沒反應,無法繼續進行。
接下來給菜單添加事件,點擊時打開新窗口。
這裡得使用 BrowserWindow
,則需要使用 require
。
直接使用 require 會報錯 require is not defined
。需要在主進程中開啟:
const mainWindow = new BrowserWindow({
webPreferences: {
// 否則報錯:require is not defined
+ nodeIntegration: true,
// 在新版本的electron中由於安全性的原因還需要設置 contextIsolation: false
+ contextIsolation: false,
},
})
接下來就得在引入 BrowserWindow,相當於在渲染進程中使用主進程的 api,需要使用 remote。如果這麼用則會報錯 const BrowserWindow = require("electron").remote.BrowserWindow
,網友說:electron12 中已經廢棄了remote 模塊,如果需要則需自己安裝 @electron/remote。
進入 npmjs 中搜索 @electron/remote
,用於連接主進程到渲染進程的 js 對象:
// @electron/remote是Electron 模塊,連接主進程到渲染進程的 js 對象
@electron/remote is an Electron module that bridges JavaScript objects from the main process to the renderer process. This lets you access main-process-only objects as if they were available in the renderer process.
// @electron/remote是electron內置遠程模塊的替代品,該模塊已被棄用,最終將被刪除。
@electron/remote is a replacement for the built-in remote module in Electron, which is deprecated and will eventually be removed.
用法如下:
// 安裝依賴
$ npm install --save @electron/remote
// 在主進程中進行初始化
require('@electron/remote/main').initialize()
require("@electron/remote/main").enable(mainWindow.webContents);
// 渲染進程
const { BrowserWindow } = require('@electron/remote')
核心頁面的完整代碼如下:
- 主進程頁面:
// index.js 入口文件(主進程)
const { app, BrowserWindow} = require('electron')
// 在主進程中進行初始化,然後才能從渲染器中使用
require('@electron/remote/main').initialize()
// 熱加載
try {
require('electron-reloader')(module);
} catch { }
function createWindow() {
const mainWindow = new BrowserWindow({
// 去除邊框,菜單也不可見了
frame: false,
width: 1500,
height: 500,
webPreferences: {
// 否則報錯:require is not defined
nodeIntegration: true,
// 在新版本的electron中由於安全性的原因還需要設置 contextIsolation: false
contextIsolation: false,
// 不需要 enableremoteModule,remote 也生效
// enableremoteModule: true
},
})
// 在electron >= 14.0.0中,您必須使用新的enableAPI 分別為每個所需的遠程模塊啟用WebContents
// 筆者:"electron": "^20.1.1"
require("@electron/remote/main").enable(mainWindow.webContents);
// 加載html頁面
mainWindow.loadFile('index.html')
}
app.whenReady().then(() => {
createWindow()
})
// 加載菜單
require('./menu.js')
- 渲染進程頁面
<!-- index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Electron 網頁</title>
<style>
.nav{-webkit-app-region: drag;}
.nav span{-webkit-app-region: no-drag;background-color:pink;cursor: pointer;}
</style>
</head>
<body>
<p class="nav">
<span class="j-new">新建窗口</span>
<span><a href='www.baidu.com'>百度</a></span>
</p>
hello world2
<script src="index-js.js"></script>
</body>
</html>
// index-js.js 渲染進程
// 渲染窗口使用 require 瀏覽器報錯:Uncaught ReferenceError: require is not defined
const { BrowserWindow } = require('@electron/remote')
const elem = document.querySelector('.j-new')
elem.onclick = function(){
const ArticlePage = new BrowserWindow({
width: 500,
height: 500,
})
ArticlePage.loadFile('article.html')
}
打開瀏覽器
現在需要點擊自定義菜單中的百度
,然後打開瀏覽器並跳轉到百度。
這裡主要用到 shell,它屬於主進程也屬於渲染進程,所以這裡無需使用 remote 方式引入。首先在 index.html 中增加 a 標籤,然後在 js 中註冊點擊事件,最後調用 shell.openExternal 即可。請看代碼:
<p class="nav">
<span class="j-new">新建窗口</span>
<span><a href='//www.baidu.com'>百度</a></span>
</p>
// index-js.js
const { shell} = require('electron')
// 渲染窗口使用 require 瀏覽器報錯:Uncaught ReferenceError: require is not defined
const { BrowserWindow } = require('@electron/remote')
...
const aElem = document.querySelectorAll('a');
[...aElem].forEach(item => item.onclick=(e)=>{
// 防止主進程打開頁面
e.preventDefault()
const url = e.target.getAttribute('href');
shell.openExternal(url)
})
點擊百度
,會打開本地默認瀏覽器。
Tip:如果不要 http
直接寫成 www.baidu.com
,筆者測試失敗。
文件讀取和保存
先看要實現的效果:
點擊讀取
,選擇文件後,文件內容會顯示到 textarea 中,對 textarea 進行修改文案,點擊保存
,輸入要保存的文件名即可保存。
這裡需要使用主進程的 dialog api。由於 dialog 屬於主進程,要在渲染進程中使用,則需要使用 remote。
dialog – 顯示用於打開和保存文件、警報等的本機系統對話框。這裡用到兩個 api 分別用於打開讀取文件和保存文件的系統窗口:
dialog.showOpenDialogSync
,打開讀取文件的系統窗口,可以定義標題、確定按鈕的文本、指定可顯示文件的數組類型等等,點擊保存,返回用戶選擇的文件路徑。接下來就得用 node 去根據這個路徑讀取文件內容dialog.showSaveDialogSync
,與讀文件類似,這裡是保存,返回要保存的文件路徑
Tip:真正讀取文件和保存文件還是需要使用 node 的 fs 模塊。有關 node 的讀寫文件可以參考這裡
核心代碼如下:
// index-js.js
const fs = require('fs')
const { BrowserWindow, dialog } = require('@electron/remote')
...
// 文件操作
const readElem = document.querySelector('.j-readFile')
const textarea = document.querySelector('textarea')
const writeElem = document.querySelector('.j-writeFile')
// 讀文件
readElem.onclick = function () {
// 返回 string[] | undefined, 用戶選擇的文件路徑,如果對話框被取消了 ,則返回undefined。
const paths = dialog.showOpenDialogSync({ title: '選擇文件', buttonLabel: '自定義確定' })
console.log('paths', paths)
fs.readFile(paths[0], (err, data) => {
if (err) throw err;
textarea.value = data.toString()
console.log(data.toString());
});
}
// 寫文件
writeElem.onclick = function () {
// 返回 string | undefined, 用戶選擇的文件路徑,如果對話框被取消了 ,則返回undefined。
const path = dialog.showSaveDialogSync({ title: '保存文件', buttonLabel: '自定義確定' })
console.log('path', path)
// 讀取要保存的內容
const data = textarea.value
fs.writeFile(path, data, (err) => {
if (err) throw err;
console.log('The file has been saved!');
});
}
註冊快捷鍵
比如 vscode 有快捷鍵,這裡我們也給加上。比如按 ctrl+m 時,最大化或取消最大化窗口。
這裡需要使用 globalShortcut
(主進程 ):在應用程序沒有鍵盤焦點時,監聽鍵盤事件。
用法很簡單,直接註冊即可。請看代碼:
const { app, BrowserWindow, globalShortcut} = require('electron')
app.whenReady().then(() => {
const mainWindow = createWindow()
// 註冊快捷鍵
globalShortcut.register('CommandOrControl+M', () => {
// 主進程會在 node 中輸出,而非瀏覽器
console.log('CommandOrControl+M is pressed')
// 如果是不是最大,就最大化,否則取消最大化
!mainWindow.isMaximized() ? mainWindow.maximize() : mainWindow.unmaximize()
})
})
渲染進程中註冊快捷鍵也類似,不過需要通過 remote 的方式引入。就像這樣:
const { BrowserWindow, dialog, globalShortcut } = require('@electron/remote')
// 渲染進程中註冊快捷鍵
globalShortcut.register('CommandOrControl+i', () => {
// 主進程會在 node 中輸出,而非瀏覽器
console.log('CommandOrControl+i is pressed')
})
主線程和渲染進程通信
需求:在渲染進程中點擊一個按鈕,來觸發主線程的放大或取消放大功能。
需要使用以下兩個 api:
ipcMain
主進程 – 從主進程到渲染進程的異步通信。ipcRenderer
渲染進程 – 從渲染器進程到主進程的異步通信。
Tip:在 Electron 中,進程使用 ipcMain 和 ipcRenderer 模塊,通過開發人員定義的「通道」傳遞消息來進行通信。 這些通道是 任意 (您可以隨意命名它們)和 雙向 (您可以在兩個模塊中使用相同的通道名稱)的。
首先在主進程註冊事件:
ipcMain.on('max-unmax-event', (evt, ...args) => {
// max-unmax-event。args= [ 'pjl' ]
console.log('max-unmax-event。args=', args)
})
接着在 index.html 中增加最大化/取消最大化
按鈕:
<p class="nav">
<span class="j-new">新建窗口</span>
+ <span class="j-max">最大化/取消最大化</span>
</p>
最後在渲染進程中,點擊按鈕時通過 ipcRenderer
觸發事件,並可傳遞參數。就像這樣
// 最大化/取消最大化
const maxElem = document.querySelector('.j-max')
maxElem.onclick = function() {
ipcRenderer.send('max-unmax-event', 'pjl')
console.log('max')
}
打包
打包可以藉助 electron-packager
來完成。
// 安裝
$ npm install --save-dev electron-packager
// 用法
npx electron-packager <sourcedir> <appname> --platform=<platform> --arch=<arch> [optional flags...]
// 就像這樣
"build": "electron-packager ./ electron-demo --platform=win32 --arch=x64 ./outApp --overwrite --icon=./favicon.ico"
筆者環境是 node13.14,構建失敗:
$ npm run build
> [email protected] build electron\electron-demo
> electron-packager ./ electron-demo --platform=win32 --arch=x64 ./outApp --overwrite --icon=./favicon.ico
CANNOT RUN WITH NODE 13.14.0
Electron Packager requires Node >= 14.17.5.
npm ERR! code ELIFECYCLE
...
react/vue 中使用 electron
用法很簡單,首先準備好 react 或 vue 項目,筆者就以多次使用的 react(spug) 項目來進行。
安裝 electron 包:
$ npm i -D electron
在 package.json 中增加運行命令,指定 electron 入口文件:
$ git diff package.json
"name": "spug_web",
+ "main": "electron-main.js",
"scripts": {
"test": "react-app-rewired test",
"eject": "react-scripts eject",
+ "electron": "electron ."
},
"devDependencies": {
+ "electron": "^20.1.2",
編寫 electron 入口文件如下,其中 loadFile 要改為 loadURL
。
// electron-main.js
const { app, BrowserWindow } = require('electron')
const path = require('path')
function createWindow() {
const mainWindow = new BrowserWindow({
width: 800,
height: 600,
})
// loadFile 改為 loadURL
mainWindow.loadURL('//localhost:3000/')
}
app.whenReady().then(() => {
createWindow()
})
// 啟動 react 項目,在瀏覽器中能通過 //localhost:3000/ 正常訪問
$ npm run start
// 啟動 electron
$ npm run electron
正常的話, electron 中就能看到 react 的項目。效果如下:
Tip:如果發佈的話,首先通過 react 構建,例如輸出 dir,然後將 loadURL 改為 loadFile('./dir/index.html')
。
完整代碼
index.js
// index.js 入口文件(主進程)
const { app, BrowserWindow, globalShortcut, ipcMain} = require('electron')
// 在主進程中進行初始化,然後才能從渲染器中使用
require('@electron/remote/main').initialize()
// 熱加載
try {
require('electron-reloader')(module);
} catch { }
function createWindow() {
const mainWindow = new BrowserWindow({
// frame boolean (可選) - 設置為 false 時可以創建一個無邊框窗口 默認值為 true。
// 去除邊框,菜單也不可見了
frame: false,
width: 1500,
height: 500,
webPreferences: {
// 否則報錯:require is not defined
nodeIntegration: true,
// 在新版本的electron中由於安全性的原因還需要設置 contextIsolation: false
contextIsolation: false,
// 不需要 enableremoteModule,remote 也生效
// enableremoteModule: true
},
})
// 在electron >= 14.0.0中,您必須使用新的enableAPI 分別為每個所需的遠程模塊啟用WebContents
// 筆者:"electron": "^20.1.1"
require("@electron/remote/main").enable(mainWindow.webContents);
// 加載html頁面
mainWindow.loadFile('index.html')
return mainWindow
}
app.whenReady().then(() => {
const mainWindow = createWindow()
// 註冊快捷鍵
globalShortcut.register('CommandOrControl+M', () => {
// 主進程會在 node 中輸出,而非瀏覽器
console.log('CommandOrControl+M is pressed')
// 如果是不是最大,就最大化,否則取消最大化
!mainWindow.isMaximized() ? mainWindow.maximize() : mainWindow.unmaximize()
})
})
// 加載菜單
require('./menu.js')
ipcMain.on('max-unmax-event', (evt, ...args) => {
console.log('max-unmax-event。args=', args)
})
index.html
<!-- index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Electron 網頁</title>
<style>
.nav{-webkit-app-region: drag;}
.nav span{-webkit-app-region: no-drag;background-color:pink;cursor: pointer;}
</style>
</head>
<body>
<p class="nav">
<span class="j-new">新建窗口</span>
<span><a href='//www.baidu.com'>百度</a></span>
<span class="j-max">最大化/取消最大化</span>
</p>
<p>hello world</p>
<section>
<h3>文件操作</h3>
<textarea style="height:100px;width:100%;" placeholder="請先讀取文件,修改後保存"></textarea>
<button class="j-readFile">讀取文件</button>
<button class="j-writeFile">保存文件</button>
</section>
<script src="index-js.js"></script>
</body>
</html>
index-js.js
// index-js.js
const { shell, ipcRenderer } = require('electron')
const fs = require('fs')
// 渲染窗口使用 require 瀏覽器報錯:Uncaught ReferenceError: require is not defined
const { BrowserWindow, dialog, globalShortcut } = require('@electron/remote')
const elem = document.querySelector('.j-new')
elem.onclick = function () {
const ArticlePage = new BrowserWindow({
width: 500,
height: 500,
})
ArticlePage.loadFile('article.html')
}
const aElem = document.querySelectorAll('a');
[...aElem].forEach(item => item.onclick = (e) => {
e.preventDefault()
const url = e.target.getAttribute('href');
console.log('href', url)
shell.openExternal(url)
})
// 文件操作
const readElem = document.querySelector('.j-readFile')
const textarea = document.querySelector('textarea')
const writeElem = document.querySelector('.j-writeFile')
// 讀文件
readElem.onclick = function () {
// 返回 string[] | undefined, 用戶選擇的文件路徑,如果對話框被取消了 ,則返回undefined。
const paths = dialog.showOpenDialogSync({ title: '選擇文件', buttonLabel: '自定義確定' })
console.log('paths', paths)
fs.readFile(paths[0], (err, data) => {
if (err) throw err;
textarea.value = data.toString()
console.log(data.toString());
});
}
// 寫文件
writeElem.onclick = function () {
// 返回 string | undefined, 用戶選擇的文件路徑,如果對話框被取消了 ,則返回undefined。
const path = dialog.showSaveDialogSync({ title: '保存文件', buttonLabel: '自定義確定' })
console.log('path', path)
// 讀取要保存的內容
const data = textarea.value
console.log('data', data)
fs.writeFile(path, data, (err) => {
if (err) throw err;
console.log('The file has been saved!');
});
}
// 渲染進程中註冊快捷鍵
globalShortcut.register('CommandOrControl+i', () => {
// 主進程會在 node 中輸出,而非瀏覽器
console.log('CommandOrControl+i is pressed')
// mainWindow.webContents.openDevTools()
// // 如果是不是最大,就最大化,否則取消最大化
// !mainWindow.isMaximized() ? mainWindow.maximize() : mainWindow.unmaximize()
})
// 最大化/取消最大化
const maxElem = document.querySelector('.j-max')
maxElem.onclick = function() {
ipcRenderer.send('max-unmax-event', 'pjl')
console.log('max')
}
menu.js
// menu.js
const { BrowserWindow, Menu } = require('electron')
const template = [
{
id: '1', label: 'one',
submenu: [
{
label: '新開窗口',
click: () => {
const ArticlePage = new BrowserWindow({
width: 500,
height: 500,
})
ArticlePage.loadFile('article.html')
}
},
]
},
{ id: '2', label: 'two' },
]
const menu = Menu.buildFromTemplate(template)
Menu.setApplicationMenu(menu)
package.json
{
"name": "electron-demo",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"start": "electron .",
"build": "electron-packager ./ electron-demo --platform=win32 --arch=x64 ./outApp --overwrite --icon=./favicon.ico"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"electron": "^20.1.1",
"electron-packager": "^16.0.0",
"electron-reloader": "^1.2.3"
},
"dependencies": {
"@electron/remote": "^2.0.8"
}
}