【实践】 如何使用Electron开发第一个应用

同步更新 微信公众号【爱写代码的小任】 欢迎关注

背景

Electron是一个强大的框架,可以帮助开发者使用Web技术(HTML、CSS和JavaScript)构建跨平台的桌面应用程序。它已经被广泛应用于许多知名应用的开发,如Visual Studio Code、Slack和GitHub Desktop等。
在最近的工作中, 需要开发使用类似的这样一个桌面应用程序,于是就和代码搭子一起合作, 带您一步步了解如何使用Electron开发您的第一个应用。

理论知识

1. 架构

Electron应用由两个主要进程组成:主进程和渲染进程。主进程负责管理应用的生命周期、创建和控制渲染进程以及与底层操作系统进行交互。渲染进程是基于Chromium的浏览器进程,负责呈现和处理Web页面。

2. 主进程

主进程是Electron应用的入口点,通常在main.js文件中定义。它是一个Node.js进程,可以使用Electron的API调用底层操作系统的功能,如创建窗口、处理系统事件和执行文件操作等。

3. 渲染进程

染进程是在Electron窗口中加载的Web页面运行的进程。每个窗口都有一个单独的渲染进程,使用Chromium提供的Web技术来呈现用户界面和处理用户交互。渲染进程使用Electron的API与主进程进行通信,实现进程间的消息传递和协作。

4. 主进程和渲染进程的通信

Electron提供了IPC(进程间通信)机制,允许主进程和渲染进程之间进行通信。使用Electron的ipcMain和ipcRenderer模块,开发者可以在主进程和渲染进程之间发送和接收消息,以实现进程间的数据传递和事件触发。

5. 打包和分发

Electron应用可以使用Electron Builder、electron-packager、Electron Forge等工具进行打包和分发。这些工具可以将应用程序打包为可执行文件或安装程序,以便在不同操作系统上进行部署和分发。

6. 应用扩展

过使用Electron的模块系统,开发者可以使用众多的第三方模块和插件来扩展应用的功能。这些模块可以用于实现文件系统操作、数据库访问、网络请求、GUI组件等,为应用提供更多的能力和灵活性。

7. 安全性

由于Electron应用运行在本地环境中,开发者需要注意安全性方面的考虑。例如,需要注意处理用户输入的安全性、防范跨站脚本攻击(XSS)、限制访问本地资源等。

实践过程

1. 创建项目

在开始之前,确保您已经安装了Node.js和npm(Node.js的包管理器)
因为Electron下载默认从国外,一般会有限制,建议提前执行如下配置,获取最好的下载效果

# pnpm
pnpm config set electron_mirror "https://registry.npmmirror.com/-/binary/electron/" 
# yarn
yarn config set electron_mirror "https://registry.npmmirror.com/-/binary/electron/" 

准备主进程代码 main.js

// 省略其他代码
const mainWindow = new BrowserWindow({
    frame: true,//创建无边框窗口
    resizable: false,   //不允许用户改变窗口大小
    width: 800,        //设置窗口宽高
    height: 600,
    icon: iconPath,     //应用运行时的标题栏图标
    webPreferences: {
      backgroundThrottling: false,   //设置应用在后台正常运行
      nodeIntegration: true,     //设置能在页面使用nodejs的API
      contextIsolation: false,
      // preload: path.join(__dirname, './preload.js')
    }
  })

  mainWindow.loadFile('index.html')

准备渲染进程代码

<!DOCTYPE html>
<html>

<head>
    <meta charset="utf-8">
    <title>Untitled Document</title>
    <link rel="stylesheet" href="css/base.css">
</head>

<body>
<h1>登录</h1>
<form action="/login" method="post">
    <label for="username">用户名:</label>
    <input type="text" id="username" name="username">
    <br>
    <label for="password">密码:</label>
    <input type="password" id="password" name="password">
    <br>
    <img src="xx" @click="getCode" class="login-code-img" />
    <input type="submit" value="登录">
</form>
</body>
<script src=" https://cdn.bootcdn.net/ajax/libs/axios/0.26.1/axios.min.js"></script>
<script src="js/login.js"></script>
</html>

准备渲染进程 JavaScript代码 login.js

const { ipcRenderer } = require('electron')

