Node.js + Electron开发跨平台桌面应用实战

17老师 · 2026-06-15 · 阅读约14分钟

← 返回博客列表

前言

Electron是一个使用Web技术开发桌面应用的框架,VS Code、Slack、Discord等知名应用都是基于Electron开发的。本文将带你从零搭建一个完整的Electron桌面应用。

一、项目初始化

# 创建项目
mkdir my-electron-app && cd my-electron-app
npm init -y

# 安装依赖
npm install electron --save-dev
npm install electron-builder --save-dev

# 配置package.json
{
  "name": "my-electron-app",
  "version": "1.0.0",
  "main": "main.js",
  "scripts": {
    "start": "electron .",
    "build": "electron-builder",
    "build:win": "electron-builder --win",
    "build:mac": "electron-builder --mac",
    "build:linux": "electron-builder --linux"
  }
}

二、主进程开发

2.1 创建主窗口

// main.js
const { app, BrowserWindow, ipcMain, dialog, Tray, Menu, nativeImage } = require('electron')
const path = require('path')
const fs = require('fs')

let mainWindow
let tray

function createWindow() {
  mainWindow = new BrowserWindow({
    width: 1200,
    height: 800,
    minWidth: 800,
    minHeight: 600,
    webPreferences: {
      nodeIntegration: true,
      contextIsolation: false,
      preload: path.join(__dirname, 'preload.js')
    },
    titleBarStyle: 'hiddenInset',
    icon: path.join(__dirname, 'assets/icon.png')
  })

  mainWindow.loadFile('index.html')
  
  // 开发环境打开DevTools
  if (process.env.NODE_ENV === 'development') {
    mainWindow.webContents.openDevTools()
  }

  mainWindow.on('closed', () => {
    mainWindow = null
  })
}

app.whenReady().then(createWindow)

app.on('window-all-closed', () => {
  if (process.platform !== 'darwin') {
    app.quit()
  }
})

app.on('activate', () => {
  if (BrowserWindow.getAllWindows().length === 0) {
    createWindow()
  }
})

2.2 IPC通信

// 主进程中监听渲染进程的消息
ipcMain.handle('read-file', async (event, filePath) => {
  try {
    const content = fs.readFileSync(filePath, 'utf-8')
    return { success: true, content }
  } catch (error) {
    return { success: false, error: error.message }
  }
})

ipcMain.handle('write-file', async (event, filePath, content) => {
  try {
    fs.writeFileSync(filePath, content, 'utf-8')
    return { success: true }
  } catch (error) {
    return { success: false, error: error.message }
  }
})

ipcMain.handle('open-file-dialog', async () => {
  const result = await dialog.showOpenDialog(mainWindow, {
    properties: ['openFile'],
    filters: [
      { name: 'Text Files', extensions: ['txt', 'md', 'json'] },
      { name: 'All Files', extensions: ['*'] }
    ]
  })
  return result.filePaths[0]
})

三、渲染进程开发

<!-- index.html -->
<!DOCTYPE html>
<html>
<head>
  <title>My Electron App</title>
  <style>
    body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; margin: 0; padding: 20px; }
    .toolbar { display: flex; gap: 10px; margin-bottom: 20px; }
    .btn { padding: 8px 16px; border: none; border-radius: 6px; cursor: pointer; background: #2563eb; color: white; }
    .btn:hover { background: #1d4ed8; }
    textarea { width: 100%; height: 400px; font-family: monospace; font-size: 14px; }
  </style>
</head>
<body>
  <div class="toolbar">
    <button class="btn" onclick="openFile()">打开文件</button>
    <button class="btn" onclick="saveFile()">保存文件</button>
    <button class="btn" onclick="saveAsFile()">另存为</button>
  </div>
  <textarea id="editor" placeholder="在此编辑内容..."></textarea>

  <script>
    const { ipcRenderer } = require('electron')
    let currentFilePath = null

    async function openFile() {
      const filePath = await ipcRenderer.invoke('open-file-dialog')
      if (filePath) {
        const result = await ipcRenderer.invoke('read-file', filePath)
        if (result.success) {
          document.getElementById('editor').value = result.content
          currentFilePath = filePath
        }
      }
    }

    async function saveFile() {
      if (!currentFilePath) return saveAsFile()
      const content = document.getElementById('editor').value
      await ipcRenderer.invoke('write-file', currentFilePath, content)
    }

    async function saveAsFile() {
      const content = document.getElementById('editor').value
      // 保存到默认位置
      currentFilePath = require('path').join(require('os').homedir(), 'Desktop', 'untitled.txt')
      await ipcRenderer.invoke('write-file', currentFilePath, content)
    }
  </script>
</body>
</html>

四、系统托盘

// main.js 中添加托盘功能
function createTray() {
  const icon = nativeImage.createFromPath(path.join(__dirname, 'assets/tray-icon.png'))
  tray = new Tray(icon)
  
  const contextMenu = Menu.buildFromTemplate([
    { label: '显示窗口', click: () => mainWindow.show() },
    { label: '最小化', click: () => mainWindow.minimize() },
    { type: 'separator' },
    { label: '退出', click: () => app.quit() }
  ])
  
  tray.setToolTip('My Electron App')
  tray.setContextMenu(contextMenu)
  
  tray.on('double-click', () => {
    mainWindow.show()
  })
}

五、自动更新

// 使用electron-updater
const { autoUpdater } = require('electron-updater')

// 配置更新服务器
autoUpdater.setFeedURL({
  provider: 'generic',
  url: 'https://your-server.com/releases/'
})

// 检查更新
autoUpdater.checkForUpdatesAndNotify()

// 监听更新事件
autoUpdater.on('update-available', () => {
  mainWindow.webContents.send('update-available')
})

autoUpdater.on('download-progress', (progress) => {
  mainWindow.webContents.send('download-progress', progress)
})

autoUpdater.on('update-downloaded', () => {
  autoUpdater.quitAndInstall()
})

六、打包发布

6.1 electron-builder配置

// package.json
{
  "build": {
    "appId": "com.example.myapp",
    "productName": "My App",
    "win": {
      "target": "nsis",
      "icon": "assets/icon.ico"
    },
    "mac": {
      "target": "dmg",
      "icon": "assets/icon.icns"
    },
    "linux": {
      "target": ["AppImage", "deb"],
      "icon": "assets/icon.png"
    },
    "nsis": {
      "oneClick": false,
      "allowToChangeInstallationDirectory": true
    }
  }
}

6.2 构建命令

# 构建所有平台
npm run build

# 仅构建Windows
npm run build:win

# 仅构建Mac
npm run build:mac

# 仅构建Linux
npm run build:linux

总结

Electron让Web开发者可以轻松构建跨平台桌面应用。掌握主进程、渲染进程、IPC通信、系统托盘、自动更新等核心技术,你就可以开发出专业级的桌面应用。如果需要Electron应用开发服务,欢迎联系17老师。

关于17老师:AI应用·数字化管理·全栈开发·网络安全全栈专家。联系邮箱:j.d88888888@qq.com,微信:AFIST17
上一篇
Docker+K8s部署指南
下一篇
Claude API高级用法