扫码加入

  • 正文
  • 相关推荐
申请入驻 产业图谱

Electron:看这一篇就够了

14小时前
179
加入交流群
扫码加入
获取工程师必备礼包
参与热点资讯讨论

在前端技术迅猛发展的今天,JavaScript 不再只是浏览器中的脚本语言,它正在一步步打破平台的边界,深入各类系统开发。而 Electron,作为一个将 Web 技术与桌面开发完美融合的框架,正成为众多开发者打造跨平台桌面应用的不二选择。

本篇《Electron:看这一篇就够了》,旨在为你梳理 Electron 的核心理念、技术结构与实战要点。从架构原理到主渲染进程,从 IPC 通信到自动打包发布,力求一文读懂、直达实用,助你从 Web 跨入桌面,快速上手 Electron 开发。

无论你是前端工程师、全栈开发者,还是桌面开发的新探索者,相信这一篇文章,都能成为你走进 Electron 世界的最佳起点。

如果你还在寻找学习 Electron 的理由,AI 编辑器 Cursor 就是一个足够有说服力的答案 —— 它就是用 Electron 构建的。

在学习electron之前,需要具备JS的相关知识,可先阅读以下文档:

https://blog.csdn.net/weixin_43431593/article/details/156149817

一、快速开始

1、开发环境Cursor

cursor:看这一篇就够了_cursor pycharm-CSDN博客

2、搭建开发环境与示例应用开发

3、打包分发应用

4、Electron与Cursor

5、结合bootstrap5 (可选UI组件)

Bootstrap5:看这一篇就够了-CSDN博客

6、main.js解析

