Electron 多进程下的“库引入“全解析:核心模块、Electron API、第三方依赖与 utilityProcess 的依赖处理

很多人写 Electron 时对 require 的认知还停留在"装了包就能 require"。但 Electron 是一个多进程模型,一旦涉及主进程、渲染进程、utilityProcess、再叠加打包后的目录变化,require 能不能成功就变成了两个完全不同维度的问题:

  • 路径维度:这个模块的文件到底在不在我能解析到的地方?(解析问题,可修)
  • 身份维度:这个能力到底属不属于"我这个进程"?(进程边界问题,不可修,只能换进程)

这篇文档把 Electron 里会遇到的五类"库"逐一拆开讲清楚,并结合本 demo 的真实代码,讲透:把这些文件放到安装包之外(比如用户数据目录)后,怎么处理才能让它们跑起来 ,以及 webpack 打包时必须注意的点


一、先建立正确的心智模型:三种进程不是"同一个 Node"

Electron 不是"一个 Node 进程里跑了个浏览器"。它是 Chromium 的多进程架构,每个进程内嵌了 Node 运行时(V8 + libuv),但进程身份不同,能拿到的 native 绑定就不同

进程 本质 Node 运行时 Electron 主进程 API(app/net/session...)
主进程 main Chromium 的 browser process(根进程) ✅ 完整 ✅ 完整
渲染进程 renderer Chromium 渲染进程 ⚠️ 默认关闭,可开启 ⚠️ 受限(主要是 ipcRenderer 等)
utilityProcess Chromium Services 子进程 ✅ 完整 ❌ 几乎只有 process.parentPort

记住一句话贯穿全文:

能不能用 Node 生态 → 看模块在不在能被解析到的 node_modules(路径问题)。
能不能用 Electron 能力 → 看你是不是那个持有 Chromium 子系统的进程(身份问题)。


二、五类库逐一拆解

1. Node.js 核心模块(内置模块)

例子:path fs http https net module crypto os url child_process...

特征 :它们被编译进了 Electron 二进制本身require('path') 根本不碰文件系统,直接从二进制内部的模块表返回。

结论

  • 任何进程(main / renderer 开了 nodeIntegration / utilityProcess)里都能用。
  • 不管脚本文件被搬到哪(安装目录、asar、用户数据目录、临时目录),永远解析得到。
  • 它跟 node_modules 没有任何关系,也跟 NODE_PATH 无关。

本 demo 的 utility-server.jsrequire('http')require('module')require('path') 之所以"搬到用户目录也能用",就是因为它们是核心模块。

⚠️ 易混点:net 这个名字同时存在两套东西------Node 自带的 require('net')(裸 TCP/IPC socket,核心模块,到处能用)和 Electron 的 require('electron').net(基于 Chromium 网络栈的 HTTP 客户端,主进程专属)。名字一样,性质天差地别,见下一节。


2. Electron 扩展库(主进程专属 API)

例子:app BrowserWindow session net(electron 的) dialog protocol ipcMain Menu Tray globalShortcut...

特征 :这些不是普通可被搬运的 JS 库 ,而是指向"正在运行的 browser process"内部 native 对象的绑定(handle)

  • app 绑定的是这个进程的生命周期和 Chromium 消息循环;
  • net(electron)绑定的是 browser process 里那个唯一的网络服务实例,要走系统代理、复用 session 的 cookie;
  • session / BrowserWindow 绑定的是窗口与会话子系统。

这些对象只在主进程这一个进程里存在

结论

  • 只能在主进程里用。
  • 即使在 utilityProcess 里 require('electron'),也拿不到这些 handle ------ 因为它本身就不是那个持有这些对象的进程。这不是"包没装",搬库也搬不过来。
  • 在 utilityProcess 里需要这些能力时:让主进程 调用,结果通过 process.parentPort 回传。本 demo 的 deep-link 转发就是这个模式------所有 Electron 侧的事(fork、建管道、协议注册)都在主进程,utility 只管业务。
