学习记录:DAY32

Electron 开发之旅:从入门到实践

前言


接续上一篇 blog,这篇的内容主要和 Electron 有关。

课设不是特别想做下去了,实际核心代码大概只有 3,4 百行左右,比较水......

或许会把 Docker 的部署也做一做(权当是练习了)。

日程


现在是晚上 7 点 40 分,希望 9 点之前能把应用打包好,这样今天还能匀点时间用来复习。

困困困困困困困困困困困困困困困困困困

Electron 的水比我想象的深,搞不懂所以用 bat 了

过 0 点了,都不睡觉是吧,我和你们爆了😡😭😡😭

学习内容


《省流》

  1. Electron 安装与入门
  2. 预加载脚本与进程通信
  3. 打包应用
  4. bat 脚本启动前后端

1. Electron 安装与入门

先放个官方的安装教程:安装指导 | Electron

基本上参照官方的教程安装即可。因为 Electron 的安装过程分为两部分:

  • 下载 npm 包 (镜像)
  • 下载 Electron 二进制文件(GitHub 下载)

所以国服玩家最好设置一下国内的镜像源:

bash 复制代码
# 设置淘宝镜像源
npm config set registry https://registry.npmmirror.com

# 从国内镜像下载二进制包
npm install electron --save-dev --electron-mirror=https://npmmirror.com/mirrors/electron/

接着是入门程序:

官方 api 文档:app | Electron

javascript 复制代码
// main.js
const { app, BrowserWindow } = require('electron')
function createWindow() {
    const win = new BrowserWindow({ // 创建窗口对象
        width: 800,
        height: 600,
        autoHideMenuBar: true, // 隐藏菜单栏
    })
    win.loadURL('https://www.github.com') // 加载网页
}
app.on('ready', () => { // 启动钩子
    createWindow()
})

如果要启动本地的网页资源:

javascript 复制代码
win.loadFile('./pages/index.html')

通常还需要设置内容安全策略来控制资源的访问:

html 复制代码
<!-- index.html -->
<meta
    http-equiv="X-Content-Security-Policy"
    content="default-src 'self'; script-src 'self'"
/>

关于 CSP 的详细说明请参考:内容安全策略(CSP) - HTTP | MDN

窗口默认行为:

javascript 复制代码
// windows 习惯:关闭所有窗口后退出程序
app.on('window-all-closed', () => {
    if (process.platform !== 'darwin') app.quit()
})
// Mac 习惯
app.on('activate', () => {
    if (BrowserWindow.getAllWindows().length === 0) createWindow()
})

配置自动重启:

这个能保证在编写代码后 electron 应用能自动更新

bash 复制代码
# 安装 nodemon
npm i nodemon -D

# 配置 nodemon.json
{
    "ignore": [
        "node_modules",
        "dist"
    ],
    "restartable": "r",
    "watch": ["*.*"],
    "ext": "html,js,css",
    "delay": 10000
}

# 配置 package.json
"start": "nodemon --exec electron .",

2. 预加载脚本与进程通信

首先要了解 Electron 的流程模型:

由一个入口 主进程(main.js)和多个渲染进程(render.js [即 index.html] 的 js 脚本)组成

预加载脚本(preload.js),作为沟通两者的桥梁

javascript 复制代码
// preload.js
const { contextBridge } = require('electron')
contextBridge.exposeInMainWorld('myAPI', { // 形成全局上下文键值对
    version: process.version,
})

在 main.js 中声明

javascript 复制代码
const path = require('path')
function createWindow() {
    const win = new BrowserWindow({ 
        ...
        webPreferences: {
            preload: path.resolve(__dirname, "./preload.js")
        }
    })
}

在 render.js 中调用

javascript 复制代码
const btn1 = document.getElementById('btn1')
btn1.onclick = () => {
    alert(myAPI.version)
}

进程通信(IPC)是 Electron 中的一个重要概念,实现进程之间的通信

1)单向通信

在 main.js 中导入 ipc 模块:

javascript 复制代码
const { ipcMain } = require('electron')
function createWindow() {
    .....
    ipcMain.on('file-save', writeFile)
}
function writeFile(_, data) {
    fs.writeFileSync(
        path.join(__dirname, 'output.txt'),
        data
    )
}

在 preload.js 中设置信道方法:

javascript 复制代码
const { contextBridge, ipcRenderer } = require('electron')
contextBridge.exposeInMainWorld('myAPI', {
    version: process.version,
    saveFile: (data) => {
        ipcRenderer.send('file-save', data)
    }
})

在 render.js 中触发方法,将信息 单向 向主进程通信

javascript 复制代码
btn2.onclick = () => {
    myAPI.saveFile(input.value)
}

2)双向通信

实现原理和单向类似,只是方法细节上有所区别

javascript 复制代码
// main.js
ipcMain.handle('file-read', readFile)
....
function readFile() {
    return fs.readFileSync(
        path.join(__dirname, 'output.txt')
    ).toString()
}

// preload.js
readFile: () => {
    return ipcRenderer.invoke('file-read')
}

// render.js
btn3.onclick = async () => {
    let data = await myAPI.readFile()
    alert(data)
}

!需要注意一个 axios 上的细节:

Electron 的进程间通信是异步的

invoke() 设计就是返回 Promise 来处理这种异步性

Promise 链延续:

  • 如果不 return,Promise 链会在 preload 层中断
  • 加了 return 才能让 Promise 链延续到渲染进程

实际调用示例对比

能正常工作的版本:

