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 的运行规则, 你可以基于此去实现自己想要的功能

相关推荐
哆啦A梦15881 小时前
axios 的二次封装
前端·vue.js·node.js
阿珊和她的猫1 小时前
深入理解与手写发布订阅模式
开发语言·前端·javascript·vue.js·ecmascript·状态模式
yinuo1 小时前
一行 CSS 就能搞定!用 writing-mode 轻松实现文字竖排
前端
snow@li2 小时前
html5:拖放 / demo / 拖放事件(Drag Events)/ DataTransfer 对象方法
前端·html·拖放
浪裡遊3 小时前
Nivo图表库全面指南:配置与用法详解
前端·javascript·react.js·node.js·php
漂流瓶jz4 小时前
快速定位源码问题:SourceMap的生成/使用/文件格式与历史
前端·javascript·前端工程化
samroom4 小时前
iframe实战:跨域通信与安全隔离
前端·安全
fury_1235 小时前
vue3:数组的.includes方法怎么使用
前端·javascript·vue.js
weixin_405023375 小时前
包资源管理器NPM 使用
前端·npm·node.js
宁&沉沦5 小时前
Cursor 科技感的登录页面提示词
前端·javascript·vue.js