vite + electron + typescript 的启动与开发

什么是 electron

Electron是一个使用 JavaScript、HTML 和 CSS 构建桌面应用程序的框架。 嵌入 Chromium 和 Node.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 的运行规则, 你可以基于此去实现自己想要的功能

相关推荐
修炼的dance4 分钟前
录屏关键帧间隔多少合适?六档实测,2秒到10秒小了75.6%
前端
Lank_M10 分钟前
关键帧从1秒拉到10秒,录屏小87.3%,摄像头小10.8%
前端
补码缩写12 分钟前
用 ffprobe 拆录屏,八成字节在 29 个关键帧上
前端
百思不得小李14 分钟前
一个复杂的动态表单是如何实现的
前端·设计模式
陈随易28 分钟前
Cmder 停更 17 个月,2.0 支持 Windows Terminal
前端·后端·程序员
风骏时光牛马39 分钟前
前沿视界
前端
kyriewen44 分钟前
我扫了 814 个依赖,97 个还敢要 Node 10
前端·javascript·程序员
陈随易1 小时前
3000 万人在用的翻译插件,重写一遍就是一门生意
前端·后端·程序员
IT_陈寒1 小时前
Vue的这个响应式陷阱,差点让我加班到天亮
前端·人工智能·后端
Flynt2 小时前
别写拖拽行为,只声明"能拖到哪":UIST 最佳论文的拖拽方案我实测了
前端·svg·preact