window.addEventListener('load', function () {
  let login_img = document.querySelector('.login-code-img')
  axios.defaults.baseURL = 'http://localhost:8080/api'
  axios.get('/captchaImage').then(res => {
    let codeUrl = "data:image/gif;base64," + res.data.img
    login_img.src = codeUrl
  })

  //监听
  ipcRenderer.on('mainsend', (event, arg) => {
    console.log('渲染进程收到的消息:', arg)
  })
  // 表单验证
  const form = document.querySelector("form");

  form.addEventListener("submit", (event) => {
    event.preventDefault();

    ipcRenderer.send('setNewData', '渲染进程的数据')
    // 获取表单数据
    const username = document.querySelector("#username").value;
    const password = document.querySelector("#password").value;

    // 验证用户名和密码
    if (username === "" || password === "") {
      alert("用户名或密码不能为空");
      return;
    }

    // 发送登录请求
    const request = new XMLHttpRequest();
    request.open("POST", "/login");
    request.setRequestHeader("Content-Type", "application/json");
    request.send(JSON.stringify({ username, password }));

    // 处理登录结果
    request.addEventListener("load", () => {
      const response = JSON.parse(request.responseText);

      if (response.success) {
        // 登录成功,跳转到主页
        window.location.href = "/";
      } else {
        // 登录失败,显示错误信息
        alert(response.error);
      }
    });
  });
})

准备 package.json

{
  "name": "tasky",
  "version": "1.0.0",
  "description": "This is a task management app",
  "main": "main.js",
  "scripts": {
    "dev": "electron .",
    "make": "electron-forge make"
  },
  "dependencies": {
    "react": "18.2.0",
    "vue": "3.4.15"
  },
  "devDependencies": {
    "cross-env": "7.0.3",
    "electron": "28.2.0",
    "nodemon": "^3.0.3"
  }
}

2. 启动项目

$ pnpm dev

过一会儿,就会启动起来
登录截图

3. 项目打包

Electron Forge 是一个用于加速Electron应用开发的工具集。它提供了一组命令行工具和脚手架,用于初始化、构建、打包和发布Electron应用。

# 自动导入 electron forge 相关依赖和文件
$ npx electron-forge import

这个时候,会在项目根目录生成 forge.config.js

module.exports = {
  packagerConfig: {
    asar: true,
  },
  rebuildConfig: {},
  makers: [
    {
      name: '@electron-forge/maker-squirrel',
      config: {},
    },
    {
      name: '@electron-forge/maker-zip',
      platforms: ['darwin'],
    },
    {
      name: '@electron-forge/maker-deb',
      config: {},
    },
    {
      name: '@electron-forge/maker-rpm',
      config: {},
    },
  ],
  plugins: [
    {
      name: '@electron-forge/plugin-auto-unpack-natives',
      config: {},
    },
  ],
};

同时自动配置项目依赖 和 运行命令

   scripts: {
      "make": "electron-forge make"
   }

  "devDependencies": {
    "@electron-forge/cli": "^7.2.0",
    "@electron-forge/maker-deb": "^7.2.0",
    "@electron-forge/maker-rpm": "^7.2.0",
    "@electron-forge/maker-squirrel": "^7.2.0",
    "@electron-forge/maker-zip": "^7.2.0",
    "@electron-forge/plugin-auto-unpack-natives": "^7.2.0",
  }

执行 make 命令

$ pnpm run make

生成如下目录和包 Mac下如图
包目录

扩展知识

1. 进程间通信

渲染进程发送与接收

const { ipcRenderer } = require('electron')

window.addEventListener('load',function(){
  // 渲染进程 监听主进程消息
  ipcRenderer.on('mainsend', (event, arg) => {
      console.log('渲染进程收到的消息:',arg)
  })
  // 表单验证
  const form = document.querySelector("form");

  form.addEventListener("submit", (event) => {
    event.preventDefault();
	// 渲染进程发送消息到主进程
    ipcRenderer.send('setNewData', '渲染进程的数据')
    // 获取表单数据
    const username = document.querySelector("#username").value;
    const password = document.querySelector("#password").value;
    // 省略其他代码
 });
})

主进程发送与接收

const electron = require('electron')
// 这里主要引入 ipcMain
const { app, BrowserWindow, ipcMain, Tray, Menu, screen, dialog } = electron

app.on('ready', () => {
	// 注意: 这里要能确保ipc通信在生命周期里面
    mainWindow.webContents.on('did-finish-load', () => {
    	// 主进程 发送
	    mainWindow.webContents.send('mainsend', '这是主进程的主动搭讪')
	    // 接收渲染进程的事件消息
	    ipcMain.on('setNewData', (event, data) => {
	      console.log('主进程接收---------------------111', data);
	    })
	})
   // 省略其他代码
})

2. 应用自动升级

准备测试升级文件

provider: generic,
url: "http://192.168.1.155:3000/checkUpdate"

