官網:https://www.electronjs.org/zh/
安裝依賴
初始化package.json
pnpm init
安裝依賴
pnpm add -D electron
安裝報錯解決方案:https://blog.csdn.net/qq_38463737/article/details/140277803
1、打開npm的配置文件
# cmd 運行打開配置文件
npm config edit
2、在空白地方添加淘寶鏡像,下面三個(缺什麼補什麼,但要是同一個公司單位的鏡像)
registry=https://registry.npmmirror.com
electron_mirror=https://cdn.npmmirror.com/binaries/electron/
electron_builder_binaries_mirror=https://npmmirror.com/mirrors/electron-builder-binaries/
手動配置完重新安裝即可
pnpm add -D electron
啓動一個簡單的項目
1、修改package.json文件中的main與scripts配置段
其中author和description為必填
main 主進程腳本,這指向跟目錄的main.js
start 為啓動命令:electron .
{
"name": "Electron-補習所",
"version": "1.0.0",
"description": "electron-test",
"main": "main.js",
"scripts": {
"start": "electron ."
},
"keywords": [],
"author": "wangfan",
"license": "ISC",
"devDependencies": {
"electron": "^32.1.2"
}
}
2、根目錄新建main.js
const { app, BrowserWindow } = require("electron");
// 監聽app的ready事件
app.on("ready", () => {
// 創建一個窗口
const win = new BrowserWindow({
width: 500, // 窗口寬度
height: 500, // 窗口高度
autoHideMenuBar: true, // 隱藏默認菜單
});
win.loadURL('https://www.electronjs.org/zh/'); // 加載線上url頁面
// BrowserWindow的更多配置可參考文檔:https://www.electronjs.org/zh/docs/latest/api/browser-window
});
這樣一個簡單的項目就啓動了。
加載本地頁面
根目錄新建pages文件夾,在裏面寫入index.html、index.css,正常的寫一些內容
main.js文件修改,通過loadFile方法加載本地頁面
const { app, BrowserWindow } = require("electron");
// 監聽app的ready事件
app.on("ready", () => {
const win = new BrowserWindow({
width: 500,
height: 500,
autoHideMenuBar: true,
alwaysOnTop: true
});
win.loadFile('./pages/index.html'); // 加載頁面-指定路徑
});
啓動項目:pnpm run start
開發者工具,打開後會有一個內容安全策略警告
解決方法是配置CSP,具體配置可上MDN上查看:https://developer.mozilla.org/zh-CN/docs/Web/HTTP/CSP
配置完後重啓項目,內容安全策略警告就會消失。
完善窗口行為
這裏我將new BrowserWindow封裝為一個函數createWindow,當應用ready(準備完畢)後,調用createWindow創建一個窗口。
const { app, BrowserWindow } = require("electron");
// 創建一個窗口-封裝
function createWindow() {
// 創建一個窗口
const win = new BrowserWindow({
width: 800, // 窗口寬度
height: 500, // 窗口高度
autoHideMenuBar: true, // 隱藏默認菜單
});
win.loadFile("./pages/index.html"); // 加載頁面
// BrowserWindow的更多配置可參考文檔:https://www.electronjs.org/zh/docs/latest/api/browser-window
}
// 監聽app的ready事件
app.on("ready", () => {
createWindow();
// window所有窗口關閉時,並且不是蘋果系統,退出應用-管理窗口的生命週期
app.on("window-all-closed", () => {
if (process.platform !== "darwin") app.quit();
});
});
// 應用被激活時,窗口數量為0,自動創建一個窗口-管理窗口的生命週期
app.on("activate", () => {
if (BrowserWindow.getAllWindows().length === 0) createWindow();
});
窗口行為分兩種,windows系統和mac系統,在windows系統中,所有窗口關閉後自動關閉應用,而mac系統則是自動縮小到任務欄不會關閉應用。
所以這裏需要做窗口行為管理,也就是官網所説的管理窗口的生命週期
自動啓動應用
項目每次主進程(main.js)修改後,應用都需要手動重啓,非常麻煩,我們可以安裝nodemon來自動重啓項目
pnpm i nodemon -D
在package.json中配置
{
// 省略其它配置......
"scripts": {
"start": "nodemon --exec electron ." // 通過nodemon --exec來啓動electron .
}
}
這樣每次修改主進程內容後應用會自動重啓
另外你也可以在文件中具體配置nodemon,來實現主進程、頁面內容修改後自動重啓功能。
根目錄新建:nodemon.json
{
"ignore": ["node modules", "dist"],
"restartable": "r",
"watch": ["*.*"],
"ext": "html,js,css"
}
主進程與渲染進程
在electron中,主進程只有一個,渲染進程可以有n個
main.js就是主進程,主進程是node環境運行的
在html中運行的js就是渲染進程,渲染進程是在web環境中運行的
新建一個js文件,在index.html中引入它
修改js文件
// render.js
let btn = document.getElementById("btn1");
btn.addEventListener("click", () => {
alert("彈窗");
});
主進程(node環境)與渲染進程(web環境)作用的環境不同,他們之間是隔離開的,web端不能使用nodeAPI而node也無法使用webAPI。
web端由於是沙箱環境,如果你想在web端獲取一些系統、環境、硬件的信息,可能就需要藉助node能力與系統交互,那麼electron就為我們提供了主進程與渲染進程之間的通信。
主進程與渲染進程之間通過預加載腳本通信,預加載腳本在渲染端(web環境)運行,雖然預加載腳本在web端運行,但是它可以訪問一部分的nodeAPI,預加載腳本就是主進程與渲染進程之間的橋樑。
根目錄新建預加載腳本:preload.js
這樣我們的目錄結構就一目瞭然了,pages文件夾下管理渲染進程,preload.js管理預加載腳本,main.js管理主進程
我們隨便在preload.js中寫點東西打印
// preload.js
console.log('預加載腳本')
然後在主進程中引入預加載腳本,預加載腳本只能使用絕對路徑,這裏使用node模塊path引入根目錄下的preload.js
// main.js
const { app, BrowserWindow } = require("electron");
const path = require("path");
// 創建一個窗口-封裝
function createWindow() {
const win = new BrowserWindow({
// 省略其它配置項...
webPreferences: {
preload: path.resolve(__dirname,'./preload.js'), // 加載預加載腳本,絕對路徑
}
});
win.loadFile("./pages/index.html"); // 加載頁面
}
// 監聽app的ready事 件
app.on("ready", () => {
createWindow();
});
// 省略其它配置項...
啓動項目後會發現窗口打印了預加載腳本的輸出語句
應用的運行順序是:主進程 -> 預加載腳本 -> 渲染進程
現在我們來看預加載腳本如何與渲染進程通信的
使用 contextBridge 來選擇要從預加載腳本中暴露哪些 API,通過exposeInMainWorld向渲染器進程暴露一個全局的 window.abc變量。
// preload.js
const { contextBridge } = require("electron");
// 向渲染進程暴露全局myAPI變量
contextBridge.exposeInMainWorld("myAPI", {
mytext: "這是暴露的變量,預加載進程可使用部分nodeAPI",
version: process.version
});
然後在渲染進程中打印window
你會發現暴露的myAPI直接掛載在window身上
若是掛在在window身上,你可以直接獲取myAPI變量
btn.addEventListener("click", () => {
// alert("彈窗");
// console.log(window);
console.log(myAPI);
});
這樣就做到了渲染進程訪問預加載腳本的能力。
進程通訊(IPC)-重要
需求:點擊按鈕,在系統D盤創建一個hello.text文件,文件內容來自用户輸入
渲染進程向主進程通信(單項)
先編寫頁面
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta
http-equiv="Content-Security-Policy"
content="default-src 'self'; img-src https://*; child-src 'none';"
/>
<title>Document</title>
<link rel="stylesheet" href="./index.css" />
</head>
<body>
<h1>歡迎光臨</h1>
<button id="btn1">查看彈窗</button>
<br>
<br>
<hr>
<button id="setBtn">向D盤寫入hello.txt</button>
<input type="text" id="input">
</body>
<script src="./render.js"></script>
</html>
// render.js
let btn = document.getElementById("btn1");
let setBtn = document.getElementById("setBtn");
let input = document.getElementById("input");
btn.addEventListener("click", () => {
console.log(myAPI);
});
// 設置寫入按鈕的點擊事件
setBtn.onclick = () => {
// 調用預加載腳本中定義的函數saveFile並傳入input的輸入內容
myAPI.saveFile(input.value)
};
編寫預加載腳本
在預加載腳本中,通過ipcRenderer將消息發送到主進程創建的監聽器
// preload.js
const { contextBridge, ipcRenderer } = require("electron");
contextBridge.exposeInMainWorld("myAPI", {
mytext: "這是暴露的變量",
saveFile: (data) => {
// 通過ipcRenderer.send向主進程發送消息
ipcRenderer.send("file-save", data);
},
});
編寫主進程
在主進程中,通過ipcMain監聽預加載腳本發送的消息,監聽的時機在加載頁面之前。
1、創建窗口
2、訂閲預加載腳本
3、加載頁面
// main.js
const { app, BrowserWindow, ipcMain } = require("electron");
const path = require("path");
const fs = require("fs"); // 引入node的fs模塊操縱文件
// 自定義的函數,用於寫入文件
// 回調函數有兩個參數:一個IpcMainEvent結構和傳入的變量
// 但是這裏event用不上,所以用_佔位
function writeFile(_, data) {
// 在用户D盤下寫入hello.txt文件,文件內容為data
fs.writeFileSync("D:/hello.txt", data);
}
// 創建一個窗口-封裝
function createWindow() {
// 創建一個窗口
const win = new BrowserWindow({
width: 800, // 窗口寬度
height: 500, // 窗口高度
autoHideMenuBar: true, // 隱藏默認菜單
webPreferences: {
preload: path.resolve(__dirname, "./preload.js"), // 加載預加載腳本,絕對路徑
},
});
// 訂閲預加載腳本,在加載頁面之前,調用自定義函數writeFile
ipcMain.on("file-save", writeFile);
win.loadFile("./pages/index.html"); // 加載頁面
}
// 監聽app的ready事 件
app.on("ready", () => {
createWindow();
// 省略其它...
});
根據上面的步驟,就可以在系統D盤創建一個hello.txt文件,內容為輸入框的內容。
渲染進程向主進程通信(雙向)
讀取我們之前寫入的hello.txt文件內容
在頁面中寫好結構
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta
http-equiv="Content-Security-Policy"
content="default-src 'self'; img-src https://*; child-src 'none';"
/>
<title>Document</title>
<link rel="stylesheet" href="./index.css" />
</head>
<body>
<h1>歡迎光臨</h1>
<button id="btn1">查看彈窗</button>
<br>
<br>
<hr>
<button id="setBtn">向D盤寫入hello.txt</button>
<input type="text" id="input">
<br>
<br>
<hr>
<button id="getBtn">讀取D盤中hello.txt的內容</button>
</body>
<script src="./render.js"></script>
</html>
修改render.js的內容,同樣的,調用預加載腳本中的自定義函數
// render.js
let btn = document.getElementById("btn1");
let setBtn = document.getElementById("setBtn");
let input = document.getElementById("input");
let getBtn = document.getElementById("getBtn");
btn.addEventListener("click", () => {
console.log(myAPI);
});
setBtn.onclick = () => {
console.log(input.value);
myAPI.saveFile(input.value);
};
// 讀取D盤中hello.txt的內容
// 實際上ipcRenderer.invoke返回的是一個Promise,所以使用async-await獲取
getBtn.onclick = async () => {
let txt = await myAPI.readFile();
console.log(txt);
};
在預加載腳本中,通過invoke來向主進程暴露請求
// preload.js
const { contextBridge, ipcRenderer } = require("electron");
console.log("預加載腳本");
contextBridge.exposeInMainWorld("myAPI", {
mytext: "這是暴露的變量",
saveFile: (data) => {
ipcRenderer.send("file-save", data);
},
// 自定義讀取函數
readFile: () => {
// 通過ipcRenderer.invoke向主進程發送請求
// 並將請求回來的值返回出去,ipcRenderer.invoke返回的是一個Promise
return ipcRenderer.invoke("file-read");
},
});
在主進程中監聽invoke請求並執行自定義函數操作,最後將結果返回
通過ipcMain.handle監聽invoke事件
const { app, BrowserWindow, ipcMain } = require("electron");
const path = require("path");
const fs = require("fs");
function writeFile(_, data) {
fs.writeFileSync("D:/hello.txt", data);
}
// 自定義函數,讀取D盤下的hello.txt文件內容,並返回
function readFile() {
// 利用node的fs模塊操作文件,readFileSync讀取文件,toString將buffer轉為字符串返回
return fs.readFileSync("D:/hello.txt").toString();
}
// 創建一個窗口-封裝
function createWindow() {
const win = new BrowserWindow({
width: 800,
height: 500,
autoHideMenuBar: true,
webPreferences: {
preload: path.resolve(__dirname, "./preload.js"), // 加載預加載腳本,絕對路徑
},
});
// 訂閲預加載腳本,在加載頁面之前
// 監聽預加載腳本send事件
ipcMain.on("file-save", writeFile);
// 監聽預加載腳本invoke事件
ipcMain.handle("file-read", readFile);
win.loadFile("./pages/index.html"); // 加載頁面
}
// 監聽app的ready事 件
app.on("ready", () => {
createWindow();
});
打包應用
pnpm install electron-builder -D
在package.json中配置
{
"name": "original-electron",
"version": "1.0.0",
"description": "original electron",
"main": "main.js",
"scripts": {
"start": "nodemon --exec electron .",
"build": "electron-builder" // 打包命令
},
"build": {
"appId": "myelectron-app", // 應用程序唯一標識符
"win": {
"icon": "./logo.ico", // 應用圖標
"target": [
{
"target": "nsis", // 指定使用 NSIS 作為安裝程序格式
"arch": ["x64"] // 生成64位安裝包
}
]
},
"nsis": {
"oneClick": false, // 設置為 false 使安裝程序顯示安裝嚮導界面,而不是一鍵安裝
"perMachine": true, // 允許每台機器安裝一次,而不是每個用户都安裝
"allowToChangeInstallationDirectory": true // 允許用户在安裝過程中選擇安裝目錄
}
},
}
運行打包命令
pnpm run build
目錄中會生成dist文件夾,內部就打包的產物
參考文檔:
安裝依賴失敗:https://blog.csdn.net/qq_38463737/article/details/140277803
官網:https://www.electronjs.org/zh/
electron-vite腳手架:https://cn.electron-vite.org/