vite + electron + typescript 的启动与开发

什么是 electron

Electron是一个使用 JavaScript、HTML 和 CSS 构建桌面应用程序的框架。 嵌入 ChromiumNode.js 到 二进制的 Electron 允许您保持一个 JavaScript 代码代码库并创建 在Windows上运行的跨平台应用 macOS和Linux------不需要本地开发 经验。

它的运行核心可以总结为下图

electron 的核心概念

主进程

每个 Electron 应用都有一个单一的主进程,作为应用程序的入口点。 主进程在 Node.js 环境中运行,这意味着它具有 require 模块和使用所有 Node.js API 的能力。

预加载脚本

预加载(preload)脚本包含了那些执行于渲染器进程中,且先于网页内容开始加载的代码 。 这些脚本虽运行于渲染器的环境中,却因能访问 Node.js API 而拥有了更多的权限, 预加载脚本是运行在渲染进程的,他的优先级高于渲染进程, 他有部分的 node.js API 权限。

渲染进程

每个 Electron 应用都会为每个打开的 BrowserWindow ( 与每个网页嵌入 ) 生成一个单独的渲染器进程。 洽如其名,渲染器负责 渲染 网页内容。 所以实际上,运行于渲染器进程中的代码是须遵照网页标准的 (至少就目前使用的 Chromium 而言是如此) 。每一个 tab 栏就对应一个渲染进程。

进程间通讯

其本质就是发布订阅模式,但是这里叫 ipc(进程间通讯)

  • 主进程向渲染进程
    • 主进程通过 webContents.send 向渲染进程发送消息
    • 预加载脚本通过 ipcRenderer.on 方法发送到渲染进程
    • 渲染进程接受对应方法即可
  • 渲染进程向主进程
    • 渲染进程调用预加载脚本的方法传递消息
    • 预加载脚本通过 ipcRender.send 方法发送到主进程
    • 主进程通过 ipcMain.on 方法监听消息
  • 主进程与渲染进程双向通讯
    • 发送方使用 invoke 触发事件
    • 接受方通过 handle 处理事件

其数据核心流向可以总结为这张图

启动项目

我们使用 vite + electron + typescript 做一个简单的开机一键启动应用的工具, 在这里我们就不自己在项目中去配置 electron , 感兴趣的可以查看官网 www.electronjs.org/zh/docs/lat... 如果下载不下来,要确保 node 版本为高版本, 并使用 pnpm 下载。 这里我们直接使用便捷的脚手架进行搭建 cn.electron-vite.org/guide/

sql 复制代码
npm create @quick-start/electron@latest

根据指引构建完成后你会得到这样一个文件目录

erlang 复制代码
.
├──src
│  ├──main
│  │  ├──index.ts
│  │  └──...
│  ├──preload
│  │  ├──index.ts
│  │  └──...
│  └──renderer    # with vue, react, etc.
│     ├──src
│     ├──index.html
│     └──...
├──electron.vite.config.ts
├──package.json
└──...

可以根据自己的需要在 vite 配置文件中配置想要的相关信息

同时也可以在 ts 配置文件中配置 ts 相关配置

  • main 文件夹

    main 文件夹就是主进程, 可以和计算机底层进行 io 操作,在这里我们添加两行代码

javascript 复制代码
import { onGetAppList, onHandleStartCMD } from './startApp'

....

