人人都会AI编程

5.3 模块加载机制:主进程模块、渲染进程模块、node_modules 共享机制

更新时间:2026-07-11

Electron 应用的模块加载看似和普通 Node.js 或前端项目差不多,但因为主进程和渲染进程的运行环境不同,实际使用中有一些关键差异需要理清。这一节帮你弄明白三个问题:主进程怎么加载模块、渲染进程怎么加载模块,以及 node_modules 里的那些代码在两种进程间如何共享。

5.3.1 主进程的模块加载

主进程运行在完整的 Node.js 环境中,所以它的模块加载方式和你写服务端代码时完全一样 —— 直接使用 require 或 ES Modules 的 import(需要 Node.js 版本支持)。你可以加载任何 npm 包,也可以加载 Electron 内置的原生模块(如 appBrowserWindowipcMain 等)。

// main.js —— 主进程
const { app, BrowserWindow, ipcMain } = require('electron');
const path = require('path');
const fs = require('fs');                // Node.js 核心模块
const axios = require('axios');         // npm 安装的第三方库
const myUtils = require('./utils');     // 自己的 CommonJS 模块

由于主进程拥有完整的系统权限,你在这里加载的模块可以直接读写文件、发起网络请求、调用操作系统接口。Electron 也为主进程提供了 electron 命名空间,所有框架级的 API(窗口管理、菜单、托盘、通知等)都从 require('electron') 中获取。

如果项目配置了 ES Modules("type": "module"),主进程也可以使用 import 语法,不过需要注意 Electron 自身的模块目前仍然建议用 require 引入(或者在 import 中通过 default 导出访问)。大多数 Electron 项目的主进程还是采用 CommonJS 风格以保持兼容性。

5.3.2 渲染进程的模块加载

渲染进程的环境本质上是 Chromium 浏览器,因此它默认只能使用标准的 Web 技术加载模块,比如用 <script> 标签引入全局库、通过 ES Modules 的 import 加载 .js 文件或前端框架生成的资源。

关键安全限制:出于安全考虑,现代 Electron 应用不建议在渲染进程中直接开启 Node.js 集成(即 nodeIntegration: true)。这意味着你不能直接在渲染进程的 JS 里写 require('fs')require('electron')。所有对 Node.js 或 Electron API 的访问必须通过预加载脚本(preload)提供的桥接接口。

// preload.js —— 预加载脚本,运行在受限的 Node.js 环境
const { contextBridge, ipcRenderer } = require('electron');

// 向渲染进程暴露一个安全的 'api' 对象
contextBridge.exposeInMainWorld('api', {
  readFile: (filePath) => ipcRenderer.invoke('read-file', filePath),
  showDialog: (options) => ipcRenderer.invoke('show-dialog', options),
});

然后在渲染进程(你的 HTML/JS 页面)里,就可以像使用普通浏览器 API 一样调用:

// renderer.js —— 渲染进程
document.getElementById('btn').addEventListener('click', async () => {
  const content = await window.api.readFile('/path/to/file.txt');
  console.log(content);
});

前端框架(如 Vue、React)经过打包工具(Vite、Webpack)处理后,会生成浏览器可识别的 JS 文件。这些打包文件通过 <script type="module"> 或普通 <script> 标签加载到渲染进程中,完全遵循前端的模块化规范。你需要哪种前端库,就在前端构建流程中安装和引入,和普通网页开发无异。

总结一下渲染进程的模块加载规则:

  • Web 标准加载:使用 ES Modules 或打包后的 bundle,不能使用 require
  • 不能直接访问 Node.js 模块fspath 等不可用,Electron 原生模块 (electron) 也不可访问。
  • 通过预加载脚本安全暴露:需要调用系统能力时,在主进程实现功能,利用 IPC 和预加载脚本封装成渲染进程可用的 API。

5.3.3 node_modules 的共享机制

Electron 项目的 node_modules 目录对于主进程和预加载脚本是完全可访问的,因为这两个环境都运行在 Node.js 之上。你通过 npm install 安装的任何包,在主进程里都可以直接用 requireimport 导入。预加载脚本也一样,它有权加载需要的 Node 模块(比如 electronpath,或者用来封装高级功能的第三方库)。

但对于渲染进程,情况就不同了。渲染进程是纯浏览器环境,不能直接访问 node_modules 里的 Node.js 模块。即使你在项目根目录安装了 lodashaxios 等模块,渲染进程也无法用 require('lodash')。如果你想在界面上使用这些库,正确的做法是:

  • 将它们作为前端依赖安装到 dependencies 中,然后通过前端打包工具(如 Vite、Webpack)处理,让它们被打包进最终的页面 JS 文件里。
  • 如果某个库只有 Node.js 版本而没有浏览器版本(例如 fs-extra),那就只能在主进程中加载,再通过 IPC 把结果传给渲染进程。

一个常见的误解是,既然 Electron 打包了 Chromium 和 Node.js,那渲染进程里应该也能轻松引用 Node 模块。实际上 Electron 故意隔离了这两种环境,以避免直接将系统权限暴露给可能被攻击的表层界面。这种隔离确保了:即使渲染进程中的页面代码被注入了恶意脚本,攻击者也无法直接调用 require('child_process').exec('rm -rf /') 这种危险命令,因为他根本没有 require

node_modules 共享的实际意义在于:主进程和预加载脚本可以共用同一套依赖,不需要为这两个运行环境分别安装包。比如你可以在主进程和预加载脚本中都使用 electronpath,它们来自同一个 node_modules/electron 和 Node.js 内置模块。这种共享简化了项目结构,也保证了版本的一致性。

最后提一下 electron 模块本身的加载机制。在 Electron 中,require('electron') 返回的是一个包含了所有框架 API 的对象。这个模块并不是通过 npm install 安装出来的,而是 Electron 内置的。主进程和预加载脚本可以直接加载它,渲染进程则不能(除非通过预加载桥接)。这是很多新手踩坑的地方:在渲染进程里写 const { ipcRenderer } = require('electron') 会报错,必须把 ipcRenderer 移到预加载脚本中,再通过 contextBridge 暴露出去。

实用小结

  • 主进程:想用啥直接用 require,权限全开。
  • 渲染进程:忘记 require,把 Node 需求交给 IPC + 预加载。
  • node_modules:主进程和预加载共享,渲染进程请通过前端构建工具或 IPC 使用需要的库。

理解了这套模块加载机制,你就掌握了 Electron 应用动静分离、安全防护的核心设计,后续写主进程逻辑、设计预加载桥接、组织前端依赖的时候,方向就不会偏。