Electron一小时新手快速入门

一、Electron是什么

1、了解

Electron 是⼀个跨平台桌⾯应⽤开发框架,开发者可以使⽤:HTML、CSS、JavaScript 等 Web 技术来构建桌⾯应⽤程序,它的本质是结合了 Chromium 和 Node.js,现在⼴泛⽤于桌⾯应 ⽤程序开发。

2、技术架构

二、搭建一个demo

Electron官网

1、node.js和npm安装

出现版本号即安装。

js 复制代码
node -v
npm -v

我本地开发node版本是14,需要切换成较高版本,推荐使用nvm切换,我切换成20可以跑,可以参考这篇nvm安装详细教程(安装nvm、node、npm、cnpm、yarn及环境变量配置)-CSDN博客

2、项目初始化

项目目录

2.1、创建文件+初始化文件
js 复制代码
mkdir my-electron-app && cd my-electron-app
npm init

或者手动创建文件夹,之后进行初始化

2.2、安装electron
js 复制代码
npm install --save-dev electron
//或者
npm install electron -D
2.3、配置自动更新

就不用每次保存后都npm start了

js 复制代码
npm i nodemon -D

手动添加一个nodemon.json文件

js 复制代码
{
    "ignore": [ //忽略文件
        "node_modules",
        "dist"
    ],
    "restartable": "r", //按r更新
    "watch": ["*.*"],//监听所有文件
    "ext": "js,html,css,json"//包含
 
}
2.4、得到package.json文件,添加start
js 复制代码
{
  "name": "electron-app",
  "version": "1.0.0",
  "main": "main.js",
  "type": "pages",
  "devDependencies": {
    "electron": "^37.2.1",
    "electron-builder": "^26.0.12",
    "nodemon": "^3.1.10"
  },
  "scripts": {
    "start": "nodemon --exec electron .",
    "build": "electron-builder ",
    "pack": "electron-builder --dir"
  },
  "build": {
    "appId": "",
    "productName": "测试electron-dome",
    "directories": {
      "output": "dist"
    },
    "files": [
      "**/*",
      "!node_modules/**/{README.md,CHANGELOG.md,test,*.ts}"
    ],
    "win": {
      "icon": "./assets/images/logo2.png",
      "asar": true,
      "requestedExecutionLevel": "",
      "target": [
        {
          "target": "nsis",
          "arch": [
            "x64"
          ]
        }
      ]
    }
  },
  "nsis": {
    "oneClick": false,
    "perMachine": true,
    "allowToChangeInstallationDirectory": true
  },
  "dependencies": {
    "react": "^19.1.0",
    "react-dom": "^19.1.0"
  }
}
2.5、创建main.js
js 复制代码
console.log('Hello from Electron!')//在main.js中添加打印

之后运行

js 复制代码
npm start

终端出现打印内容,搭建成功

2.6、内容安全策略

运行之后,控制台有黄色提示文字,解决方法在index.html里加入代码

js 复制代码
<meta
    http-equiv="Content-Security-Policy"
    content="default-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self'  data: https://*; child-src 'none';" />
    <!-- self 同源  https://* 跨域     'none' 禁用所有子资源  'unsafe-inline' 允许内联样式 data: URI来嵌⼊图像。这种URI模式允许将图像数据直接嵌⼊到HTML或CSS中,⽽不是通过外部链接引⽤ -->

关于CSP的详细说明:内容安全策略详解