const { app, BrowserWindow } = require('electron')const path = require('path')function createWindow () {  const win = new BrowserWindow({    width: 900,    height: 700,    webPreferences: {      nodeIntegration: false,      contextIsolation: true,      preload: path.join(__dirname, 'preload.js')    }  })  // 加载 index.html  win.loadFile('index.html')  // 开发时打开开发者工具  // win.webContents.openDevTools()}app.whenReady().then(() => {  createWindow()  app.on('activate', () => {    if (BrowserWindow.getAllWindows().length === 0) {      createWindow()    }  })})app.on('window-all-closed', () => {  if (process.platform !== 'darwin') {    app.quit()  }}) 

流程大概是这样:

其中,electron模块:主进程核心模块app:控制应用程序生命周期 BrowserWindow:创建和控制浏览器窗口path模块:Node.js 内置模块,用于处理文件路径。

7、index.html解析

<!DOCTYPE html><html><head>    <meta charset="UTF-8">    <meta name="viewport" content="width=device-width, initial-scale=1">    <title>Electron 应用</title>    <link href="node_modules/bootstrap/dist/css/bootstrap.min.css" rel="stylesheet">    <style>        body {            background-color: #f8f9fa;        }        .version-card {            transition: transform 0.2s;        }        .version-card:hover {            transform: translateY(-5px);        }    </style></head><body>    <nav >        <div >            <a  href="#">Electron Study</a>        </div>    </nav>    <div >        <div >            <div >                <div >                    <div >                        <h1 >欢迎使用 Electron!</h1>                                                <div >                            <div >                                <div >                                    <div >                                        <h5 >Node.js</h5>                                        <p  id="node-version">加载中...</p>                                    </div>                                </div>                            </div>                            <div >                                <div >                                    <div >                                        <h5 >Chrome</h5>                                        <p  id="chrome-version">加载中...</p>                                    </div>                                </div>                            </div>                            <div >                                <div >                                    <div >                                        <h5 >Electron</h5>                                        <p  id="electron-version">加载中...</p>                                    </div>                                </div>                            </div>                        </div>                        <div >                            <button  type="button" data-bs-toggle="collapse" data-bs-target="#systemInfo">                                显示系统信息                            </button>                        </div>                        <div  id="systemInfo">                            <div >                                <h5 >系统信息</h5>                                <p >这是一个基于 Electron 和 Bootstrap 5 构建的现代化桌面应用。</p>                            </div>                        </div>                    </div>                </div>            </div>        </div>    </div>    <script src="node_modules/@popperjs/core/dist/umd/popper.min.js"></script>    <script src="node_modules/bootstrap/dist/js/bootstrap.min.js"></script>    <script>        window.addEventListener('DOMContentLoaded', () => {            const replaceText = (selector, text) => {                const element = document.getElementById(selector)                if (element) element.innerText = text            }            for (const type of ['chrome', 'node', 'electron']) {                replaceText(`${type}-version`, process.versions[type])            }        })    </script></body></html> 

如图,有一些功能在html没有显示的,因为它们是应用自带的 ,当然可以自定义。我们在后续的学习中,会接触到。

8、配置debug环境

9、 常见问题解决

(1)替换图标的方法

需要在网站生成ico图标

Online ICO converter

需要在 package.json中配置构建引用的图标

运行时使用的图标

(2)处理控制台输出中文乱码问题

"start": "set NODE_OPTIONS=--no-warnings --trace-warnings && chcp 65001 && electron .",

二、常用模块

1、Electron 模块

1. app - 应用程序生命周期const { app } = require('electron');// 核心功能app.on('ready', () => {  // 应用初始化完成  console.log('Electron 应用已就绪');});app.on('window-all-closed', () => {  // 所有窗口关闭  if (process.platform !== 'darwin') {    app.quit();  // 非 macOS 直接退出  }});app.on('activate', () => {  // macOS 点击 Dock 图标  if (BrowserWindow.getAllWindows().length === 0) {    createWindow();  // 重新创建窗口  }});// 路径管理const userDataPath = app.getPath('userData');  // 用户数据目录const tempPath = app.getPath('temp');         // 临时目录const desktopPath = app.getPath('desktop');   // 桌面目录// 应用信息app.setName('MyApp');      // 设置应用名称const appVersion = app.getVersion();  // 获取应用版本const appName = app.getName();        // 获取应用名称// 单实例锁const gotTheLock = app.requestSingleInstanceLock();if (!gotTheLock) {  app.quit();  // 已有一个实例在运行}底层原理:app 模块是 Electron 主进程的入口点,它:• 与操作系统原生应用程序 API 交互• 管理应用生命周期(启动、退出、激活)• 维护路径缓存映射表• 处理命令行参数解析• 实现单实例锁机制(通过文件锁或命名管道)2. BrowserWindow - 创建和管理窗口const { BrowserWindow } = require('electron');// 创建窗口const mainWindow = new BrowserWindow({  width: 800,  height: 600,  webPreferences: {    nodeIntegration: true,        // 启用 Node.js    contextIsolation: false,      // 禁用上下文隔离    preload: path.join(__dirname, 'preload.js')  // 预加载脚本  }});// 窗口事件mainWindow.on('closed', () => {  // 窗口关闭  mainWindow = null;});mainWindow.on('focus', () => {  // 窗口获得焦点  console.log('窗口获得焦点');});// 窗口控制mainWindow.loadURL('https://electronjs.org');  // 加载 URLmainWindow.loadFile('index.html');             // 加载本地文件mainWindow.webContents.openDevTools();         // 打开开发者工具mainWindow.maximize();  // 最大化mainWindow.minimize();  // 最小化mainWindow.restore();   // 恢复mainWindow.close();     // 关闭// 窗口信息const bounds = mainWindow.getBounds();  // 获取窗口位置和大小const isMaximized = mainWindow.isMaximized();  // 是否最大化底层架构:├── NativeWindow (C++层)│   ├── Windows: HWND│   ├── macOS: NSWindow│   └── Linux: GtkWindow├── WebContents (网页内容)│   ├── Chromium渲染进程│   ├── 页面加载器│   └── DevTools└── 窗口装饰    ├── 标题栏    ├── 边框    └── 控制按钮3. Menu - 应用程序菜单const { Menu, MenuItem } = require('electron');// 应用菜单模板const template = [  {    label: '文件',    submenu: [      {        label: '打开',        accelerator: 'CmdOrCtrl+O',  // 快捷键        click: () => { console.log('打开文件'); }      },      { type: 'separator' },  // 分隔线      { label: '退出', role: 'quit' }  // 内置角色    ]  },  {    label: '编辑',    submenu: [      { label: '撤销', role: 'undo' },      { label: '重做', role: 'redo' },      { type: 'separator' },      { label: '剪切', role: 'cut' },      { label: '复制', role: 'copy' },      { label: '粘贴', role: 'paste' }    ]  }];// 创建菜单const menu = Menu.buildFromTemplate(template);Menu.setApplicationMenu(menu);  // 设置应用菜单// 上下文菜单const contextMenu = Menu.buildFromTemplate([  { label: '复制', click: () => {} },  { label: '粘贴', click: () => {} }]);// 显示上下文菜单window.addEventListener('contextmenu', (e) => {  e.preventDefault();  contextMenu.popup();});操作系统适配:// 平台特定的菜单if (process.platform === 'darwin') {  // macOS  template.unshift({  // 添加 macOS 的应用菜单    label: app.getName(),    submenu: [      { role: 'about' },      { type: 'separator' },      { role: 'services' },      { type: 'separator' },      { role: 'hide' },      { role: 'hideothers' },      { role: 'unhide' },      { type: 'separator' },      { role: 'quit' }    ]  });}4. dialog - 原生对话框const { dialog } = require('electron');// 1. 消息对话框dialog.showMessageBox(mainWindow, {  type: 'info',  // none/info/error/question/warning  title: '提示',  message: '操作成功',  detail: '文件已保存',  buttons: ['确定', '取消'],  defaultId: 0,  // 默认按钮索引  cancelId: 1    // 取消按钮索引}).then(result => {  console.log('点击了按钮:', result.response);});// 2. 文件对话框dialog.showOpenDialog({  title: '选择文件',  defaultPath: app.getPath('documents'),  buttonLabel: '选择',  filters: [    { name: '图片', extensions: ['jpg', 'png', 'gif'] },    { name: '所有文件', extensions: ['*'] }  ],  properties: ['openFile', 'multiSelections']  // 多选}).then(result => {  if (!result.canceled) {    console.log('选择的文件:', result.filePaths);  }});// 3. 保存对话框dialog.showSaveDialog({  title: '保存文件',  defaultPath: path.join(app.getPath('documents'), '未命名.txt'),  filters: [{ name: '文本文件', extensions: ['txt'] }]}).then(result => {  if (!result.canceled) {    fs.writeFileSync(result.filePath, '文件内容');  }});系统原生调用链:Electron dialog API  ↓ IPC主进程 dialog 模块  ↓调用操作系统原生 API:  - Windows: IFileDialog (COM 组件)  - macOS: NSSavePanel/NSPanel  - Linux: GtkFileChooser/GtkMessageDialog5. ipcMain - 主进程通信const { ipcMain } = require('electron');// 1. 监听渲染进程消息ipcMain.on('message-from-renderer', (event, data) => {  console.log('收到消息:', data);    // 回复消息  event.reply('reply-to-renderer', { result: 'ok' });  // 或  event.sender.send('reply-to-renderer', { result: 'ok' });});// 2. 同步通信ipcMain.on('sync-message', (event, data) => {  const result = processData(data);  event.returnValue = result;  // 同步返回});// 3. 一次性监听ipcMain.once('init-complete', (event) => {  console.log('初始化完成');});// 4. 处理异步操作ipcMain.handle('read-file', async (event, filePath) => {  try {    const data = await fs.promises.readFile(filePath, 'utf8');    return { success: true, data };  } catch (error) {    return { success: false, error: error.message };  }});底层 IPC 架构:主进程 (Node.js) ↔ 渲染进程 (Chromium)    ↓                       ↓ipcMain                 ipcRenderer    ↓                       ↓IPC 通道 (MessageChannel)    ↓进程间通信 (IPC)    ├── Windows: 命名管道/LPC    ├── macOS: Mach Ports    └── Linux: Unix Domain Sockets6. shell - 系统外壳集成const { shell } = require('electron');// 1. 打开外部应用/URLshell.openExternal('https://electronjs.org');  // 浏览器打开shell.openPath('/Users/name/Documents');        // 文件管理器打开// 2. 在文件管理器中显示shell.showItemInFolder('/path/to/file.txt');// 3. 回收站操作shell.trashItem('/path/to/file.txt').then(() => {  console.log('文件已移动到回收站');});// 4. 剪切板操作const currentClipboard = shell.readText();  // 读取文本shell.writeText('要复制的文本');           // 写入文本// 5. 桌面集成shell.beep();  // 系统提示音系统调用映射:// Windowsshell.openExternal('...')  // ShellExecuteWshell.showItemInFolder('...')  // SHOpenFolderAndSelectItems// macOSshell.openExternal('...')  // [[NSWorkspace sharedWorkspace] openURL:]shell.showItemInFolder('...')  // [[NSWorkspace sharedWorkspace] selectFile:...]// Linuxshell.openExternal('...')  // gtk_show_uri / xdg-open

2、Node.js 核心模块

1. fs - 文件系统const fs = require('fs');const fsPromises = fs.promises;  // Promise API// 1. 同步操作(阻塞)try {  const data = fs.readFileSync('file.txt', 'utf8');  fs.writeFileSync('output.txt', data);  fs.appendFileSync('log.txt', 'n新的内容');} catch (error) {  console.error('文件操作失败:', error);}// 2. 异步操作(回调)fs.readFile('file.txt', 'utf8', (err, data) => {  if (err) throw err;  console.log(data);});// 3. Promise API(推荐)async function processFile() {  try {    const stats = await fsPromises.stat('file.txt');    console.log('文件大小:', stats.size);    console.log('修改时间:', stats.mtime);        if (stats.isFile()) {      const data = await fsPromises.readFile('file.txt', 'utf8');      console.log('文件内容:', data);    }  } catch (error) {    console.error('错误:', error);  }}// 4. 流式操作(大文件)const readStream = fs.createReadStream('largefile.txt', {  encoding: 'utf8',  highWaterMark: 64 * 1024  // 64KB 缓冲区});const writeStream = fs.createWriteStream('copy.txt');readStream.on('data', (chunk) => {  console.log('读取到', chunk.length, '字节');  writeStream.write(chunk);});readStream.on('end', () => {  writeStream.end();  console.log('文件复制完成');});// 5. 文件监视const watcher = fs.watch('file.txt', (eventType, filename) => {  console.log(`${filename} 发生了 ${eventType} 事件`);});// 6. 目录操作fsPromises.mkdir('new-folder', { recursive: true });  // 创建目录const files = fsPromises.readdir('.');  // 读取目录fsPromises.rmdir('empty-folder');  // 删除目录文件系统底层:Node.js fs 模块  ↓libuv 文件系统操作  ↓操作系统文件系统 API:  - Windows: Win32 API (CreateFile, ReadFile, WriteFile)  - Unix: POSIX API (open, read, write, stat)  ↓文件系统驱动程序  ↓磁盘 I/O2. path - 路径处理const path = require('path');// 1. 路径解析const fullPath = path.resolve('src', 'app.js');  // 解析为绝对路径const relativePath = path.relative('/data/orandea', '/data/orandea/test/aaa');// 2. 路径拼接const joinedPath = path.join(__dirname, 'assets', 'image.png');// 3. 路径信息const filePath = '/home/user/project/src/app.js';console.log(path.dirname(filePath));   // /home/user/project/srcconsole.log(path.basename(filePath));  // app.jsconsole.log(path.extname(filePath));   // .jsconsole.log(path.basename(filePath, '.js'));  // app// 4. 路径解析对象const parsed = path.parse(filePath);// {//   root: '/',//   dir: '/home/user/project/src',//   base: 'app.js',//   ext: '.js',//   name: 'app'// }// 5. 平台差异处理console.log(path.sep);  // Windows: "", Unix: "/"console.log(path.delimiter);  // Windows: ";", Unix: ":"// 6. 路径规范化console.log(path.normalize('/foo/bar//baz/asdf/quux/..'));  // /foo/bar/baz/asdf路径解析算法:// path.resolve 的实现思路function resolve(...paths) {  let resolvedPath = '';  let resolvedAbsolute = false;    for (let i = paths.length - 1; i >= 0; i--) {    const path = paths[i];        // 跳过空路径    if (path.length === 0) continue;        resolvedPath = path + '/' + resolvedPath;    resolvedAbsolute = path.charAt(0) === '/';        if (resolvedAbsolute) {      break;    }  }    // 规范化路径  resolvedPath = normalizeString(resolvedPath, !resolvedAbsolute);    if (resolvedAbsolute) {    return '/' + resolvedPath;  }    return resolvedPath.length > 0 ? resolvedPath : '.';}3. os - 操作系统信息const os = require('os');// 1. 系统信息console.log('操作系统平台:', os.platform());  // win32, darwin, linuxconsole.log('系统架构:', os.arch());         // x64, arm, ia32console.log('系统版本:', os.version());      // Windows 10, Darwin 20.6.0console.log('主机名:', os.hostname());       // 计算机名称console.log('临时目录:', os.tmpdir());       // 临时文件目录// 2. 内存信息console.log('总内存:', formatBytes(os.totalmem()));console.log('可用内存:', formatBytes(os.freemem()));console.log('内存使用率:', (1 - os.freemem() / os.totalmem()) * 100 + '%');function formatBytes(bytes) {  const units = ['B', 'KB', 'MB', 'GB', 'TB'];  let i = 0;  while (bytes >= 1024 && i < units.length - 1) {    bytes /= 1024;    i++;  }  return bytes.toFixed(2) + ' ' + units[i];}// 3. CPU 信息const cpus = os.cpus();console.log('CPU 核心数:', cpus.length);cpus.forEach((cpu, i) => {  console.log(`CPU ${i}: ${cpu.model} - 速度: ${cpu.speed}MHz`);});// 4. 网络信息const networkInterfaces = os.networkInterfaces();for (const [name, interfaces] of Object.entries(networkInterfaces)) {  interfaces.forEach(iface => {    if (iface.family === 'IPv4' && !iface.internal) {      console.log(`接口 ${name}: ${iface.address}`);    }  });}// 5. 用户信息console.log('当前用户:', os.userInfo().username);console.log('家目录:', os.homedir());底层系统调用:// 不同平台的实现switch (process.platform) {  case 'win32':    // 使用 Windows API: GetSystemInfo, GlobalMemoryStatusEx    break;  case 'darwin':    // 使用 sysctl, sysctlbyname    break;  case 'linux':    // 读取 /proc 文件系统    // /proc/meminfo, /proc/cpuinfo, /proc/self/stat    break;}

3、模块协同工作示例

const { app, BrowserWindow, dialog, ipcMain, shell } = require('electron');const fs = require('fs').promises;const path = require('path');const os = require('os');class FileManager {  constructor() {    this.currentDir = os.homedir();  }    // 打开文件选择对话框  async openFileDialog() {    const { canceled, filePaths } = await dialog.showOpenDialog({      title: '选择文件',      defaultPath: this.currentDir,      properties: ['openFile', 'multiSelections']    });        if (!canceled && filePaths.length > 0) {      this.currentDir = path.dirname(filePaths[0]);      return await this.readFiles(filePaths);    }    return [];  }    // 读取多个文件  async readFiles(filePaths) {    const files = [];        for (const filePath of filePaths) {      try {        const stats = await fs.stat(filePath);                if (stats.isFile()) {          const content = await fs.readFile(filePath, 'utf8');          files.push({            name: path.basename(filePath),            path: filePath,            size: stats.size,            modified: stats.mtime,            content: content.substring(0, 1000)  // 前1000字符          });        }      } catch (error) {        console.error(`读取文件失败 ${filePath}:`, error);      }    }        return files;  }    // 保存文件  async saveFile(content, defaultPath) {    const { canceled, filePath } = await dialog.showSaveDialog({      title: '保存文件',      defaultPath: defaultPath || path.join(this.currentDir, '未命名.txt'),      filters: [        { name: '文本文件', extensions: ['txt', 'md'] },        { name: '所有文件', extensions: ['*'] }      ]    });        if (!canceled && filePath) {      await fs.writeFile(filePath, content, 'utf8');            // 在文件管理器中显示      if (process.platform !== 'darwin') {        shell.showItemInFolder(filePath);      }            return filePath;    }    return null;  }}// 应用初始化app.whenReady().then(() => {  const fileManager = new FileManager();  const mainWindow = new BrowserWindow({ /* 配置 */ });    // 注册 IPC 处理器  ipcMain.handle('open-files', () => fileManager.openFileDialog());  ipcMain.handle('save-file', (event, content, defaultPath) =>     fileManager.saveFile(content, defaultPath)  );    // 监听来自渲染进程的请求  ipcMain.on('show-in-folder', (event, filePath) => {    if (fs.existsSync(filePath)) {      shell.showItemInFolder(filePath);    }  });    // 窗口关闭处理  mainWindow.on('close', async (event) => {    const choice = await dialog.showMessageBox(mainWindow, {      type: 'question',      buttons: ['保存并退出', '不保存退出', '取消'],      defaultId: 0,      cancelId: 2,      message: '是否保存当前内容?'    });        switch (choice.response) {      case 0:  // 保存并退出        event.preventDefault();        await saveCurrentContent();        mainWindow.destroy();        break;      case 1:  // 不保存退出        // 直接退出        break;      case 2:  // 取消        event.preventDefault();        break;    }  });});

4、性能优化与最佳实践

1. 模块懒加载// 需要时再加载模块let fs, path, os;async function processFile(filePath) {  if (!fs) fs = await import('fs/promises');  if (!path) path = await import('path');    const ext = path.extname(filePath);  return fs.readFile(filePath, 'utf8');}2. 错误处理// 统一的错误处理class AppError extends Error {  constructor(message, code, module) {    super(message);    this.code = code;    this.module = module;    this.timestamp = new Date().toISOString();  }}function safeRequire(moduleName) {  try {    return require(moduleName);  } catch (error) {    if (error.code === 'MODULE_NOT_FOUND') {      throw new AppError(`模块 ${moduleName} 未找到`, 'MODULE_MISSING', 'core');    }    throw error;  }}3. 资源管理// 清理资源class ResourceManager {  constructor() {    this.resources = new Set();  }    register(resource) {    this.resources.add(resource);    return resource;  }    cleanup() {    for (const resource of this.resources) {      if (resource.close) resource.close();      if (resource.destroy) resource.destroy();      if (resource.removeAllListeners) resource.removeAllListeners();    }    this.resources.clear();  }}// 使用const resources = new ResourceManager();app.on('before-quit', () => resources.cleanup());

持续更新中。。。

相关推荐