客户端代码如下

/*
 * 自动升级方法
 */
function checkUpdate() {
	// 本机开发调试,需要指定如下配置
  Object.defineProperty(app, 'isPackaged', {
    get() {
      return true;
    }
  })
  	// 本机开发调试,需要指定如下配置
  autoUpdater.updateConfigPath = path.join(__dirname, 'dev-app-update.yml')
  
   if (process.platform == 'darwin') {
    autoUpdater.setFeedURL('http://192.168.1.155:3000/checkUpdate')
  } else {
    autoUpdater.setFeedURL('http://192.168.1.155:3000/checkUpdate')
  }
  autoUpdater.checkForUpdates()
  autoUpdater.on('error', (err) => {
    console.log('err', err)
  })
  autoUpdater.on('update-available', () => {
    console.log('found new version')
  })
  autoUpdater.on('update-not-available', () => {
    console.log('Notfound new version')
  })
  autoUpdater.on('update-downloaded', () => {
    dialog.showMessageBox({
      type: 'info',
      title: '应用更新',
      message: '发现新版本,是否更新?',
      buttons: ['是', '否']
    }).then((buttonIndex) => {
      if (buttonIndex.response == 0) {
        autoUpdater.quitAndInstall()
        app.quit()
      }
    })
  })
}

后面再应用 ready的时候调用即可

app.on('ready', () => {
  //检查更新
  checkUpdate()
  // 省略其他代码
})

生成签名代码【主要是为了计算文件签名】

const crypto = require('crypto');
const fs = require('fs');
 
// 异步读取文件并计算SHA-512哈希
function getSha512Hash(filePath) {
  return new Promise((resolve, reject) => {
    fs.readFile(filePath, (err, data) => {
      if (err) {
        reject(err);
      } else {
        const hash = crypto.createHash('sha512');
        hash.update(data);
        resolve(hash.digest('hex'));
      }
    });
  });
}
 
// 使用方法
const filePath = '/Users/i-sinosoft/Downloads/tasky-darwin-arm64-1.0.0.zip'; // 替换为你的文件路径
 
getSha512Hash(filePath).then(hash => {
  console.log('SHA-512 Hash:', hash);
}).catch(error => {
  console.error('Error generating SHA-512 hash:', error);
});

服务端升级API代码(测试)

const express = require("express")

const app = express()
app.get('/checkUpdate/:platform', (req, res, next) => {
    let {platform} = req.params
    let resinfo = null
    // 返回 Windows 配置
    if (platform == 'latest.yml') {
        resinfo = {
            name: 'v1.0.2',
            version: 'v1.0.2',
            path: "https://niaoshuai-book.oss-cn-beijing.aliyuncs.com/tasky-1.0.0%20Setup.exe",
            notes: "xxxxxx",
            sha512: '4d1a4a9f797a43e968483ee7dcc74ed25d1b0d256e979e68d310dce4cd131541ad0e84f0e4ddf23a424133f2736038c79c43aa3b45e9a280d487fbc693dd3f05'
        }
    }
    // 返回 Mac 配置
    if (platform == 'latest-mac.yml') {
        resinfo = {
            name: 'v1.0.3',
            version: 'v1.0.3',
            path: "https://niaoshuai-book.oss-cn-beijing.aliyuncs.com/tasky-darwin-arm64-1.0.0.zip",
            notes: "xxxxxx", sha512:'8f7c5951a4a100eec0c05caa19d9a5e2392122830360301fd4ca2baae2dedac54ff6065d6aed03e433287863a5c0a250aa9a8bbdd5eba1b2756ec14e2ab4c01f'
        }
    }
    if (!resinfo) {
        resinfo = {code: 400, msg: '参数错误'}
    }
    res.send(resinfo)
})
app.listen(3000, () => {
    console.log("express web server is listen at 3000 port!");
})


Mac下效果
在这里插入图片描述
Windows下效果
在这里插入图片描述

总结

通过本文, 您学会了如何使用Electron 开发第一个应用。您了解了创建主进程和渲染进程的基本步骤,并启动了一个简单的Electron应用;同时也使用Electron Forge可以减少开发者在配置和构建Electron应用程序时的工作量,并提供一致的开发和分发体验。
最后希望这个经验能够激发您的创造力,探索Electron的更多功能,并构建出令人惊艳的跨平台桌面应用。

代码地址: https://gitee.com/eight-liang-listener/electron_first.git

关注我,实时获取我的更新动态,和我一起讨论

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

爱写代码的小任

感谢老板打赏,我将会再接再厉

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值