js 复制代码
// ❌ 在 utilityProcess 里这样做,net 是 undefined
const { net } = require('electron')

// ✅ utilityProcess 里只能用 Node 自带的网络能力
const http = require('http')   // 或 https / net(node)

3. 渲染进程可开启的库(nodeIntegration / contextIsolation)

渲染进程默认是一个纯浏览器环境,没有 require、没有 Node 。能否在 renderer 里 require 取决于 BrowserWindowwebPreferences

配置 效果 安全性
nodeIntegration: false(默认) renderer 里没有 require/process/Buffer ✅ 安全,推荐
nodeIntegration: true renderer 里可直接 require('fs') ❌ 危险,加载远程内容时等于把 Node 暴露给网页
contextIsolation: true(默认) preload 与页面 JS 隔离上下文 ✅ 安全,推荐

推荐做法(也是 Electron 官方方向) :renderer 不开 nodeIntegration,需要的 Node/Electron 能力通过 preload + contextBridge 白名单暴露,或走 ipcRenderer → ipcMain 让主进程代劳。

js 复制代码
// preload.js ------ 只把需要的能力按白名单暴露给渲染层
const { contextBridge, ipcRenderer } = require('electron')
contextBridge.exposeInMainWorld('api', {
  handleProtocol: url => ipcRenderer.invoke('protocol:handle', url)
})

关键认知:renderer 的"业务依赖"(Vue、axios 等)不是靠运行时 require('node_modules') 来的,而是 webpack 在构建时直接打进了 renderer bundle 。所以 renderer 反而最"自包含",它不依赖 NODE_PATH,依赖的是资源路径对不对(见第四节)。


4. 第三方库(npm 依赖,住在 node_modules)

例子:本 demo 的 express axios vue

特征 :它们是真实存在于 node_modules 里的文件,require('express') 走 Node 的文件系统解析算法:从脚本所在目录开始,逐级向上找 node_modules/express

结论

  • 这是纯路径问题 。脚本旁边能向上找到 node_modules/express 就成功,找不到就报 Cannot find module 'express'
  • 打包后这个目录关系经常被破坏(asar、用户目录热更新等),需要手动修复解析路径,见第三、四节。

但这里有个分叉,取决于 webpack 怎么处理它(见第五节):

  • 如果 webpack 把第三方库打进 bundle (renderer 通常如此)→ 运行时根本不 require,无所谓 node_modules
  • 如果 webpack 把它列为 external (本 demo 主进程/utility 如此)→ 产物里保留了运行时 require('express'),那就必须保证运行时能解析到 node_modules

5. utilityProcess 进程里的库

utilityProcess 是被主进程 utilityProcess.fork(scriptPath, args, options) 拉起的轻量 Node 子进程。它的依赖能力等于:

Node 核心模块(永远有) + 能被解析到的 node_modules 里的第三方库(要修路径) + 几乎为零的 Electron API(只有 parentPort)

本 demo 的 utility-server.js 是一个独立打包入口,它要用 express,于是问题就来了:当这个脚本被放到用户数据目录后,它旁边没有 node_modules,怎么 require('express')

答案就是下一节的核心。


三、把文件放到"安装包之外"后,怎么让它跑起来

本 demo 已经实现了 utility-server 脚本的"用户目录优先 + 热更新"。核心代码在 src/main/index.js。拆开看它解决了两件事。

3.1 文件本身:脚本放哪、优先用哪个

utilityProcess.fork() 要的是一个真实可访问的 JS 文件路径。demo 的策略是"用户目录有就用用户目录的,否则用包内的":

js 复制代码
function resolveUserInstallUtilityServerPath() {
  // %APPDATA%/electron-demo/utility-process/utility-server.js
  return path.join(app.getPath('userData'), 'utility-process', 'utility-server.js')
}

function resolveUtilityServerStartPath(bundledServicePath) {
  const userServicePath = resolveUserInstallUtilityServerPath()
  if (fs.existsSync(userServicePath)) {
    return userServicePath   // 用户目录热更新脚本优先
  }
  return bundledServicePath  // 回落到安装包内 dist/electron/server/utility-server.js
}

