動態

詳情 返回 返回

原生electron起步-從零到一完成構建和打包 - 動態 詳情

官網: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/

image.png
手動配置完重新安裝即可

pnpm add -D electron

image.png

啓動一個簡單的項目

1、修改package.json文件中的mainscripts配置段

其中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
});

image.png
image.png
這樣一個簡單的項目就啓動了。

加載本地頁面

根目錄新建pages文件夾,在裏面寫入index.htmlindex.css,正常的寫一些內容
image.png
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
image.png
開發者工具,打開後會有一個內容安全策略警告
image.png
解決方法是配置CSP,具體配置可上MDN上查看:https://developer.mozilla.org/zh-CN/docs/Web/HTTP/CSP
image.png
image.png
配置完後重啓項目,內容安全策略警告就會消失。

完善窗口行為

這裏我將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中引入它
image.png
修改js文件

// render.js
let btn = document.getElementById("btn1");

btn.addEventListener("click", () => {
  alert("彈窗");
});

image.png
主進程(node環境)與渲染進程(web環境)作用的環境不同,他們之間是隔離開的,web端不能使用nodeAPInode也無法使用webAPI

web端由於是沙箱環境,如果你想在web端獲取一些系統、環境、硬件的信息,可能就需要藉助node能力與系統交互,那麼electron就為我們提供了主進程與渲染進程之間的通信。

主進程與渲染進程之間通過預加載腳本通信,預加載腳本在渲染端(web環境)運行,雖然預加載腳本在web端運行,但是它可以訪問一部分的nodeAPI,預加載腳本就是主進程與渲染進程之間的橋樑。

根目錄新建預加載腳本:preload.js
image.png
這樣我們的目錄結構就一目瞭然了,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();
});

// 省略其它配置項...

啓動項目後會發現窗口打印了預加載腳本的輸出語句
image.png
應用的運行順序是:主進程 -> 預加載腳本 -> 渲染進程

現在我們來看預加載腳本如何與渲染進程通信的
使用 contextBridge 來選擇要從預加載腳本中暴露哪些 API,通過exposeInMainWorld向渲染器進程暴露一個全局的 window.abc變量。

// preload.js
const { contextBridge } = require("electron");

// 向渲染進程暴露全局myAPI變量
contextBridge.exposeInMainWorld("myAPI", {
  mytext: "這是暴露的變量,預加載進程可使用部分nodeAPI",
  version: process.version
});

然後在渲染進程中打印window
image.png
image.png
你會發現暴露的myAPI直接掛載在window身上
若是掛在在window身上,你可以直接獲取myAPI變量

btn.addEventListener("click", () => {
  //   alert("彈窗");
  //   console.log(window);
  console.log(myAPI);
});

image.png
這樣就做到了渲染進程訪問預加載腳本的能力。

進程通訊(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文件,內容為輸入框的內容。
image.png
image.png
image.png

渲染進程向主進程通信(雙向)

讀取我們之前寫入的hello.txt文件內容
image.png
在頁面中寫好結構

<!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();
});

image.png

打包應用

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/

user avatar aser1989 頭像
點贊 1 用戶, 點贊了這篇動態!
點贊

Add a new 評論

Some HTML is okay.