Files
claude[bot]andClaude 7128ee7576 build: bundle lib/ with rolldown instead of webpack (#54080)
* build: bundle lib/ with rolldown instead of webpack

Replace the webpack + ts-loader pipeline that builds Electron's internal
JavaScript bundles with a small rolldown driver (build/bundle/bundle.mjs).

- build/bundle/targets.mjs describes the seven bundles (browser, renderer,
  worker, webview, isolated_renderer, node, utility) and the per-target
  behaviour that used to live in one webpack config each.
- BUILDFLAG(...) folding, the `electron` aliases, the Promise/process/
  Buffer/global providers and the profiling/try-catch wrappers are kept
  with the same semantics. Node built-ins imported from lazily evaluated
  modules stay lazy via a one line CommonJS shim so that load order
  matches the old bundles.
- Type checking moves out of the bundling step into a single
  electron_lib_typecheck action (build/bundle/typecheck.mjs) that every
  bundle depends on, instead of running seven times.
- gen-filenames.ts asks bundle.mjs for the module graph directly and
  emits a lib_typecheck_sources list for the new action.
- A few lib/ modules are adjusted for an ESM-first bundler: net.ts uses
  a plain module.exports object, web-contents.ts keeps its session
  side-effect import explicitly, and defineProperties caches the loaded
  module so each electron.* getter keeps returning the same object.

Drops webpack, webpack-cli and ts-loader from devDependencies.

* chore: wrap comment for clang-format

* build: keep bundle declarations out of the wrapper's parameter scope

node_mksnapshot compiles the js2c bundles with Node's built-in module
parameters (exports, require, module, process, internalBinding,
primordials), and a hoisted top-level `const { internalBinding } = ...`
from lib/node redeclared one of them. Wrap the bundle body in an IIFE, as
the webpack output was.

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-09-18 17:52:57 +02:00

94 lines
3.8 KiB
TypeScript

// Initialize ASAR support in fs module.
import { wrapFsWithAsar } from './asar-fs-wrapper';
wrapFsWithAsar(require('fs'));
// Hook child_process.fork.
import cp = require('child_process'); // oxlint-disable-line import/first
const originalFork = cp.fork;
cp.fork = (modulePath, args?, options?: cp.ForkOptions) => {
// Parse optional args.
if (args == null) {
args = [];
} else if (typeof args === 'object' && !Array.isArray(args)) {
options = args as cp.ForkOptions;
args = [];
}
// Fallback to original fork to report arg type errors.
if (
typeof modulePath !== 'string' ||
!Array.isArray(args) ||
(typeof options !== 'object' && typeof options !== 'undefined')
) {
return originalFork(modulePath, args, options);
}
// When forking a child script, we setup a special environment to make
// the electron binary run like upstream Node.js. With the runAsNode fuse
// disabled that environment is ignored and the child would start another
// copy of the app instead, so refuse up front.
if (!process._linkedBinding('electron_common_features').isRunAsNodeEnabled()) {
throw new Error(
'child_process.fork() is not supported when the runAsNode fuse is disabled; use utilityProcess.fork() instead'
);
}
options = options ?? {};
options.env = Object.create(options.env || process.env);
options.env!.ELECTRON_RUN_AS_NODE = '1';
// On mac the child script runs in helper executable.
if (!options.execPath && process.platform === 'darwin') {
options.execPath = process.helperExecPath;
}
return originalFork(modulePath, args, options);
};
// Hand crashpad's handler to ELECTRON_RUN_AS_NODE children so their crashes are
// reported too; shell/app/node_main.cc reads these back. Every async spawn goes
// through ChildProcess.prototype.spawn and every sync one through spawnSync,
// both with the normalized file and envPairs.
if (process.platform === 'linux') {
const { getCrashdumpSignalFD, getCrashpadHandlerPID } = process._linkedBinding('electron_common_crashpad_support');
const childProcess = require('internal/child_process') as typeof import('@node/lib/internal/child_process');
// Invalid options are left for Node's own validation to reject.
const addCrashpadEnv = (options: any) => {
if (
options?.file !== process.execPath ||
!Array.isArray(options.envPairs) ||
!options.envPairs.some((pair: unknown) => typeof pair === 'string' && pair.startsWith('ELECTRON_RUN_AS_NODE='))
) {
return;
}
const fd = getCrashdumpSignalFD();
const pid = getCrashpadHandlerPID();
if (fd !== -1 && pid !== -1) {
options.envPairs.push(`CRASHDUMP_SIGNAL_FD=${fd}`, `CRASHPAD_HANDLER_PID=${pid}`);
}
};
const { spawn } = childProcess.ChildProcess.prototype;
childProcess.ChildProcess.prototype.spawn = function (options: any) {
addCrashpadEnv(options);
return spawn.call(this, options);
};
const { spawnSync } = childProcess;
childProcess.spawnSync = (options: any) => {
addCrashpadEnv(options);
return spawnSync(options);
};
}
// Prevent Node from adding paths outside this app to search paths.
import path = require('path'); // oxlint-disable-line import/first
const Module = require('module') as NodeJS.ModuleInternal;
const resourcesPathWithTrailingSlash = process.resourcesPath + path.sep;
const originalNodeModulePaths = Module._nodeModulePaths;
Module._nodeModulePaths = function (from) {
const paths: string[] = originalNodeModulePaths(from);
const fromPath = path.resolve(from) + path.sep;
// If "from" is outside the app then we do nothing.
if (fromPath.startsWith(resourcesPathWithTrailingSlash)) {
return paths.filter(function (candidate) {
return candidate.startsWith(resourcesPathWithTrailingSlash);
});
} else {
return paths;
}
};