包内脚本也要多路径兜底,因为开发态、--dir、asar 后的 __dirname 不一样:

js 复制代码
function resolveBundledUtilityServerPath() {
  const candidates = [
    path.resolve(__dirname, UTILITY_SERVER_BUNDLE),
    path.resolve(__dirname, '..', UTILITY_SERVER_BUNDLE),
    path.resolve(app.getAppPath(), 'dist', 'electron', UTILITY_SERVER_BUNDLE),
    path.resolve(app.getAppPath(), UTILITY_SERVER_BUNDLE)
  ]
  const servicePath = candidates.find(c => fs.existsSync(c))
  if (!servicePath) throw new Error(`utility server bundle not found...`)
  return servicePath
}

3.2 依赖解析:用户目录脚本怎么找到包内的 node_modules

这是整个方案最关键、最容易踩坑的地方。脚本搬到用户目录后,它旁边没有 node_modules,但当前安装包里有(通常在 app.asar/node_modules)。主进程要把这个路径注入给子进程。

第一步,主进程多路径找到当前安装包的 node_modules

js 复制代码
function resolveUtilityServerNodePath() {
  const candidates = [
    path.resolve(app.getAppPath(), 'node_modules'),                       // 开发态 / app 解包
    path.resolve(process.resourcesPath || '', 'app.asar', 'node_modules'),// 打包后 asar
    path.resolve(process.resourcesPath || '', 'app', 'node_modules'),     // asar 解包
    path.resolve(__dirname, '..', '..', 'node_modules'),
    path.resolve(__dirname, '..', 'node_modules')
  ]
  return candidates.find(c => c && fs.existsSync(c))
}

第二步,fork 时通过 env 自定义变量 把这个路径传给子进程(注意 cwd 也指到脚本目录):

js 复制代码
const nodePath = resolveUtilityServerNodePath()
if (!nodePath) {
  throw new Error(`utility server node_modules path not found...`)
}

protocolServiceProcess = utilityProcess.fork(servicePath, [PIPE_NAME], {
  cwd: path.dirname(servicePath),
  env: {
    ...process.env,
    UTILITY_SERVER_NODE_PATH: nodePath   // ⭐ 业务自定义变量,不是直接塞 NODE_PATH
  },
  stdio: 'pipe'
})

第三步,子进程脚本require 第三方库之前 ,用 Node 核心模块 module 把这个路径注册进解析算法:

js 复制代码
// dist/electron/server/utility-server.js 开头
const Module = require('module')   // 核心模块,一定找得到
const path = require('path')

const utilityServerNodePath = process.env.UTILITY_SERVER_NODE_PATH
if (utilityServerNodePath) {
  process.env.NODE_PATH = utilityServerNodePath
  Module._initPaths()              // ⭐ 重新初始化解析路径
}

const express = require('express') // 必须在 _initPaths 之后!
const http = require('http')

为什么这么绕?三个细节:

  1. 顺序不能反require('express') 必须在 Module._initPaths() 之后。webpack 产物里这段是手写在最前面、独立于 webpack 模块系统的引导代码,确保解析路径先就位。
  2. 不要只在 env 里直接传 NODE_PATH 。实测发现,对"用户目录脚本"这个场景,fork 时直接给 env 设 NODE_PATH 并不可靠,子进程启动后 require('express') 仍可能找不到 app.asar/node_modules。更稳的是传一个业务自定义变量UTILITY_SERVER_NODE_PATH),进程内自己 process.env.NODE_PATH = ... + Module._initPaths(),时机完全可控。
  3. express 等依赖在 asar 里能被 require 。Electron 给 Node 的 fs/模块解析打了补丁,app.asar 会被当成虚拟目录,所以 app.asar/node_modules/express 是能正常 require 的,不需要把依赖解包。

3.3 验证:用 /health 自证从哪启动、依赖路径是什么