2.7、兼容mac和windows(适用性更好

mac关闭程序之后并没有直接退出应用,windows直接退出

为了兼容这点,在main.js添加代码段

js 复制代码
console.log("Hello from Electron!");
const { app, BrowserWindow } = require("electron");
function createWindow() {
    app.on("window-all-closed", () => {
        if (process.platform !== "darwin") {
            app.quit();
        }
    });
    app.on("activate", () => {
        if (BrowserWindow.getAllWindows().length === 0) {
            createWindow();
        }
    });
}
app.on("ready", () => {
  createWindow();
  const win = new BrowserWindow({
    // https://www.electronjs.org/zh/docs/latest/api/browser-window   一些相关api
    width: 800,
    height: 600,
    autoHideMenuBar: true, //隐藏菜单栏
    x: 100, //窗口位置
    y: 100, //窗口位置
    resizable: false, //禁止改变窗口大小
    webPreferences: {
      nodeIntegration: true,
    },
  });
  win.loadFile("./pages/index.html");
});
// 监听所有窗口关闭事件
app.on("window-all-closed", () => {
  // 如果所处平台不是mac(darwin),则退出应⽤。
  if (process.platform !== "darwin") app.quit();
});
// 监听mac下点击 dock 图标时触发
app.on("activate", () => {
  //如果当前应⽤没有窗⼝,则创建⼀个新的窗⼝
  if (BrowserWindow.getAllWindows().length === 0) createWindow();
});

3、制作程序

3.1、加载一个链接页面loadURL

在main.js写入

js 复制代码
console.log("Hello from Electron!");
const { app, BrowserWindow } = require("electron");
app.on("ready", () => {
  const win = new BrowserWindow({
    // https://www.electronjs.org/zh/docs/latest/api/browser-window   一些相关api
    width: 800,
    height: 600,
    autoHideMenuBar: true, //隐藏菜单栏
    x: 100, //窗口位置
    y: 100, //窗口位置
    resizable: false, //禁止改变窗口大小
    webPreferences: {
      nodeIntegration: true,
    },
  });
  //加载一个页面
  win.loadURL('https://www.baidu.com/')
});
3.2、加载自己的页面loadFile

改为win.loadFile("./pages/index.html"); 同时新建一个index.html

正常写前端页面即可

3.3、实现主进程与渲染进程之间的通信(IPC) 模组(重点理解

现在main.js与render.js相当于独立的两个模块,比如main.js中可以输出node中的变量process,比如:

js 复制代码
console.log(process.version); //打印node的版本号
console.log(process.versions.chrome);// 打印chrome的版本号
console.log(process.versions.electron);// 打印electron的版本号

而render.js中可以获取window的内容。

想让两者之间产生通信,所以就有"中间商"preload.js(顾名思义,预加载,最好还是用这个名字),预加载脚本在渲染器加载网页之前注入,但它仍是在渲染进程中运行的脚本,可以访问到渲染进程的全局对象,也可以访问Node.js的API。

3.3.0 引入预加载脚本preload.js

在main.js中添加webPreferences属性,设置preload属性为preload.js的路径

js 复制代码
const path = require("path");
....
 
preload: path.resolve(__dirname, "./preload.js"),//写绝对路径

main.js文件

js 复制代码
// 一、导入必要的 Electron 模块
// 二、定义创建窗口的函数
// 三、当 Electron 初始化完成后创建窗口
// 四、在窗口中加载 HTML 界面

// app:控制应用程序生命周期的模块
// BrowserWindow:用于创建和控制浏览器窗口的模块
// ipcMain:用于处理主进程与渲染进程之间通信(IPC)的模块
const { app, BrowserWindow, ipcMain } = require('electron');
const path = require("path");
const fs = require("fs"); // node API

// 1. 创建一个 800×600 像素的窗口
// 2. webPreferences.nodeIntegration: true 允许在渲染进程中使用 Node.js API
//    在 BrowserWindow 构造器中使用 webPreferences.preload 传入脚本的路径
//    __dirname 字符串指向当前正在执行的脚本的路径
//    path.join API 将多个路径联结在一起,创建一个跨平台的路径字符串
// 3. win.loadFile('index.html') 加载当前目录下的 index.html 文件作为窗口内容
const createWindow = () => {
  const win = new BrowserWindow({
    width: 800,
    height: 600,
    webPreferences: {
      nodeIntegration: true,
      // preload: path.join(__dirname, 'preload.js'),
      preload: path.resolve(__dirname, "preload.js"),
    },
  })


  ipcMain.on("save-file", (event, data) => {
    // 保存文件writeFileSync方法
    fs.writeFileSync("E://hello.txt", data);
  })

  // 使用 ipcMain.handle 设置一个主进程处理程序
  ipcMain.handle("read-file", () => {
    // 读取文件readFileSync方法,需要转成字符串
    return fs.readFileSync("E://hello.txt").toString();
  });

  // 加载本地的页面loadFile
  win.loadFile('./pages/dome/index.html');
  // 正确加载 HTML 文件
  // if (app.isPackaged) {
  //   // 生产环境路径
  //   win.loadFile(path.join(__dirname, './pages/index.html'))
  // } else {
  //   // 开发环境路径
  //   win.loadFile('./pages/index.html')
  // }

  if (!app.isPackaged) {
    win.webContents.openDevTools();
  }

  // 在加载文件loadFile之后设置一个定时器
  // setTimeout(() => {
  //   // 5秒后发送消息
  //   win.webContents.send("message", "测试"); // send对应on
  // }, 5000);

  // //加载一个页面
  // win.loadURL('https://www.baidu.com/')
}

// 启动应用:
// app.whenReady() 等待 Electron 完成初始化
// 当准备就绪后,调用 createWindow 函数创建窗口
app.whenReady().then(() => {
  createWindow();

  // 监听mac下点击 dock 图标时触发
  app.on('activate', () => {
    //如果当前应⽤没有窗⼝,则创建⼀个新的窗⼝
    if (BrowserWindow.getAllWindows().length === 0) {
      createWindow()
    }
  })
});

// 当所有窗口关闭时退出应用(mac 除外)
app.on('window-all-closed', () => {
  // 所处平台不是mac(darwin),则调用 app.quit() 来退出应用程序
  if (process.platform !== 'darwin') {
    app.quit();
  }
})
3.3.1、主进程←渲染进程

demo1:点击按钮,可以在D盘创建一个hello.txt文件,文件内容可自己输入

1.先在页面创建按钮及输入框元素,在renderjs中添加事件

index.html

js 复制代码
<input id="input1"></input>
<button id="btn2">现在我要往D盘hello.txt写入内容</button>

render.js

js 复制代码
// 渲染进程
const btn2 = document.getElementById('btn2');
const input1 = document.getElementById('input1');
 
btn2.onclick = () => {
  myData.saveFile(input1.value);
};

预加载脚本preload.js

js 复制代码
const { contextBridge, ipcRenderer } = require("electron");//引入"桥梁"
 
contextBridge.exposeInMainWorld("myData", {
    version: process.version, // 暴露process.versions对象,包含node和chrome版本号
    saveFile: (data) => { // 暴露saveFile方法,用于保存文件
        ipcRenderer.send("save-file", data); // 发送save-file事件,携带data参数
    }
})

主进程main.js

注意在加载文件loadFile之前要订阅消息

js 复制代码
const { app, BrowserWindow, ipcMain } = require("electron");
const fs = require("fs");//node API
...
ipcMain.on("save-file", (event, data) => {
    // 保存文件writeFileSync方法
    fs.writeFileSync("D://hello.txt", data);
  });
win.loadFile("./pages/index.html");

这样就实现了

3.3.2、主进程→(双向)←渲染进程

demo2:点击按钮,可以在D盘创建一个hello.txt文件,文件内容可自己输入,同时可以实现读取(点击按钮,可以将文件内容写入文本框

1.先在页面创建按钮及输入框元素,在renderjs中添加事件

index.html

js 复制代码
<input id="input1"></input>
<button id="btn2">现在我要往D盘hello.txt写入内容</button>
<button id="btn3">读取hello.txt内容</button>

render.js

js 复制代码
// 渲染进程
const btn2 = document.getElementById('btn2');
const input1 = document.getElementById('input1');
const btn3 = document.getElementById('btn3');
 
btn2.onclick = () => {
  myData.saveFile(input1.value);
};
btn3.onclick = () => {
  // 读取内容,然后写入到input1中
  myData.readFile().then((data) => {
    input1.value = data;
  });
};

预加载脚本preload.js

js 复制代码
const { contextBridge, ipcRenderer } = require("electron");//引入"桥梁"
 
contextBridge.exposeInMainWorld("myData", {
    version: process.version, // 暴露process.versions对象,包含node和chrome版本号
    saveFile: (data) => { // 暴露saveFile方法,用于保存文件
        ipcRenderer.send("save-file", data); // 发送save-file事件,携带data参数
    },
    readFile(){ // 暴露readFile方法,用于读取文件
        return ipcRenderer.invoke("read-file"); // 发送read-file事件,不携带参数
    }
})

主进程main.js

注意在加载文件loadFile之前要订阅消息

js 复制代码
const { app, BrowserWindow, ipcMain } = require("electron");
const fs = require("fs");//node API
...
ipcMain.on("save-file", (event, data) => { //send对应on
    // 保存文件writeFileSync方法
    fs.writeFileSync("D://hello.txt", data);
  });
ipcMain.handle("read-file", () => { //invoke对应handle,返回值是一个promise
    // 保存文件
    return fs.readFileSync("D://hello.txt").toString();//需要转成字符串
  });
win.loadFile("./pages/index.html");

这样就实现了

3.3.3、主进程→渲染进程

demo3:应用加载6s后,让渲染进程弹出"测试" 主进程main.js

在加载文件loadFile之后设置一个定时器

js 复制代码
...
  win.loadFile("./pages/index.html");
  setTimeout(() => {
    // 发送消息
    win.webContents.send("message", "测试"); //send对应on
  }, 6000);

预加载脚本preload.js

js 复制代码
const { contextBridge, ipcRenderer } = require("electron");//引入"桥梁"
 
contextBridge.exposeInMainWorld("myData", {
    ...
    getMessage: (callback) => { // 暴露getMessage方法,用于获取消息
        return ipcRenderer.on("message", callback);
    }
})

render.js

js 复制代码
// 渲染进程
const btn2 = document.getElementById('btn2');
const input1 = document.getElementById('input1');
const btn3 = document.getElementById('btn3');
 
btn2.onclick = () => {
  myData.saveFile(input1.value);
};
btn3.onclick = () => {
  // 读取内容,然后写入到input1中
  myData.readFile().then((data) => {
    input1.value = data;
  });
};
//新增
window.onload = () => {
  myData.getMessage((event, str) => { // 接收message事件,携带data参数
    alert(str); // 弹出data
  });
};

这样就实现了

4、打包程序

4.1 electron-vite

官网地址:electron-vite | 下一代 Electron 开发构建工具

js 复制代码
npm i electron-vite -D

electron-vite 是 Vue + Vite 项目的最佳选择,兼顾开发效率与现代化工具链。

4.2 electron-builder
js 复制代码
npm install electron-builder -D
针对 Vue 项目的打包工具推荐
场景分析
  • 你的项目基于 Vue,可能已使用 Vite 或 Webpack 构建。若希望保留现有工具链,优先选择与 Vite 集成的方案。
  • 若项目需深度系统集成(如硬件调用),需权衡性能与开发效率。

推荐方案

1. 优先选择 electron-vite

理由:

  • 天然适配 Vite 项目,支持 Vue 3 的热更新和按需编译,开发体验流畅。
  • 统一配置简化多进程管理,适合中小型项目快速迁移到 Electron。

适用场景:Vue 3 + Vite 技术栈,需快速迭代和轻量打包。

2. 复杂项目选 electron-builder

理由:

  • 支持多平台安装包生成(如 NSIS、DMG),适合企业级分发需求。
  • 可通过 Webpack 优化依赖,灵活控制打包体积。

适用场景:需定制安装流程、自动更新或签名机制的大型项目。

相关推荐
mogullzr1 分钟前
4.1.ByteOJ用户模块——登录注册功能(RSA + TimeStamp加密过)
前端·后端
鹏多多.3 分钟前
flutter-使用AnimatedDefaultTextStyle实现文本动画
android·前端·css·flutter·ios·html5·web
卑微前端在线挨打1 小时前
2025数字马力一面面经(社)
前端
OpenTiny社区1 小时前
一文解读“Performance面板”前端性能优化工具基础用法!
前端·性能优化·opentiny
拾光拾趣录2 小时前
🔥FormData+Ajax组合拳,居然现在还用这种原始方式?💥
前端·面试
不会笑的卡哇伊2 小时前
新手必看!帮你踩坑h5的微信生态~
前端·javascript
bysking2 小时前
【28 - 记住上一个页面tab】实现一个记住用户上次点击的tab,上次搜索过的数据 bysking
前端·javascript
Dream耀2 小时前
跨域问题解析:从同源策略到JSONP与CORS
前端·javascript
前端布鲁伊2 小时前
【前端高频面试题】面试官: localhost 和 127.0.0.1有什么区别
前端
HANK2 小时前
Electron + Vue3 桌面应用开发实战指南
前端·vue.js