在 Electron 项目长期维护的过程中,代码规范和类型安全会直接决定团队的开发效率、重构信心以及新成员的上手速度。这一节不讲空泛的理论,而是聚焦两个工具在真实 Electron 工程中的落地:ESLint 保证代码风格和潜在错误检测,TypeScript 为主进程、预加载脚本和渲染进程提供统一的类型约束。
15.4.1 为什么要在一个项目里同时用 ESLint 和 TypeScript
很多开发者会有一个误解:既然用了 TypeScript,编译阶段就能发现很多类型错误,是不是可以不用 ESLint 了?实际上,两者的职责并不重叠:
- TypeScript 负责类型检查,确保你传给函数的是正确的参数、对象上不会出现不存在的属性。
- ESLint 负责代码风格与逻辑规则,例如禁止使用
var、要求使用const、检测可能的空值调用、统一导入顺序、避免使用已废弃的 API 等。
在 Electron 项目中,同一个仓库里通常包含多种运行环境:主进程运行在 Node.js 环境下,渲染进程运行在浏览器环境下,预加载脚本则处于一种受限的 Node.js 环境。这三者的全局变量、可用 API 各不相同。ESLint 的 env 配置和 TypeScript 的 tsconfig 可以分别为不同目录指定正确的上下文,避免出现误报或漏报。
15.4.2 在 Electron 项目中配置 ESLint
基础安装与初始化
npm install -D eslint @eslint/js
# 如果使用 TypeScript,还需要
npm install -D typescript @typescript-eslint/parser @typescript-eslint/eslint-plugin
然后创建 eslint.config.js(扁平配置风格,ESLint 9+ 推荐)或 .eslintrc.cjs(传统风格)。这里以扁平配置为例:
// eslint.config.js
import js from '@eslint/js';
import tseslint from 'typescript-eslint';
export default tseslint.config(
// 全局忽略模式
{ ignores: ['dist/', 'out/', 'node_modules/'] },
// 基础 JS 规则
js.configs.recommended,
// 主进程与预加载脚本:Node.js 环境
{
files: ['electron/**/*.ts', 'preload/**/*.ts'],
languageOptions: {
globals: {
...require('globals').node, // Node.js 全局变量
},
},
rules: {
'no-console': 'off', // 主进程允许 console
'@typescript-eslint/no-var-requires': 'off', // 动态 require 有时不可避免
},
},
// 渲染进程:浏览器环境
{
files: ['src/**/*.ts', 'src/**/*.tsx'],
languageOptions: {
globals: {
...require('globals').browser, // 浏览器全局变量
},
},
rules: {
'no-console': 'warn', // 渲染进程尽量用 logging 工具
},
},
// TypeScript 推荐规则
...tseslint.configs.recommended,
);
如果你的项目还在用 .eslintrc,核心思路相同:为 electron/ 和 src/ 分别配置 overrides,并指定不同的 env。
真实项目中常见的规则调整
Electron 开发中有几个特殊场景需要单独配置:
- 主进程里常常使用
ipcMain.handle、app.whenReady()等 Electron API,这些在 ESLint 看来可能是未定义的全局变量。需要安装eslint-plugin-electron插件,它提供了electron环境配置,能正确识别app、BrowserWindow、ipcMain等全局对象。安装后在languageOptions.globals中引入,或者使用插件内置的环境配置。 contextBridge.exposeInMainWorld注入到window上的自定义属性,在 TypeScript 中需要声明全局类型(见 15.4.3),在 ESLint 层面可以用globals手动声明,避免no-undef报错。- 如果渲染进程使用了 JSX 或 React,记得安装
eslint-plugin-react或对应的 Vue 插件,并开启推荐规则。
一个实用的命令:在 package.json 的 scripts 中添加:
{
"lint": "eslint . --ext .ts,.tsx,.js,.jsx",
"lint:fix": "eslint . --ext .ts,.tsx,.js,.jsx --fix"
}
15.4.3 TypeScript 在 Electron 项目中的整合
Electron 项目天然适合 TypeScript,因为它需要频繁地在主进程和渲染进程之间传递结构化的数据(IPC 消息、配置对象、文件信息等)。类型定义能够充当两个进程之间的“契约文档”,避免因字段拼写错误导致的难以排查的 bug。
多项目结构与 tsconfig 策略
由于主进程、预加载脚本和渲染进程的目标环境不同,通常会为它们准备不同的 tsconfig 文件:
project/
├── tsconfig.json // 根配置,仅用于引用
├── tsconfig.main.json // 主进程 + 预加载
├── tsconfig.renderer.json // 渲染进程
└── src/ // 渲染进程代码
└── electron/ // 主进程代码
└── preload/ // 预加载脚本
示例 tsconfig.main.json:
{
"compilerOptions": {
"target": "ES2022",
"module": "commonjs", // 主进程通常用 CommonJS(或 ESM 用 module: NodeNext)
"lib": ["ES2022"],
"outDir": "dist/main",
"rootDir": "electron",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"types": ["node"],
"resolveJsonModule": true
},
"include": ["electron/**/*.ts", "preload/**/*.ts"]
}
示例 tsconfig.renderer.json:
{
"compilerOptions": {
"target": "ES2022",
"module": "ESNext",
"lib": ["ES2022", "DOM", "DOM.Iterable"],
"outDir": "dist/renderer",
"rootDir": "src",
"strict": true,
"jsx": "preserve",
"esModuleInterop": true,
"skipLibCheck": true,
"types": ["vite/client"], // 或 webpack 的环境类型
"moduleResolution": "bundler",
"allowImportingTsExtensions": true
},
"include": ["src/**/*.ts", "src/**/*.tsx"]
}
这样做的好处是:主进程可以使用 Node.js 的模块解析策略,渲染进程则可以使用现代打包工具(Vite/Webpack)的模块解析,两者互不干扰。
打通主进程与渲染进程的类型通信
这是 TypeScript 在 Electron 中最有价值的部分。通常会定义一个共享的类型文件(如 shared/ipc-types.ts):
// shared/ipc-types.ts
export interface FileInfo {
name: string;
path: string;
size: number;
}
export interface IElectronAPI {
openFile: () => Promise<FileInfo | null>;
saveFile: (content: string) => Promise<void>;
onFileSaved: (callback: (path: string) => void) => void;
}
然后在主进程的 IPC handler 里实现这些接口:
// electron/main.ts
import { ipcMain, dialog } from 'electron';
import type { FileInfo } from '../shared/ipc-types';
ipcMain.handle('open-file', async (): Promise<FileInfo | null> => {
const result = await dialog.showOpenDialog({ /* ... */ });
// ...返回 FileInfo 或 null
});
在预加载脚本中,使用 contextBridge 暴露 API 时强制类型:
// preload/index.ts
import { contextBridge, ipcRenderer } from 'electron';
import type { IElectronAPI, FileInfo } from '../shared/ipc-types';
const api: IElectronAPI = {
openFile: () => ipcRenderer.invoke('open-file'),
saveFile: (content) => ipcRenderer.invoke('save-file', content),
onFileSaved: (callback) => ipcRenderer.on('file-saved', (_event, path) => callback(path)),
};
contextBridge.exposeInMainWorld('electronAPI', api);
渲染进程中,通过类型声明文件让 TypeScript 知道 window.electronAPI 的存在:
// src/types/electron.d.ts
import type { IElectronAPI } from '../../shared/ipc-types';
declare global {
interface Window {
electronAPI: IElectronAPI;
}
}
此后,在任何组件中使用 window.electronAPI.openFile() 时,TypeScript 都会提供完整的类型提示和检查,避免把 file.path 误写成 file.paht 这种低级错误。
真实项目中的细节
- 使用
electron-builder打包时,通常会让主进程和预加载脚本先被 TypeScript 编译到dist/目录,再在electron-builder.yml中指定入口文件为编译后的.js文件。 - 如果项目使用了 TypeScript 的 path alias(如
@shared/*),记得在主进程的tsconfig中配置好paths和baseUrl,并在编译时使用tsc-alias或类似工具将路径替换为实际相对路径,否则打包后运行会报模块找不到。 - 为了保持类型严格性,建议在根
tsconfig.json中开启strict: true,并逐步消灭any。一个良好的习惯是为主进程的 IPC handler 返回值和参数都显式标注类型,这样即便几个月后回来看代码,也能立刻知道每个通道传的是什么数据。
15.4.4 ESLint 与 TypeScript 的协同工作流
将两者无缝衔接的关键配置:
- 使用
typescript-eslint作为解析器,这样 ESLint 在检查.ts文件时能够理解 TypeScript 的语法和类型信息。 - 在
eslint.config.js中启用tseslint.configs.recommended,它会替代原生的 ESLint 规则,避免与 TypeScript 编译器规则冲突。 - 把类型检查的任务交给 TypeScript 编译器(
tsc --noEmit),ESLint 只负责代码风格和逻辑规则,这样运行速度更快(ESLint 不需要执行繁重的类型推导)。
日常开发中,可以在 package.json 中添加检查脚本,组合使用:
{
"lint": "eslint . --ext .ts,.tsx,.js,.jsx",
"typecheck": "tsc --noEmit -p tsconfig.main.json && tsc --noEmit -p tsconfig.renderer.json",
"validate": "npm run lint && npm run typecheck"
}
配合 Git hooks(如 Husky + lint-staged),可以在每次提交前自动修复格式并检查类型,确保仓库中的代码始终处于健康状态。
15.4.5 避坑指南
- 主进程 import 路径问题
如果渲染进程使用 Vite 等打包工具,可以用 @/ 别名,但主进程直接运行在 Node.js,不经过打包(除非你也用工具打包)。因此主进程中的相对导入一定要写对,或者用 tsconfig 的 paths + tsc-alias 处理。
contextBridge暴露的对象引用不响应更新
这是 Vue/React 响应式系统容易踩的坑。window.electronAPI 是静态对象,如果把它赋值给响应式状态,它不会自动追踪底层 IPC 事件。正确做法是在组件中调用 API 方法,并手动更新本地状态。
- 类型文件版本差异
Electron 的类型定义(@types/electron)已弃用,现在 Electron 自带了类型声明。在项目中只需安装 electron 即可获得类型支持。如果还在使用旧包,请移除 @types/electron,避免类型冲突。
- ESLint 检查 dist 目录
一定要在 ESLint 配置中忽略编译输出目录(如 dist/、out/),否则可能因为检查大量自动生成的 JS 文件而严重拖慢速度。
通过 ESLint 与 TypeScript 的扎实整合,你的 Electron 项目将拥有两个层面的质量护栏:编译时就能发现数据类型错误,提交前自动纠正风格违规。这不仅是代码规范的建设,更是对项目长期可维护性的投资——当项目从一个人变成三个人,从三个窗口变成十个窗口时,你会感谢这套基础设施的存在。