排查这类问题,光看"能不能跑"不够,要让服务自己报告它的处境。demo 的 /health 返回了关键字段:

js 复制代码
app.get('/health', (req, res) => {
  res.json({
    startedFrom: __filename,                                  // 证明从用户目录还是包内启动
    cwd: process.cwd(),                                       // 证明 fork 的工作目录
    utilityServerNodePath: process.env.UTILITY_SERVER_NODE_PATH || '' // 证明注入的依赖路径
  })
})
  • 用户目录热更新生效时,startedFrom 应指向 %APPDATA%/electron-demo/utility-process/utility-server.js
  • utilityServerNodePath 应指向当前安装包的 app.asar/node_modules

3.4 渲染进程放到安装包之外,处理方式完全不同

注意:renderer 的"用户目录优先/热更新"和 utility-server 不是一回事

  • utility-server 是被 fork 当 Node 脚本跑的 → 要解决"依赖在哪"(NODE_PATH)。
  • renderer 是被主进程 win.loadFile(...) 加载进 BrowserWindow 的页面 → 它的业务依赖已被 webpack 打进 bundle,不需要 node_modules;要解决的是"资源相对路径/协议对不对"。

所以 renderer 的热更新形态是:把构建好的 renderer 目录放用户目录 → 主进程检测到就 loadFile(用户目录/index.html),否则 loadFile(包内/index.html)。两者结构同构(用户目录优先),但触发机制不同(fork vs loadFile),要解决的根因也不同(依赖解析 vs 资源路径)。并且无论哪种,utilityProcess 只能由主进程启动,renderer 不能自己 fork,主进程都绕不开。


四、一张表收口:遇到 require 报错该往哪查

报错/现象 属于哪类 根因 怎么修
Cannot find module 'path'/'http' 核心模块 几乎不可能,除非进程类型不对 检查是不是在浏览器纯环境里跑
utilityProcess 里 electron.net 是 undefined Electron 扩展库 进程身份问题,utility 不是主进程 改用 Node 的 http/net,或主进程代劳 + parentPort
renderer 里 require is not defined 渲染进程库 nodeIntegration 没开(且不该开) 用 preload + contextBridge / ipc
用户目录脚本 Cannot find module 'express' 第三方库 旁边没有 node_modules,解析失败 注入 NODE_PATH + Module._initPaths()
打包后 utility server bundle not found 文件路径 asar 后 __dirname 变了 多 candidate 路径兜底 fs.existsSync

五、webpack 打包时的注意事项

本 demo 主进程和 utility 用 .electron-vue/configs/webpack.main.config.js 打包,几个点必须注意。

5.1 utility 脚本要作为独立入口打包

utilityProcess.fork() 需要一个真实文件。开发态直接指向 src/main/utility-server.js 可能能跑,但生产包目录结构变化后极易找不到。所以把它配成独立 entry,输出到固定子目录:

js 复制代码
entry: {
  main: path.join(__dirname, '../../src/main/index.js'),
  'utility-server': path.join(__dirname, '../../src/main/utility-server.js')
},
output: {
  filename: pathData => {
    if (pathData.chunk?.name === utilityServerBundle.entryName) {
      return utilityServerBundle.relativeOutputPath   // server/utility-server.js
    }
    return '[name].js'
  },
  library: { type: 'commonjs2' },   // 产物是 CommonJS,能被 fork 当 Node 脚本跑
  path: electronDistDir
}

入口路径还通过 DefinePlugin 注入成常量 __UTILITY_SERVER_BUNDLE__,主进程用它来定位脚本,避免硬编码字符串。

5.2 第三方依赖列为 externals,不要打进 bundle

这是主进程/utility 打包与 renderer 最大的区别:

js 复制代码
const { dependencies } = require('../../package.json')

externals: [
  ...Object.keys(dependencies || {})   // express / axios / vue 全部 external
],

