UNPKG

utquidem

Version:

The meta-framework suite designed from scratch for frontend-focused modern web development.

432 lines (333 loc) 10.7 kB
--- sidebar_position: 4 --- # 开发桌面应用 本章将介绍如何使用 Modern.js,进行桌面应用开发。本章对应的代码仓库地址为:[middle-platform-electron](https://github.com/modern-js-dev/modern-js-examples/tree/main/quick-start/middle-platform-electron)。 通过本章你可以了解到: - 桌面应用项目:创建与开发调试。 - 桌面应用项目结构。 - 桌面应用基本开发流程。 :::tip 提示 - 上一章节中,着重讲解了如何使用 Modern.js,进行中后台项目的开发,此处不再重复,以在 Electron 中的开发为主。 - 本章节以渲染进程[**关闭 Node **](/docs/guides/features/electron/develop)开发为例,进行讲解。 ::: ## 环境准备 import EnvPrepare from '@site/docs/components/env-prepare.md'; <EnvPrepare /> ## 创建项目 使用 `@modern-js/create` 创建新项目,运行命令如下: ```bash npx @modern-js/create middle-platform-electron ``` :::tip 提示 middle-platform-electron 为创建的项目名。 ::: 按照如下选择,生成项目: ```bash ? 请选择你想创建的工程类型: 应用 ? 请选择开发语言: TS ? 请选择包管理工具: pnpm ? 是否需要支持以下类型应用: Electron ? 是否需要调整默认配置: 否 ``` 目录结构如下: ```bash . ├── README.md ├── config │ └── electron │ ├── entitlements.mac.plist // mac 下应用权限文件 │ ├── icon.icns // mac 应用图标 │ ├── icon.ico // windows 应用图标 │ └── logo.png ├── electron // 主进程代码目录 │ ├── main.ts // 主进程默认入口文件 │ ├── modern-app-env.d.ts │ └── tsconfig.json ├── modern.config.js ├── package.json ├── pnpm-lock.yaml ├── src // 渲染进程代码 │ ├── App.css │ ├── App.tsx │ └── modern-app-env.d.ts └── tsconfig.json ``` ## 启用 Electron 如果已经存在 Modern.js 项目,则只需要启用 Electron 功能。 首先执行 `pnpm run new`: ```bash (base) ➜ modernjs-1.0.0 git:(master) pnpm run new > modernjs-1.0.0@0.1.0 new /Users/kp/Documents/work/modernjs-1.0.0 > modern new ? 请选择你想要的操作 启用可选功能 ? 启用可选功能 启用「Electron」模式 [INFO] 依赖自动安装成功 [INFO] 启用 Electron 模式成功! 可在项目的目录下运行以下命令: pnpm run dev:electron # 按开发环境的要求,启动 Electron pnpm run build:electron # 按产品环境的要求,构建 Electron 项目 ``` 然后,在项目 `modern.config.js` 下新增如下相关配置: ```ts export default defineConfig({ output: { assetPrefix: '../../', }, runtime: { state: true, router: { supportHtml5History: process.env.NODE_ENV === 'development', }, }, electron: { builder: { baseConfig: { appId: 'com.bytedance.demo', // eslint-disable-next-line no-template-curly-in-string artifactName: 'demo_${env.VERSION}.${ext}', files: [ { from: '.', to: '.', filter: ['!**/*.map', '!**/*.d.ts', '!*.log', '!*.lock'], }, ], directories: { app: 'dist', }, }, }, }, } ``` :::tip 提示 如果在 package.json 以存在部分相同配置,则需要删除其中一个即可。 ::: ## 开发调试 进入项目根目录,之后执行 `pnpm run dev:electron` 即可启动应用: ```bash # 进入项目根目录 cd middle-platform-electron # 启动开发服务器 pnpm run dev:electron ``` :::info 补充信息 启动时,可以将主进程、渲染进程分开启动,`package.json` 中的命令如下: ```json { "dev": "modern dev", // 启动渲染进程 "dev:main": "modern dev electron-main" // 启动主进程 } ``` ::: ## 打开窗口 ### 主进程注册打开窗口函数 在 Electron 中,一般在主进程里进行窗口管理。因此,我们在主进程注册窗口打开函数。 ```ts title='electron/main.ts' import Runtime, { winService } from '@modern-js/runtime/electron-main'; // ... const runtime = new Runtime({ // ... mainServices: { openWindow: (winName: string) => { return winService.createWindow({ name: winName }); } }, }); ``` :::info 补充信息 更多信息,请参考【[主进程注册服务](/docs/guides/features/electron/ipc/regist-services/main)】。 ::: ### 新建预加载脚本目录 我们在窗口中,关闭了 Node 配置。因此,我们通过 Electron 提供的预加载脚本的形式进行开发,我们在预加载脚本里将 Node 相关 API 注入到全局变量,从而在渲染进程中即可使用。 首先,我们新建 `electron/preload/index.ts`,并在其中通过 API `exposeInMainWorld` 注册窗口打开函数。 ```ts title='electron/preload/index.ts' import { exposeInMainWorld, browserWindowPreloadApis, } from '@modern-js/runtime/electron-render'; const { callMain } = browserWindowPreloadApis; export const apis = { ...browserWindowPreloadApis, openWindow: (winName: string) => { return callMain('openWindow', winName); }, }; exposeInMainWorld(apis); ``` :::tip 提示 - 此处从 `'@modern-js/runtime/electron-render'` 引入的 `exposeInMainWorld` 与 Electron 原生的有点差异。 - 此处的 `exposeInMainWorld` 相当于 Electron 中的:`(apis: any) => exposeInMainWorld('bridge', apis);`。 ::: ### 窗口预加载脚本路径配置 我们只需要在窗口配置上,增加预加载脚本路径即可。 在开发时,由于在 Electron 中,`BrowserWindow` 对象预加载脚本为:JavaScript。 因此我们新建一个文件,通过 Babel 编译 TS。 ```js title='electron/preload/index.dev.js' const { join } = require('path'); const babel = require('@babel/register'); const { babelConfig } = require('@modern-js/plugin-electron/tools'); babel( Object.assign(babelConfig, { extensions: ['.ts', '.js'], }), ); require(join(__dirname, 'index.ts')); ``` :::tip 提示 此文件,仅在开发时有用,构建后均为 JS 文件,无需做转化。 ::: 然后,我们对脚本路径进行配置: ```ts title='electron/main.ts' import { join } from 'path'; // preload js for browserwindow to provide native apis for render-process const PRELOAD_JS = join( __dirname, 'preload', 'browserWindow', process.env.NODE_ENV === 'development' ? 'index.dev.js' : 'index.js', ); const runtime = new Runtime({ windowsConfig: [{ name: 'main', options: { webPreferences: { preload: PRELOAD_JS } } }], // ... }); ``` ### 新增窗口 当我们使用框架提供的 `winService` 做窗口管理时,新增窗口,只需要在启动时,添加一个窗口配置即可。 ```ts title='electron/main.ts' const runtime = new Runtime({ windowsConfig: [{ name: 'main', options: { webPreferences: { preload: PRELOAD_JS } } }, { name: 'console' }], // ... }); ``` 如上,我们新增了 `console` 窗口配置。 :::info 补充信息 - 如果不配置加载路径,则此窗口打开的时候,默认加载名为 `console` 的入口路径。 - 【[开发中后台应用](/docs/start/admin)】中,已经介绍过如何新增一个入口,这里不再重复。 ::: ### 在渲染进程中打开窗口 我们所有在预加载脚本中注册的服务,均可使用 `bridge` 在页面中进行访问。 比如,上面我们在预加载脚本中注册了 `openWindow`,即可这样使用: ```tsx title="xx/xx.tsx(渲染进程)" import bridge from '@modern-js/runtime/electron-bridge'; <button type="button" onClick={() => { bridge.openWindow('console') }} >打开 console 窗口</button> ``` 此时,你可能会遇到找不到 `openWindow` 的类型提示错误。因为我们扩展后,没做类型定义,我们可以如下扩展类型,后续就不需要类型定义了。 - 新增类型定义文件 `typings/index.d.ts`: ```ts declare module '@modern-js/electron-runtime' { export type BrowserWindowApis = typeof import('../electron/preload').apis; } ``` - 在项目根目录 `tsconfig.json` 中配置 `"types": ["./typings"]`: ```json { "compilerOptions": { // ... "types": ["./typings"] }, // ... } ``` ## 构建应用 在根目录下,直接执行 `pnpm run build:electron` 即可对应用进行构建。 产物如下: ```bash release/ ├── builder-debug.yml ├── builder-effective-config.yaml ├── demo_1.0.0.dmg # 安装包 ├── demo_1.0.0.dmg.blockmap ├── demo_1.0.0.zip ├── demo_1.0.0.zip.blockmap ├── latest-mac.yml └── mac # 免安装版 ``` :::info 补充信息 默认针对 macOS 系统做构建,更多操作系统打包请参考【[构建](/docs/guides/features/electron/pack)】。 ::: ## 测试 ### 测试主进程服务 我们在主进程里新建一个服务函数: ```ts title='electron/main.ts' const runtime = new Runtime({ windowsConfig, mainServices: { // ... getWindowCount: () => { return winService.getWindows().length; } }, }); ``` 接着,我们通过 `testServices` 注册该服务即可。 ```ts title='electron/main.ts' import { testServices } from '@modern-js/electron-test/main'; // ... const runtime = new Runtime({ windowsConfig, mainServices: testServices({ // ... getWindowCount: () => { return winService.getWindows().length; } }), }); ``` :::tip 提示 `testServices` 不影响构建产物。 ::: 新建相关测试文件: ```ts title='electron/tests/index.test.ts' /** * @jest-environment @modern-js/electron-test/dist/js/node/testEnvironment.js */ import './main-process'; jest.setTimeout(100000); ``` 此文件在加载的时候,会加载 `@jest-environment` 环境,启动一个 Electron 应用实例,挂载到 `global` 对象上。 ```ts title='electron/tests/main-process/index.ts' // test main services import TestDriver from '@modern-js/electron-test'; let testDriver: TestDriver | null = null; jest.setTimeout(100000); beforeAll(async () => { testDriver = (global as any).testDriver; // 当 main 窗口加载完毕 await testDriver?.whenReady('main'); }); describe('test main process services', () => { it('test window count', async () => { // 调用主进程服务-getWindowCount const windowsCount = await testDriver?.call({ funcName: 'getWindowCount', }) expect(windowsCount).toEqual(1); }) }) ``` :::info 补充信息 更多信息,请参考【[Electron 测试](/docs/guides/features/electron/test)】。 :::