app.whenReady().then(() => {
  electronApp.setAppUserModelId('com.electron')

  app.on('browser-window-created', (_, window) => {
    optimizer.watchWindowShortcuts(window)
  })

  // ============== event start =============
  ipcMain.handle('getAppPath', onGetAppList)
  ipcMain.handle('execStartCMD', onHandleStartCMD)
  // ============== event end ================

  createWindow()

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

抽离的两个方法

javascript 复制代码
// startApp.ts

import { exec, execSync } from 'child_process'

// 一键启动应用
const onHandleStartCMD = (event, data) => {
  // 启动目标软件(例如启动 VSCode)

  data.forEach((item) => {
    exec(`open "/Applications/${item}"`, (error, stdout) => {
      if (error) console.error(`执行失败: ${error}`)
      else console.log(`已启动: ${stdout}`)
    })
  })
}

// 获取应用列表
const onGetAppList = async () => {
  const res = execSync('ls /Applications').toString()
  return res.split('\n').filter((item) => item !== '')
}

export { onHandleStartCMD, onGetAppList }

注意上图的启动路径是可以配置的,不用写死, 这里为了方便直接写死了 /Applications

  • preload 文件夹

    这个文件是预加载脚本,可以理解为一个通道, 通过它可以实现 主进程与渲染进程的通讯

js 复制代码
import { contextBridge, ipcRenderer } from 'electron'
import { electronAPI } from '@electron-toolkit/preload'

/**
 * send 使用 on 监听
 * invoke 使用 handle 双向通讯
 */

// Custom APIs for renderer
const api = {
  // 获取应用路径
  getAppPath: async () => {
    return await ipcRenderer.invoke('getAppPath')
  },
  // 执行启动命令
  execStartCMD: async (list: string[]) => {
    return await ipcRenderer.invoke('execStartCMD', list)
  }
}

// Use `contextBridge` APIs to expose Electron APIs to
// renderer only if context isolation is enabled, otherwise
// just add to the DOM global.
if (process.contextIsolated) {
  try {
    contextBridge.exposeInMainWorld('electron', electronAPI)
    contextBridge.exposeInMainWorld('api', api)
  } catch (error) {
    console.error(error)
  }
} else {
  // @ts-ignore (define in dts)
  window.electron = {
    sendMessage(channel: string, ...args: unknown[]) {
      ipcRenderer.send(channel, ...args)
    }
  }
  // @ts-ignore (define in dts)
  window.api = api
}
  • render 文件夹

    该文件夹就是一个普通的前端项目文件夹, 可以按照正常的前端项目开发进行开发

在启动时只需要调用暴露的方法名即可 api.execStartCMD(startList)

javascript 复制代码
import React from 'react'
import { Button, Flex } from 'antd'
import { useNavigate } from 'react-router-dom'
import { useAppSelector } from '@renderer/store/hooks'

const Start: React.FC = () => {
  const navigate = useNavigate()
  const startList = useAppSelector((state) => state.startConfig.startList)

  return (
    <main>
      <Flex gap={12}>
        <section>
          <Button
            onClick={async () => {
              api.execStartCMD(startList)
            }}
          >
            一键启动
          </Button>
        </section>
        <section>
          <Button
            onClick={() => {
              navigate('/settings')
            }}
          >
            配置启动项
          </Button>
        </section>
      </Flex>
    </main>
  )
}

export default Start

打包

执行对应命令行即可

最终打包成功后会生成 dist 文件

将压缩包解压后可以获得我们的程序

使用

双击打开后

点击配置启动项

返回点击一键启动

这个是一个简单的实现, 功能并不完善, 但能够帮助你快速的理解 electron 的运行规则, 你可以基于此去实现自己想要的功能

相关推荐
brzhang1 小时前
颠覆你对代码的认知:当程序和数据只剩下一棵树,能读懂这篇文章的人估计全球也不到 100 个人
前端·后端·架构
斟的是酒中桃1 小时前
基于Transformer的智能对话系统:FastAPI后端与Streamlit前端实现
前端·transformer·fastapi
烛阴2 小时前
Fract - Grid
前端·webgl
JiaLin_Denny2 小时前
React 实现人员列表多选、全选与取消全选功能
前端·react.js·人员列表选择·人员选择·人员多选全选·通讯录人员选择
brzhang2 小时前
我见过了太多做智能音箱做成智障音箱的例子了,今天我就来说说如何做意图识别
前端·后端·架构
为什么名字不能重复呢?3 小时前
Day1||Vue指令学习
前端·vue.js·学习
eternalless3 小时前
【原创】中后台前端架构思路 - 组件库(1)
前端·react.js·架构
Moment3 小时前
基于 Tiptap + Yjs + Hocuspocus 的富文本协同项目,期待你的参与 😍😍😍
前端·javascript·react.js
Krorainas3 小时前
HTML 页面禁止缩放功能
前端·javascript·html
whhhhhhhhhw4 小时前
Vue3.6 无虚拟DOM模式
前端·javascript·vue.js