含义:webpack 不把 express 打进 bundle ,产物里保留运行时 require('express')(你能在产物里看到 module.exports = require("express") 这样的 external 包装)。

  • 好处:bundle 小、原生模块(.node)不会被 webpack 破坏、依赖走 Node 真实解析。
  • 代价 :运行时必须能解析到 node_modules ------ 这正是第三节 NODE_PATH 方案存在的原因。
  • 推论:凡是列进 dependencies 的,运行时都得在 node_modules 里真实存在;凡是只在构建期用的,放 devDependencies,不会进 externals。本 demo 里 express/axios/vue/vue-electron 在 dependencies,所以会随安装包进 app.asar/node_modules

5.3 target 要选对

js 复制代码
target: 'electron-main'   // 主进程/utility 用这个
  • 主进程 / utility:electron-main,webpack 会把 Node 核心模块和 electron 当外部依赖,不去 polyfill。
  • 渲染进程:electron-rendererweb,并把 Vue 等依赖打进 bundle(renderer 不用 externals 那套),所以 renderer 产物自包含,依赖的是资源路径而非 node_modules。

5.4 原生模块 / .node 文件

js 复制代码
module: { rules: [{ test: /\.node$/, use: 'node-loader' }] }

涉及原生 .node 的依赖更要走 externals + node-loader,绝不能让 webpack 试图把二进制打进 bundle。

5.5 electron-builder 的 files 要包含产物

jsonc 复制代码
"build": {
  "files": ["dist/electron/**/*"]   // 确保 server/utility-server.js 被打进安装包
}

否则 fork 时 resolveBundledUtilityServerPath() 在包内根本找不到脚本。


六、渲染进程"放哪都能用"的真正条件

经常有人得出这样一个结论:

"渲染进程开启了 node 集成、上下文不隔离、Vue 又打进了 renderer,那这个 renderer 放哪都能用。"

这个因果是错配的。 让 renderer 自包含/可搬运的,只有"打进 bundle"这一件事,跟 nodeIntegration、contextIsolation 没有任何关系。

条件 跟"放哪都能用"有关吗
开启 node 集成 ❌ 无关,甚至有反作用(见下)
上下文不隔离 ❌ 无关,纯安全设置
Vue 等打进 renderer bundle 这才是自包含的唯一原因

Vue 能用,是因为 webpack 在构建时 把它的代码直接塞进了 renderer 的 JS bundle,运行时根本不发生 require('vue')、不碰 node_modules这件事在 nodeIntegration 关闭时一样成立。

6.1 nodeIntegration 开启反而是新的"不可搬运"风险

很反直觉但很关键:开 node 集成的意义是"让页面能 require",而一旦你真的去 require 一个没被打进 bundle 的第三方 node 库,你就立刻把第四类库的"路径解析问题"引回了 renderer。

js 复制代码
// renderer 里开了 nodeIntegration 后:
const fs = require('fs')           // ✅ 核心模块,在二进制里,搬哪都行
const express = require('express') // ⚠️ 运行时解析!旁边没有 node_modules 就炸
  • require Node 核心模块 → 还能搬。
  • 运行时 require 外部 npm 库(没打进 bundle 的)→ 又回到"必须能解析到 node_modules",反而变得不可搬运

所以真正可搬运的 renderer,是"什么都打进 bundle、运行时不做任何外部 require "的 renderer------这种情况下你压根不需要开 node 集成。开了又去用外部库,是在给自己制造搬运障碍;何况 contextIsolation:false + nodeIntegration:true 是公认的安全反模式,加载任何不完全可信的内容时等于把 Node 暴露给页面。

6.2 即便 bundle 自包含,"放哪都行"还有两个边界

  1. renderer 不能自己跑。 它不是可执行程序,是一张要被 BrowserWindow 加载的页面。无论放哪,都必须由主进程 win.loadFile(那个路径) 指过去。"放在用户目录"只改了文件位置,加载动作仍由主进程发起------主进程绕不开。
  2. 资源相对路径 / publicPath 要对。 通过 file:// 加载时,HTML 引用的 JS/CSS/图片/字体路径必须仍能解析到。如果 webpack 用了绝对 publicPath 或假设了固定 base,换目录就会白屏/404。这才是 renderer 搬到包外真正要处理的问题------资源路径,不是依赖解析。