javascript 复制代码
// preload.js
contextBridge.exposeInMainWorld('myAPI', {
    readFile: () => {
        return ipcRenderer.invoke('file-read') // 有 return
    }
})
// 渲染进程
const data = await myAPI.readFile() // 能获取到数据

不能工作的版本:

javascript 复制代码
// preload.js
contextBridge.exposeInMainWorld('myAPI', {
    readFile: () => {
        ipcRenderer.invoke('file-read') // 无 return
    }
})
// 渲染进程
const data = await myAPI.readFile() // data 将是 undefined

建议:永远 return IPC 调用的结果:无论是 invoke() 还是 send() 的响应

3. 打包应用

这里用到的打包方法是 electron-builder,配置麻烦,但是自由度比较高

bash 复制代码
# 安装 electron-builder
npm install electron-builder -D

完整 package.json

json 复制代码
"build": {
    // 应用程序的唯一标识符(反向域名格式)
    "appId": "com.kacat.testapp",
    
    // Windows 平台特定配置
    "win": {
        // 应用程序图标的路径(建议使用 256x256 像素的.ico文件)
        "icon": "./logo.ico",
        
        // 构建目标配置
        "target": [
            {
                // 使用 NSIS 制作安装程序(.exe)
                "target": "nsis",
                // 目标架构(仅 x64 位)
                "arch": ["x64"]
            }
        ]
    },
    
    // NSIS 安装程序配置
    "nsis": {
        // 是否一键安装(false 表示显示安装向导)
        "oneClick": false,
        // 是否为所有用户安装
        "perMachine": true,
        // 是否允许用户更改安装目录
        "allowToChangeInstallationDirectory": true
    },
    
    // Electron 下载镜像配置(使用国内淘宝镜像加速下载)
    "electronDownload": {
        "mirror": "https://npmmirror.com/mirrors/electron/"
    }
}
bash 复制代码
# 执行打包
npm run build

4. bat 脚本启动前后端

bash 复制代码
@echo off
chcp 65001 > nul
title kat-watermarking

set JAR_PATH=kat-watermarking.jar
set NGINX_PATH=nginx
set NGINX_EXEC=nginx.exe
set NGINX_URL=http://localhost:10086
set JDK_DIR=jdk-17.0.2
set JAVA_CMD="%JDK_DIR%\bin\java"
if errorlevel 1 (
    echo 错误: bat 配置有误,请检查
    pause
    exit /b 1
)

echo 正在启动 Java 应用程序...
start "JavaApp" /min cmd /c "%JAVA_CMD% -jar %JAR_PATH% && pause"
if errorlevel 1 (
    echo 错误: 启动 Java 应用程序失败
    pause
    exit /b 1
)

echo 正在启动 Nginx 服务器...

tasklist /fi "imagename eq nginx.exe" | find /i "nginx.exe" > nul
if not errorlevel 1 (
    echo 检测到已有 Nginx 正在运行,正在终止旧进程...
    taskkill /f /im nginx.exe > nul 2>&1
    timeout /t 1 /nobreak > nul
)

cd "%NGINX_PATH%"
start "" "%NGINX_EXEC%"
if errorlevel 1 (
    echo 错误: 启动 Nginx 服务器失败
    pause
    exit /b 1
)
cd ..

timeout /t 2 /nobreak > nul 

echo 正在打开 Nginx 网页...
start "" "%NGINX_URL%"
if errorlevel 1 (
    echo 警告: 无法打开浏览器,请手动访问 %NGINX_URL%
)

echo 所有服务已启动完成!
pause

taskkill /f /im nginx.exe > nul 2>&1

exit /b 0

其实没有什么技术含量,挑一下有用的讲讲吧:

  • @echo off 关闭命令本身的回显,只显示输出结果(即 echo 指明的字段)
  • chcp 65001 设置控制台字符集为 utf-8
  • > nul 重定向输出到 null(即不显示输出)
  • if errorlevel 1 () 检查上一个命令的退出代码, >=1 为执行错误
  • tasklist /fi "字段 运算符 值" 列出进程列表并条件过滤
  • | 管道符,用于将一个命令的输出传递给另一个命令作为输入
  • find /i "字符串" 查找字符串,不区分大小写
  • taskkill /f /im nginx.exe /f 强制终止 /im 按进程名称终止
  • timeout /t 1 /nobreak 暂停脚本执行 1 秒钟 /nobreak 禁止用户按键跳过等待

结语


事实证明困距离不得不睡觉有很长一段距离。

如果能习惯这个感觉还能正常工作,🤤

相关推荐
西岸行者5 天前
学习笔记:SKILLS 能帮助更好的vibe coding
笔记·学习
悠哉悠哉愿意5 天前
【单片机学习笔记】串口、超声波、NE555的同时使用
笔记·单片机·学习
别催小唐敲代码5 天前
嵌入式学习路线
学习
毛小茛5 天前
计算机系统概论——校验码
学习
babe小鑫5 天前
大专经济信息管理专业学习数据分析的必要性
学习·数据挖掘·数据分析
winfreedoms5 天前
ROS2知识大白话
笔记·学习·ros2
在这habit之下5 天前
Linux Virtual Server(LVS)学习总结
linux·学习·lvs
我想我不够好。5 天前
2026.2.25监控学习
学习
im_AMBER5 天前
Leetcode 127 删除有序数组中的重复项 | 删除有序数组中的重复项 II
数据结构·学习·算法·leetcode
CodeJourney_J5 天前
从“Hello World“ 开始 C++
c语言·c++·学习