准确表述:renderer 的可搬运性 = "依赖打没打进 bundle"(构建期决定)+ "资源路径对不对"(加载期决定),跟"进程身份/node 集成"是两套维度。


七、都打进 bundle,会不会包体大、影响性能

会让安装包变大,但"包体大"和"性能差"在 Electron 里不是同一件事 ,也不像网页那样强相关。关键差异:Electron 的 renderer 从本地磁盘走 file:// 加载,没有网络下载这一环。

成本 受 bundle 大小影响吗 在 Electron 里严重吗
网络下载耗时 网页里很关键 ❌ 几乎不存在,本地磁盘读取
安装包 / 磁盘体积 ✅ 直接变大 ⚠️ 影响分发体积,不影响运行性能
JS 解析+编译+执行耗时 ✅ 真正影响启动 这才是要管的

7.1 不打进 bundle ≠ 更快

容易误以为"把 Vue 留在 node_modules、不打进 bundle"更轻,其实不是:

  • 那些代码该被解析执行一行都不会少,只是文件换了位置。
  • node_modules几百上千个小文件 ,运行时一堆 fs 查找 + asar 解析,往往比一个打好的 bundle 更慢
  • 打进 bundle 还能吃到 tree-shaking + 压缩混淆,external 的 node_modules 这两样都享受不到。

所以 renderer 这边"打进 bundle"通常是更优 选择。external 那套是 utility-server 为了可解析/可热更才用的,不是为了性能。

7.2 体积/启动真的大了,正确解法是"延迟执行"而非"踢出 bundle"

  1. 代码分割 + 路由懒加载 (最主要的杠杆):动态 import() 把非首屏页面/组件拆成独立 chunk,首屏只留必需,V8 启动要解析的 JS 就小了。
  2. production 模式构建:minify + scope hoisting + 去掉 dev 警告,Vue 2 运行时本身就只有几十 KB(gzip)。
  3. vendor 拆分:第三方库单独成 chunk,便于磁盘缓存 / V8 code cache 复用。
  4. V8 code cache / 快照(进阶):缓存"解析编译"结果,二次启动更快。

落到本 demo:renderer 依赖就是 vue@2 + axios,体量很小,打进去完全无所谓。这类担忧通常在依赖树很重(大型 UI 库、地图、富文本、ECharts 全量)时才需要认真对待。


八、本 demo 的最优选择:第三方包直接打进 utility-server,省掉 NODE_PATH

第三节为了通用性讲了 UTILITY_SERVER_NODE_PATH + Module._initPaths() 的完整方案。但针对当前这个 demo ,有更简单的路子:utility-server 运行时唯一的第三方 require 就是 expresshttp/module/path 都是核心模块)。只要把 express 打进 utility-server 的 bundle ,运行时不再发生 require('express'),这个脚本就是单文件自包含 ------丢用户目录也好、丢哪都好,不依赖任何 node_modules,于是整套 NODE_PATH 机制可以全删掉。

对热更新反而更友好:用户目录里的脚本自带 express,不用再"复用当前安装包的 node_modules",少一个出错点。

8.1 具体改三处

  1. webpack 让 express 不再 externalwebpack.main.config.js):
js 复制代码
externals: [
  ...Object.keys(dependencies || {}).filter(d => d !== 'express')
],

main 进程没 import express,所以只有 utility-server 入口会把它打进去,main 不受影响。

  1. 删掉 src/main/utility-server.js 顶部的引导块(改源文件,不是 dist):
js 复制代码
// 这一整段可以删了
const Module = require('module')
if (utilityServerNodePath) {
  process.env.NODE_PATH = utilityServerNodePath
  Module._initPaths()
}

直接 const express = require('express') 即可,webpack 会把它替换成内联代码。

  1. 删掉主进程的路径注入src/main/index.js):resolveUtilityServerNodePath()、那个 if (!nodePath) throw、fork 时 env 里的 UTILITY_SERVER_NODE_PATH 都不需要了,/health 里的 utilityServerNodePath 字段也可一并去掉。

8.2 唯一要记住的边界

这条路只对"纯 JS 依赖"成立 。express、axios 都是纯 JS,能干净打包。但如果 utility-server 引入了带原生 .node 二进制的依赖 (如 better-sqlite3serialport),webpack 没法把二进制打进 bundle,那就得退回 external + node_modules + NODE_PATH 那套。

纯 JS 依赖 → 打进 bundle,最省心;含原生模块 → 必须 external + 传 node 路径。

当前 demo 全是纯 JS,直接打进去即可。


九、总结

把 Electron 的"库引入"想成两条独立的判断线,绝大多数 require 问题都能秒定位:

  1. 这是 Electron 能力还是 Node/第三方能力?
    • Electron 主进程 API(app/net/session...)→ 身份问题,只有主进程有,utility/renderer 拿不到,改路径无效,只能换进程或让主进程代劳。
  2. 如果是 Node/第三方能力,文件解析得到吗?
    • 核心模块(path/http/net...)→ 在二进制里,永远 OK。
    • 第三方(express...)→ 看 webpack 是打进 bundle 还是 external;若 external,运行时必须能解析到 node_modules,搬到安装包外时用 UTILITY_SERVER_NODE_PATH + Module._initPaths() 修复。
  3. renderer 特殊 :它默认没有 Node,业务依赖靠 webpack 打进 bundle,搬到包外要解决的是资源路径而非依赖解析,且必须由主进程 loadFile 加载。renderer 的可搬运性来自"打进 bundle",跟 nodeIntegration / contextIsolation 无关------后者用错了反而引回依赖解析问题,还是安全反模式。

两条工程取舍:

  • 打不打进 bundle? 纯 JS 依赖优先打进 bundle(更快、可 tree-shaking、自包含、可搬运);只有含原生 .node 的依赖才必须 external + 传 node 路径。包体大主要影响安装包体积,不等于性能差,启动慢用懒加载/压缩治理,而不是踢出 bundle。
  • 本 demo 落地 :utility-server 只依赖纯 JS 的 express,直接打进它的 bundle 即可,UTILITY_SERVER_NODE_PATH + Module._initPaths() 整套都能删掉(见第八节)。

一句话收尾:utilityProcess 复用的是 Node 生态和 app.asar 的 node_modules,但拿不到任何"主进程专属"的 Electron 能力;主进程才是那个持有全部 Chromium 子系统的根进程。


相关文档:

相关推荐
小此方1 小时前
【别传:Web前端开发(三)】重塑动态视界:JS底层逻辑、数据类型流转与WebAPI交互全景草稿
前端·javascript·交互
仰望.1 小时前
vue表格使用 vxe-table 展开行实现产品列表与明细列表
前端·javascript·vue.js·vxe-table
AOwhisky1 小时前
Redis 学习笔记(第二期):核心数据类型与消息队列实战
运维·数据库·redis·笔记·学习·云计算
南岸的水1 小时前
ubuntu里面SDK编译指令及报错处理
linux·运维·ubuntu
爱网络爱Linux2 小时前
Linux 服务器开机慢?启动链路优化实战
linux·运维·redhat·rhce·rhca·红帽认证
buhuizhiyuci2 小时前
【Linux篇】数字世界的底层认识, 它是底层的地基——进程概念的认识
linux·运维·服务器
BizViewStudio2 小时前
2026 年 GEO 成为企业线上流量增长核心风口|2026 品牌 GEO 运营指南,6 家全链路优化服务商解析
运维·网络·人工智能·microsoft·ai
Gong-Yu2 小时前
MySQL数据库运维——性能优化进阶1️⃣
运维·数据库·mysql·性能优化
meilindehuzi_a2 小时前
深入理解JavaScript线性数据结构:从内存视角探究数组、链表、栈与队列
javascript·数据结构·链表