Compare commits

...

4 Commits

Author SHA1 Message Date
Sam Attard
225fc5aa08 chore: update lockfile 2026-04-05 05:46:21 +00:00
Sam Attard
43d61d4cd0 build: typecheck the Electron lib tree with tsgo during ninja
Adds a GN typescript_check template that runs tsgo --noEmit over a
tsconfig and writes a stamp file on success, wired into BUILD.gn as
electron_lib_typecheck. electron_js2c depends on it so a broken type
in lib/ fails 'e build'.

tsgo (@typescript/native-preview, the Go-based TypeScript compiler
preview) runs the full lib/ typecheck in ~400ms, about 6x faster than
tsc 6.0.2 (~2.3s). ts-loader previously typechecked implicitly inside
webpack and was removed in the esbuild migration, so this restores
typecheck coverage that was briefly absent on the bundle build path.

Because tsgo has no 'ignoreDiagnostics' option like ts-loader's, the
previously-silenced TS6059 and TS1111 errors needed to be fixed
properly:

  - tsconfig.electron.json drops 'rootDir: "lib"'. It was only
    meaningful for emit, and the TS 6 implicit rootDir inference plus
    the @node/* path alias was pulling Node's internal .js files
    (specifically internal/url.js with its #searchParams brand check)
    into the program.

  - tsconfig.json drops the @node/* path alias entirely. Every call
    site that used 'as typeof import("@node/lib/...")' is replaced
    with narrow structural types declared once in an ambient
    NodeInternalModules interface in typings/internal-ambient.d.ts.
    __non_webpack_require__ becomes an overloaded function that picks
    the right return type from a string-literal id argument, so call
    sites no longer need 'as' casts.

  - tsconfig.default_app.json: moduleResolution 'node' -> 'bundler'
    (TS 6 deprecates 'node').

  - typings/internal-ambient.d.ts: 'declare module NodeJS { }' ->
    'declare namespace NodeJS { }' (TS 6 rejects the module keyword
    for non-external declarations).

spec/ts-smoke/runner.js now invokes tsgo's bin instead of resolving
the 'typescript' package. The 'tsc' npm script is repointed from
'tsc' to 'tsgo' so the existing CI step
'node script/yarn.js tsc -p tsconfig.script.json' continues to run.

A small script/typecheck.js wrapper runs tsgo and writes the stamp
file for GN; the typescript_check template invokes it via a new
'tsc-check' npm script.
2026-04-05 05:46:21 +00:00
Sam Attard
646dfd24f7 build: replace webpack with esbuild for internal JS bundles
Replaces the webpack+ts-loader based bundler for Electron's 8 internal
init bundles (browser, renderer, worker, sandboxed_renderer,
isolated_renderer, node, utility, preload_realm) with a plain esbuild
driver under build/esbuild/. GN template now lives at
build/esbuild/esbuild.gni and is invoked via a new 'bundle' npm script.

Per-target configs move from build/webpack/webpack.config.<target>.js to
small data-only files under build/esbuild/configs/. ProvidePlugin's
global/Buffer/process/Promise capture moves to inject-shims under
build/esbuild/shims/. The wrapper-webpack-plugin try/catch and
___electron_webpack_init__ wrappers are applied as textual pre/postamble
inside the driver so shell/common/node_util.cc's CompileAndCall error
handling still works.

The old BUILDFLAG DefinePlugin pass is replaced with a small onLoad
regex that rewrites BUILDFLAG(NAME) to (true|false) using the
GN-generated buildflags.h. AccessDependenciesPlugin (used by
gen-filenames.ts to populate filenames.auto.gni) is replaced with
esbuild's built-in metafile via a new '--print-graph' flag.

A few source files needed small fixes to work under esbuild's
stricter CJS/ESM interop:

  - lib/browser/api/net.ts and lib/utility/api/net.ts mixed ESM
    'export function' with 'exports.x = ...' assignments, which
    esbuild treats as an ambiguous module. Switched to a single
    'module.exports = {}' with a getter for the dynamic 'online'
    property.

  - lib/common/timers-shim.ts is untouched; lib/common/init.ts no
    longer mutates the imported timers namespace (timers.setImmediate
    = wrap(...)). Under webpack the mutation applied to the bundled
    shim and was invisible to user apps; the refactor stores the
    wrapped values in local consts instead.

Drops webpack, webpack-cli, ts-loader, null-loader, and
wrapper-webpack-plugin from devDependencies. Adds esbuild.

Sequential build time for the 8 bundles drops from ~22s (webpack) to
~480ms (esbuild).
2026-04-05 05:46:21 +00:00
Sam Attard
f89c8efc9d fix: defer Wrappable destruction in SecondWeakCallback to a posted task
V8's second-pass weak callbacks run inside a
DisallowJavascriptExecutionScope: they may touch the V8 API but must
not invoke JS, directly or indirectly. Several Electron Wrappables
(WebContents in particular) emit JS events from their destructors,
so deleting synchronously inside SecondWeakCallback can crash with
"Invoke in DisallowJavascriptExecutionScope" when GC happens to
collect the JS wrapper during a foreground GC task — typically during
shutdown's uv_run drain after a leaked WebContentsView.

This was previously latent and timing-dependent (electron/electron#47420,
electron/electron#45416, podman-desktop/podman-desktop#12409). The
esbuild migration's keepNames option (which wraps every function/class
with an Object.defineProperty call) shifted heap layout enough to make
the spec/fixtures/crash-cases/webcontentsview-create-leak-exit case
reliably reproduce it on every run, giving a clean signal for the fix.

Both WrappableBase and DeprecatedWrappableBase SecondWeakCallback now
post the deletion via base::SequencedTaskRunner::GetCurrentDefault()
so the destructor (and any Emit it does) runs once V8 has left the GC
scope. Falls back to synchronous deletion if no task runner is
available (early/late process lifetime).

Fixes electron/electron#47420.
2026-04-05 05:46:19 +00:00
48 changed files with 1350 additions and 1498 deletions

View File

@@ -18,12 +18,12 @@ import("//tools/v8_context_snapshot/v8_context_snapshot.gni")
import("//v8/gni/snapshot_toolchain.gni")
import("build/asar.gni")
import("build/electron_paks.gni")
import("build/esbuild/esbuild.gni")
import("build/extract_symbols.gni")
import("build/js2c_toolchain.gni")
import("build/npm.gni")
import("build/templated_file.gni")
import("build/tsc.gni")
import("build/webpack/webpack.gni")
import("buildflags/buildflags.gni")
import("filenames.auto.gni")
import("filenames.gni")
@@ -162,75 +162,81 @@ npm_action("build_electron_definitions") {
outputs = [ "$target_gen_dir/tsc/typings/electron.d.ts" ]
}
webpack_build("electron_browser_bundle") {
typescript_check("electron_lib_typecheck") {
deps = [ ":build_electron_definitions" ]
tsconfig = "//electron/tsconfig.electron.json"
sources = auto_filenames.typecheck_sources
}
esbuild_build("electron_browser_bundle") {
deps = [ ":build_electron_definitions" ]
inputs = auto_filenames.browser_bundle_deps
config_file = "//electron/build/webpack/webpack.config.browser.js"
config_file = "//electron/build/esbuild/configs/browser.js"
out_file = "$target_gen_dir/js2c/browser_init.js"
}
webpack_build("electron_renderer_bundle") {
esbuild_build("electron_renderer_bundle") {
deps = [ ":build_electron_definitions" ]
inputs = auto_filenames.renderer_bundle_deps
config_file = "//electron/build/webpack/webpack.config.renderer.js"
config_file = "//electron/build/esbuild/configs/renderer.js"
out_file = "$target_gen_dir/js2c/renderer_init.js"
}
webpack_build("electron_worker_bundle") {
esbuild_build("electron_worker_bundle") {
deps = [ ":build_electron_definitions" ]
inputs = auto_filenames.worker_bundle_deps
config_file = "//electron/build/webpack/webpack.config.worker.js"
config_file = "//electron/build/esbuild/configs/worker.js"
out_file = "$target_gen_dir/js2c/worker_init.js"
}
webpack_build("electron_sandboxed_renderer_bundle") {
esbuild_build("electron_sandboxed_renderer_bundle") {
deps = [ ":build_electron_definitions" ]
inputs = auto_filenames.sandbox_bundle_deps
config_file = "//electron/build/webpack/webpack.config.sandboxed_renderer.js"
config_file = "//electron/build/esbuild/configs/sandboxed_renderer.js"
out_file = "$target_gen_dir/js2c/sandbox_bundle.js"
}
webpack_build("electron_isolated_renderer_bundle") {
esbuild_build("electron_isolated_renderer_bundle") {
deps = [ ":build_electron_definitions" ]
inputs = auto_filenames.isolated_bundle_deps
config_file = "//electron/build/webpack/webpack.config.isolated_renderer.js"
config_file = "//electron/build/esbuild/configs/isolated_renderer.js"
out_file = "$target_gen_dir/js2c/isolated_bundle.js"
}
webpack_build("electron_node_bundle") {
esbuild_build("electron_node_bundle") {
deps = [ ":build_electron_definitions" ]
inputs = auto_filenames.node_bundle_deps
config_file = "//electron/build/webpack/webpack.config.node.js"
config_file = "//electron/build/esbuild/configs/node.js"
out_file = "$target_gen_dir/js2c/node_init.js"
}
webpack_build("electron_utility_bundle") {
esbuild_build("electron_utility_bundle") {
deps = [ ":build_electron_definitions" ]
inputs = auto_filenames.utility_bundle_deps
config_file = "//electron/build/webpack/webpack.config.utility.js"
config_file = "//electron/build/esbuild/configs/utility.js"
out_file = "$target_gen_dir/js2c/utility_init.js"
}
webpack_build("electron_preload_realm_bundle") {
esbuild_build("electron_preload_realm_bundle") {
deps = [ ":build_electron_definitions" ]
inputs = auto_filenames.preload_realm_bundle_deps
config_file = "//electron/build/webpack/webpack.config.preload_realm.js"
config_file = "//electron/build/esbuild/configs/preload_realm.js"
out_file = "$target_gen_dir/js2c/preload_realm_bundle.js"
}
@@ -238,6 +244,7 @@ action("electron_js2c") {
deps = [
":electron_browser_bundle",
":electron_isolated_renderer_bundle",
":electron_lib_typecheck",
":electron_node_bundle",
":electron_preload_realm_bundle",
":electron_renderer_bundle",

326
build/esbuild/bundle.js Normal file
View File

@@ -0,0 +1,326 @@
#!/usr/bin/env node
// Driver script that replaces webpack for building Electron's internal
// JS bundles. Each bundle is a single esbuild invocation parameterized by
// the per-target configuration files under build/esbuild/configs.
//
// Invoked by the GN `esbuild_build` template via `npm run bundle -- …`.
'use strict';
const esbuild = require('esbuild');
const fs = require('node:fs');
const path = require('node:path');
const electronRoot = path.resolve(__dirname, '../..');
function parseArgs (argv) {
const args = {};
for (let i = 0; i < argv.length; i++) {
const a = argv[i];
if (a.startsWith('--')) {
const key = a.slice(2);
const next = argv[i + 1];
if (next === undefined || next.startsWith('--')) {
args[key] = true;
} else {
args[key] = next;
i++;
}
}
}
return args;
}
// Parse $target_gen_dir/buildflags/buildflags.h (a C++ header containing
// `#define BUILDFLAG_INTERNAL_NAME() (0|1)` lines) into a map of flag name
// to boolean. Used to seed the `define` table so that `BUILDFLAG(NAME)` call
// sites can be statically folded to `true`/`false` at build time.
function parseBuildflags (buildflagsPath) {
const flags = {};
if (!buildflagsPath) return flags;
const source = fs.readFileSync(buildflagsPath, 'utf8');
const re = /#define BUILDFLAG_INTERNAL_(.+?)\(\) \(([01])\)/g;
let match;
while ((match = re.exec(source)) !== null) {
const [, name, value] = match;
flags[name] = value === '1';
}
return flags;
}
// Return the list of esbuild `alias` entries used by every bundle. esbuild's
// alias matches the full module specifier (no `$` suffix trickery like
// webpack), so the bare `electron` alias also matches `electron/main`, etc.,
// because esbuild matches the leftmost segment first.
function buildAliases (electronAPIFile, { aliasTimers }) {
const aliases = {
electron: electronAPIFile,
'electron/main': electronAPIFile,
'electron/renderer': electronAPIFile,
'electron/common': electronAPIFile,
'electron/utility': electronAPIFile
};
// Only browser-platform bundles (sandboxed_renderer, isolated_renderer,
// preload_realm) need the timers shim — Node's `timers` builtin is not
// available there. For node-platform bundles (browser, renderer, worker,
// utility, node) the alias MUST NOT apply: lib/common/init.ts wraps the
// real Node timers and then assigns the wrappers onto globalThis. If
// those bundles saw the shim, the wrappers would recursively call back
// into globalThis.setTimeout and blow the stack.
if (aliasTimers) {
aliases.timers = path.resolve(electronRoot, 'lib', 'common', 'timers-shim.ts');
}
return aliases;
}
// esbuild's `alias` does not support wildcard prefixes like `@electron/internal/*`.
// We instead install a tiny resolve plugin that rewrites any import starting
// with that prefix to an absolute path under `lib/`. The plugin must also
// replicate esbuild's extension/index resolution because returning a path
// from onResolve bypasses the default resolver.
function internalAliasPlugin () {
const candidates = (base) => [
base,
`${base}.ts`,
`${base}.js`,
path.join(base, 'index.ts'),
path.join(base, 'index.js')
];
return {
name: 'electron-internal-alias',
setup (build) {
build.onResolve({ filter: /^@electron\/internal(\/|$)/ }, (args) => {
// Tolerate stray double slashes in import paths (webpack was lenient).
const rel = args.path.replace(/^@electron\/internal\/?/, '').replace(/^\/+/, '');
const base = path.resolve(electronRoot, 'lib', rel);
for (const c of candidates(base)) {
try {
if (fs.statSync(c).isFile()) return { path: c };
} catch { /* keep looking */ }
}
return { errors: [{ text: `Cannot resolve @electron/internal path: ${args.path}` }] };
});
}
};
}
// Rewrites `BUILDFLAG(NAME)` call-sites to `(true)` or `(false)` at load
// time, equivalent to the combination of webpack's DefinePlugin substitution
// (BUILDFLAG -> "" and NAME -> "true"/"false") that the old config used.
// Doing it in a single regex pass keeps the semantics identical and avoids
// fighting with esbuild's AST-level `define` quoting rules.
function buildflagPlugin (buildflags, { allowUnknown = false } = {}) {
return {
name: 'electron-buildflag',
setup (build) {
build.onLoad({ filter: /\.(ts|js)$/ }, async (args) => {
const source = await fs.promises.readFile(args.path, 'utf8');
if (!source.includes('BUILDFLAG(')) {
return { contents: source, loader: args.path.endsWith('.ts') ? 'ts' : 'js' };
}
const rewritten = source.replace(/BUILDFLAG\(([A-Z0-9_]+)\)/g, (_, name) => {
if (!Object.prototype.hasOwnProperty.call(buildflags, name)) {
if (allowUnknown) return '(false)';
throw new Error(`Unknown BUILDFLAG: ${name} (in ${args.path})`);
}
return `(${buildflags[name]})`;
});
return { contents: rewritten, loader: args.path.endsWith('.ts') ? 'ts' : 'js' };
});
}
};
}
// TODO(MarshallOfSound): drop this patch once evanw/esbuild#4441 lands and
// we bump esbuild — that PR adds a `__toCommonJSCached` helper for the
// inline-require path so identity is preserved upstream. Tracked at
// https://github.com/evanw/esbuild/issues/4440.
//
// esbuild's runtime emits `__toCommonJS = (mod) => __copyProps(__defProp({},
// "__esModule", { value: true }), mod)`, which allocates a fresh wrapper
// object every time `require()` resolves to a bundled ESM module. That
// breaks identity expectations our code relies on (e.g. sandboxed preloads
// expecting `require('timers') === require('node:timers')`, and the
// defineProperties getters in lib/common/define-properties.ts expecting
// stable namespaces). A cached WeakMap-backed version of __toCommonJS
// existed in older esbuild releases (see evanw/esbuild#2126) but was
// removed in evanw/esbuild@f4ff26d3 (0.14.27). Substitute a memoized
// variant in post-processing so every call site returns the same wrapper
// for the same underlying namespace, matching webpack's
// `__webpack_require__` cache semantics.
const ESBUILD_TO_COMMONJS_PATTERN =
/var __toCommonJS = \(mod\) => __copyProps\(__defProp\(\{\}, "__esModule", \{ value: true \}\), mod\);/;
const ESBUILD_TO_COMMONJS_REPLACEMENT =
'var __toCommonJS = /* @__PURE__ */ ((cache) => (mod) => {\n' +
' var cached = cache.get(mod);\n' +
' if (cached) return cached;\n' +
' var result = __copyProps(__defProp({}, "__esModule", { value: true }), mod);\n' +
' cache.set(mod, result);\n' +
' return result;\n' +
' })(new WeakMap());';
function patchToCommonJS (source) {
// Once evanw/esbuild#4441 lands, esbuild will emit `__toCommonJSCached`
// for inline require() — when we see that helper in the output, the
// upstream fix is active and this whole patch is a no-op (and should be
// deleted on the next esbuild bump).
if (source.includes('__toCommonJSCached')) {
return source;
}
if (!ESBUILD_TO_COMMONJS_PATTERN.test(source)) {
// Some bundles may not contain any ESM-shaped modules, in which case
// esbuild omits the helper entirely and there is nothing to patch.
if (source.includes('__toCommonJS')) {
throw new Error(
'esbuild bundle contains __toCommonJS but did not match the ' +
'expected pattern; the runtime helper has likely changed upstream. ' +
'Update ESBUILD_TO_COMMONJS_PATTERN in build/esbuild/bundle.js, or ' +
'delete patchToCommonJS entirely if evanw/esbuild#4441 has landed.'
);
}
return source;
}
return source.replace(ESBUILD_TO_COMMONJS_PATTERN, ESBUILD_TO_COMMONJS_REPLACEMENT);
}
// Wrap bundle source text in the same header/footer pairs webpack's
// wrapper-webpack-plugin used. The try/catch wrapper is load-bearing:
// shell/common/node_util.cc's CompileAndCall relies on it to prevent
// exceptions from tearing down bootstrap.
function applyWrappers (source, opts, outputFilename) {
let wrapped = patchToCommonJS(source);
if (opts.wrapInitWithProfilingTimeout) {
const header = 'function ___electron_webpack_init__() {';
const footer = '\n};\nif ((globalThis.process || binding.process).argv.includes("--profile-electron-init")) {\n setTimeout(___electron_webpack_init__, 0);\n} else {\n ___electron_webpack_init__();\n}';
wrapped = header + wrapped + footer;
}
if (opts.wrapInitWithTryCatch) {
const header = 'try {';
const footer = `\n} catch (err) {\n console.error('Electron ${outputFilename} script failed to run');\n console.error(err);\n}`;
wrapped = header + wrapped + footer;
}
return wrapped;
}
async function buildBundle (opts, cliArgs) {
const {
target,
alwaysHasNode,
loadElectronFromAlternateTarget,
wrapInitWithProfilingTimeout,
wrapInitWithTryCatch
} = opts;
const outputFilename = cliArgs['output-filename'] || `${target}.bundle.js`;
const outputPath = cliArgs['output-path'] || path.resolve(electronRoot, 'out');
const mode = cliArgs.mode || 'development';
const minify = mode === 'production';
const printGraph = !!cliArgs['print-graph'];
let entry = path.resolve(electronRoot, 'lib', target, 'init.ts');
if (!fs.existsSync(entry)) {
entry = path.resolve(electronRoot, 'lib', target, 'init.js');
}
const electronAPIFile = path.resolve(
electronRoot,
'lib',
loadElectronFromAlternateTarget || target,
'api',
'exports',
'electron.ts'
);
const buildflags = parseBuildflags(cliArgs.buildflags);
// Shims that stand in for webpack ProvidePlugin. Each target gets the
// minimum set of globals it needs; the capture files mirror the originals
// under lib/common so the behavior (grab globals before user code can
// delete them) is preserved exactly.
const inject = [];
if (opts.targetDeletesNodeGlobals) {
inject.push(path.resolve(__dirname, 'shims', 'node-globals-shim.js'));
}
if (!alwaysHasNode) {
inject.push(path.resolve(__dirname, 'shims', 'browser-globals-shim.js'));
}
inject.push(path.resolve(__dirname, 'shims', 'promise-shim.js'));
const result = await esbuild.build({
entryPoints: [entry],
bundle: true,
format: 'iife',
platform: alwaysHasNode ? 'node' : 'browser',
target: 'es2022',
minify,
// Preserve class/function names in both development and production so
// gin_helper-surfaced constructor names and stack traces stay readable.
// (Under webpack this only mattered when terser ran in is_official_build;
// esbuild applies the same rename pressure in dev too, so keep it on
// unconditionally for consistency.)
keepNames: true,
sourcemap: false,
logLevel: 'warning',
metafile: true,
write: false,
resolveExtensions: ['.ts', '.js'],
alias: buildAliases(electronAPIFile, { aliasTimers: !alwaysHasNode }),
inject,
define: {
__non_webpack_require__: 'require'
},
// Node internal modules we pull through __non_webpack_require__ at runtime.
// These must not be bundled — esbuild should leave the literal require()
// call alone so the outer Node scope resolves them.
external: [
'internal/modules/helpers',
'internal/modules/run_main',
'internal/fs/utils',
'internal/util',
'internal/validators',
'internal/url'
],
plugins: [
internalAliasPlugin(),
buildflagPlugin(buildflags, { allowUnknown: printGraph })
]
});
if (printGraph) {
const inputs = Object.keys(result.metafile.inputs)
.filter((p) => !p.includes('node_modules') && !p.startsWith('..'))
.map((p) => path.relative(electronRoot, path.resolve(electronRoot, p)));
process.stdout.write(JSON.stringify(inputs) + '\n');
return;
}
if (result.outputFiles.length !== 1) {
throw new Error(`Expected exactly one output file, got ${result.outputFiles.length}`);
}
const wrapped = applyWrappers(
result.outputFiles[0].text,
{ wrapInitWithProfilingTimeout, wrapInitWithTryCatch },
outputFilename
);
await fs.promises.mkdir(outputPath, { recursive: true });
await fs.promises.writeFile(path.join(outputPath, outputFilename), wrapped);
}
async function main () {
const cliArgs = parseArgs(process.argv.slice(2));
if (!cliArgs.config) {
console.error('Usage: bundle.js --config <path> [--output-filename X] [--output-path Y] [--mode development|production] [--buildflags path/to/buildflags.h] [--print-graph]');
process.exit(1);
}
const configPath = path.resolve(cliArgs.config);
const opts = require(configPath);
await buildBundle(opts, cliArgs);
}
main().catch((err) => {
console.error(err);
process.exit(1);
});

View File

@@ -0,0 +1,4 @@
module.exports = {
target: 'browser',
alwaysHasNode: true
};

View File

@@ -1,5 +1,5 @@
module.exports = require('./webpack.config.base')({
module.exports = {
target: 'isolated_renderer',
alwaysHasNode: false,
wrapInitWithTryCatch: true
});
};

View File

@@ -0,0 +1,4 @@
module.exports = {
target: 'node',
alwaysHasNode: true
};

View File

@@ -1,6 +1,6 @@
module.exports = require('./webpack.config.base')({
module.exports = {
target: 'preload_realm',
alwaysHasNode: false,
wrapInitWithProfilingTimeout: true,
wrapInitWithTryCatch: true
});
};

View File

@@ -1,7 +1,7 @@
module.exports = require('./webpack.config.base')({
module.exports = {
target: 'renderer',
alwaysHasNode: true,
targetDeletesNodeGlobals: true,
wrapInitWithProfilingTimeout: true,
wrapInitWithTryCatch: true
});
};

View File

@@ -1,6 +1,6 @@
module.exports = require('./webpack.config.base')({
module.exports = {
target: 'sandboxed_renderer',
alwaysHasNode: false,
wrapInitWithProfilingTimeout: true,
wrapInitWithTryCatch: true
});
};

View File

@@ -0,0 +1,4 @@
module.exports = {
target: 'utility',
alwaysHasNode: true
};

View File

@@ -1,7 +1,7 @@
module.exports = require('./webpack.config.base')({
module.exports = {
target: 'worker',
loadElectronFromAlternateTarget: 'renderer',
alwaysHasNode: true,
targetDeletesNodeGlobals: true,
wrapInitWithTryCatch: true
});
};

View File

@@ -1,9 +1,9 @@
import("../npm.gni")
template("webpack_build") {
assert(defined(invoker.config_file), "Need webpack config file to run")
template("esbuild_build") {
assert(defined(invoker.config_file), "Need esbuild config file to run")
assert(defined(invoker.out_file), "Need output file to run")
assert(defined(invoker.inputs), "Need webpack inputs to run")
assert(defined(invoker.inputs), "Need esbuild inputs to run")
npm_action(target_name) {
forward_variables_from(invoker,
@@ -11,11 +11,14 @@ template("webpack_build") {
"deps",
"public_deps",
])
script = "webpack"
script = "bundle"
inputs = [
invoker.config_file,
"//electron/build/webpack/webpack.config.base.js",
"//electron/build/esbuild/bundle.js",
"//electron/build/esbuild/shims/node-globals-shim.js",
"//electron/build/esbuild/shims/browser-globals-shim.js",
"//electron/build/esbuild/shims/promise-shim.js",
"//electron/tsconfig.json",
"//electron/yarn.lock",
"//electron/typings/internal-ambient.d.ts",
@@ -34,10 +37,10 @@ template("webpack_build") {
get_path_info(invoker.out_file, "file"),
"--output-path",
rebase_path(get_path_info(invoker.out_file, "dir")),
"--env",
"buildflags=" + rebase_path("$target_gen_dir/buildflags/buildflags.h"),
"--env",
"mode=" + mode,
"--buildflags",
rebase_path("$target_gen_dir/buildflags/buildflags.h"),
"--mode",
mode,
]
deps += [ "//electron/buildflags" ]

View File

@@ -0,0 +1,18 @@
// Injected into browser-platform bundles (sandboxed_renderer, isolated_renderer,
// preload_realm) where Node globals are not implicitly available. Supplies
// `Buffer`, `process`, and `global` — replacing webpack's ProvidePlugin
// polyfill injection plus webpack 5's built-in `global -> globalThis` rewrite
// that `target: 'web'` performed automatically.
//
// The `buffer` and `process/browser` imports below intentionally use the
// npm polyfill packages, not Node's built-in `node:buffer` / `node:process`
// modules, because these shims ship into browser-platform bundles that do
// not have Node globals available at runtime.
/* eslint-disable import/order, import/enforce-node-protocol-usage */
import { Buffer as _Buffer } from 'buffer';
import _process from 'process/browser';
const _global = globalThis;
export { _Buffer as Buffer, _process as process, _global as global };

View File

@@ -0,0 +1,14 @@
// Injected into renderer/worker bundles to replace webpack's ProvidePlugin
// that captured `Buffer`, `global`, and `process` before user code could
// delete them from the global scope. The Module.wrapper override in
// lib/renderer/init.ts re-injects these into user preload scripts later.
// Rip globals off of globalThis/self/window so they are captured in this
// module's closure and retained even if the caller later deletes them.
const _global = typeof globalThis !== 'undefined'
? globalThis.global
: (self || window).global;
const _process = _global.process;
const _Buffer = _global.Buffer;
export { _global as global, _process as process, _Buffer as Buffer };

View File

@@ -0,0 +1,7 @@
// Captures the original `Promise` constructor so that userland mutations of
// `global.Promise.resolve` do not affect Electron's internal code. Mirrors
// webpack's ProvidePlugin reference to lib/common/webpack-globals-provider.
const _Promise = globalThis.Promise;
export { _Promise as Promise };

View File

@@ -1,5 +1,42 @@
import("npm.gni")
# Runs `tsgo --noEmit` over a tsconfig via the `tsc-check` npm script (which
# wraps script/typecheck.js) and writes a stamp on success. Use this to gate
# downstream targets on a successful typecheck without emitting JS.
template("typescript_check") {
assert(defined(invoker.tsconfig), "Need tsconfig name to run")
assert(defined(invoker.sources), "Need tsc sources to run")
npm_action(target_name) {
forward_variables_from(invoker,
[
"deps",
"public_deps",
])
script = "tsc-check"
sources = invoker.sources
inputs = [
invoker.tsconfig,
"//electron/tsconfig.json",
"//electron/yarn.lock",
"//electron/script/typecheck.js",
"//electron/typings/internal-ambient.d.ts",
"//electron/typings/internal-electron.d.ts",
]
stamp_file = "$target_gen_dir/$target_name.stamp"
outputs = [ stamp_file ]
args = [
"--tsconfig",
rebase_path(invoker.tsconfig),
"--stamp",
rebase_path(stamp_file),
]
}
}
template("typescript_build") {
assert(defined(invoker.tsconfig), "Need tsconfig name to run")
assert(defined(invoker.sources), "Need tsc sources to run")

View File

@@ -1,172 +0,0 @@
const TerserPlugin = require('terser-webpack-plugin');
const webpack = require('webpack');
const WrapperPlugin = require('wrapper-webpack-plugin');
const fs = require('node:fs');
const path = require('node:path');
const electronRoot = path.resolve(__dirname, '../..');
class AccessDependenciesPlugin {
apply (compiler) {
compiler.hooks.compilation.tap('AccessDependenciesPlugin', compilation => {
compilation.hooks.finishModules.tap('AccessDependenciesPlugin', modules => {
const filePaths = modules.map(m => m.resource).filter(p => p).map(p => path.relative(electronRoot, p));
console.info(JSON.stringify(filePaths));
});
});
}
}
module.exports = ({
alwaysHasNode,
loadElectronFromAlternateTarget,
targetDeletesNodeGlobals,
target,
wrapInitWithProfilingTimeout,
wrapInitWithTryCatch
}) => {
let entry = path.resolve(electronRoot, 'lib', target, 'init.ts');
if (!fs.existsSync(entry)) {
entry = path.resolve(electronRoot, 'lib', target, 'init.js');
}
const electronAPIFile = path.resolve(electronRoot, 'lib', loadElectronFromAlternateTarget || target, 'api', 'exports', 'electron.ts');
return (env = {}, argv = {}) => {
const onlyPrintingGraph = !!env.PRINT_WEBPACK_GRAPH;
const outputFilename = argv['output-filename'] || `${target}.bundle.js`;
const defines = {
BUILDFLAG: onlyPrintingGraph ? '(a => a)' : ''
};
if (env.buildflags) {
const flagFile = fs.readFileSync(env.buildflags, 'utf8');
for (const line of flagFile.split(/(\r\n|\r|\n)/g)) {
const flagMatch = line.match(/#define BUILDFLAG_INTERNAL_(.+?)\(\) \(([01])\)/);
if (flagMatch) {
const [, flagName, flagValue] = flagMatch;
defines[flagName] = JSON.stringify(Boolean(parseInt(flagValue, 10)));
}
}
}
const ignoredModules = [];
const plugins = [];
if (onlyPrintingGraph) {
plugins.push(new AccessDependenciesPlugin());
}
if (targetDeletesNodeGlobals) {
plugins.push(new webpack.ProvidePlugin({
Buffer: ['@electron/internal/common/webpack-provider', 'Buffer'],
global: ['@electron/internal/common/webpack-provider', '_global'],
process: ['@electron/internal/common/webpack-provider', 'process']
}));
}
// Webpack 5 no longer polyfills process or Buffer.
if (!alwaysHasNode) {
plugins.push(new webpack.ProvidePlugin({
Buffer: ['buffer', 'Buffer'],
process: 'process/browser'
}));
}
plugins.push(new webpack.ProvidePlugin({
Promise: ['@electron/internal/common/webpack-globals-provider', 'Promise']
}));
plugins.push(new webpack.DefinePlugin(defines));
if (wrapInitWithProfilingTimeout) {
plugins.push(new WrapperPlugin({
header: 'function ___electron_webpack_init__() {',
footer: `
};
if ((globalThis.process || binding.process).argv.includes("--profile-electron-init")) {
setTimeout(___electron_webpack_init__, 0);
} else {
___electron_webpack_init__();
}`
}));
}
if (wrapInitWithTryCatch) {
plugins.push(new WrapperPlugin({
header: 'try {',
footer: `
} catch (err) {
console.error('Electron ${outputFilename} script failed to run');
console.error(err);
}`
}));
}
return {
mode: 'development',
devtool: false,
entry,
target: alwaysHasNode ? 'node' : 'web',
output: {
filename: outputFilename
},
resolve: {
alias: {
'@electron/internal': path.resolve(electronRoot, 'lib'),
electron$: electronAPIFile,
'electron/main$': electronAPIFile,
'electron/renderer$': electronAPIFile,
'electron/common$': electronAPIFile,
'electron/utility$': electronAPIFile,
// Force timers to resolve to our own shim that doesn't use window.postMessage
timers: path.resolve(electronRoot, 'lib', 'common', 'timers-shim.ts')
},
extensions: ['.ts', '.js'],
fallback: {
// We provide our own "timers" import above, any usage of setImmediate inside
// one of our renderer bundles should import it from the 'timers' package
setImmediate: false
}
},
module: {
rules: [{
test: (moduleName) => !onlyPrintingGraph && ignoredModules.includes(moduleName),
loader: 'null-loader'
}, {
test: /\.ts$/,
loader: 'ts-loader',
options: {
configFile: path.resolve(electronRoot, 'tsconfig.electron.json'),
transpileOnly: onlyPrintingGraph,
ignoreDiagnostics: [
// File '{0}' is not under 'rootDir' '{1}'.
6059,
// Private field '{0}' must be declared in an enclosing class.
1111
]
}
}]
},
node: {
__dirname: false,
__filename: false
},
optimization: {
minimize: env.mode === 'production',
minimizer: [
new TerserPlugin({
terserOptions: {
keep_classnames: true,
keep_fnames: true
}
})
]
},
plugins
};
};
};

View File

@@ -1,4 +0,0 @@
module.exports = require('./webpack.config.base')({
target: 'browser',
alwaysHasNode: true
});

View File

@@ -1,4 +0,0 @@
module.exports = require('./webpack.config.base')({
target: 'node',
alwaysHasNode: true
});

View File

@@ -1,4 +0,0 @@
module.exports = require('./webpack.config.base')({
target: 'utility',
alwaysHasNode: true
});

View File

@@ -174,62 +174,10 @@ auto_filenames = {
"docs/api/structures/window-session-end-event.md",
]
sandbox_bundle_deps = [
"lib/common/api/native-image.ts",
"lib/common/define-properties.ts",
"lib/common/deprecate.ts",
"lib/common/ipc-messages.ts",
"lib/common/timers-shim.ts",
"lib/common/web-view-methods.ts",
"lib/common/webpack-globals-provider.ts",
"lib/renderer/api/context-bridge.ts",
"lib/renderer/api/crash-reporter.ts",
"lib/renderer/api/ipc-renderer.ts",
"lib/renderer/api/shared-texture.ts",
"lib/renderer/api/web-frame.ts",
"lib/renderer/api/web-utils.ts",
"lib/renderer/common-init.ts",
"lib/renderer/inspector.ts",
"lib/renderer/ipc-native-setup.ts",
"lib/renderer/ipc-renderer-bindings.ts",
"lib/renderer/ipc-renderer-internal-utils.ts",
"lib/renderer/ipc-renderer-internal.ts",
"lib/renderer/security-warnings.ts",
"lib/renderer/web-frame-init.ts",
"lib/renderer/web-view/guest-view-internal.ts",
"lib/renderer/web-view/web-view-attributes.ts",
"lib/renderer/web-view/web-view-constants.ts",
"lib/renderer/web-view/web-view-element.ts",
"lib/renderer/web-view/web-view-impl.ts",
"lib/renderer/web-view/web-view-init.ts",
"lib/renderer/window-setup.ts",
"lib/sandboxed_renderer/api/exports/electron.ts",
"lib/sandboxed_renderer/api/module-list.ts",
"lib/sandboxed_renderer/init.ts",
"lib/sandboxed_renderer/pre-init.ts",
"lib/sandboxed_renderer/preload.ts",
"package.json",
"tsconfig.electron.json",
"tsconfig.json",
"typings/internal-ambient.d.ts",
"typings/internal-electron.d.ts",
]
isolated_bundle_deps = [
"lib/common/web-view-methods.ts",
"lib/isolated_renderer/init.ts",
"lib/renderer/web-view/web-view-attributes.ts",
"lib/renderer/web-view/web-view-constants.ts",
"lib/renderer/web-view/web-view-element.ts",
"lib/renderer/web-view/web-view-impl.ts",
"package.json",
"tsconfig.electron.json",
"tsconfig.json",
"typings/internal-ambient.d.ts",
"typings/internal-electron.d.ts",
]
browser_bundle_deps = [
typecheck_sources = [
"build/esbuild/shims/browser-globals-shim.js",
"build/esbuild/shims/node-globals-shim.js",
"build/esbuild/shims/promise-shim.js",
"lib/browser/api/app.ts",
"lib/browser/api/auto-updater.ts",
"lib/browser/api/auto-updater/auto-updater-msix.ts",
@@ -300,8 +248,189 @@ auto_filenames = {
"lib/common/deprecate.ts",
"lib/common/init.ts",
"lib/common/ipc-messages.ts",
"lib/common/timers-shim.ts",
"lib/common/web-view-methods.ts",
"lib/isolated_renderer/init.ts",
"lib/node/asar-fs-wrapper.ts",
"lib/node/init.ts",
"lib/preload_realm/api/exports/electron.ts",
"lib/preload_realm/api/module-list.ts",
"lib/preload_realm/init.ts",
"lib/renderer/api/clipboard.ts",
"lib/renderer/api/context-bridge.ts",
"lib/renderer/api/crash-reporter.ts",
"lib/renderer/api/exports/electron.ts",
"lib/renderer/api/ipc-renderer.ts",
"lib/renderer/api/module-list.ts",
"lib/renderer/api/shared-texture.ts",
"lib/renderer/api/web-frame.ts",
"lib/renderer/api/web-utils.ts",
"lib/renderer/common-init.ts",
"lib/renderer/init.ts",
"lib/renderer/inspector.ts",
"lib/renderer/ipc-native-setup.ts",
"lib/renderer/ipc-renderer-bindings.ts",
"lib/renderer/ipc-renderer-internal-utils.ts",
"lib/renderer/ipc-renderer-internal.ts",
"lib/renderer/security-warnings.ts",
"lib/renderer/web-frame-init.ts",
"lib/renderer/web-view/guest-view-internal.ts",
"lib/renderer/web-view/web-view-attributes.ts",
"lib/renderer/web-view/web-view-constants.ts",
"lib/renderer/web-view/web-view-element.ts",
"lib/renderer/web-view/web-view-impl.ts",
"lib/renderer/web-view/web-view-init.ts",
"lib/renderer/window-setup.ts",
"lib/sandboxed_renderer/api/exports/electron.ts",
"lib/sandboxed_renderer/api/module-list.ts",
"lib/sandboxed_renderer/init.ts",
"lib/sandboxed_renderer/pre-init.ts",
"lib/sandboxed_renderer/preload.ts",
"lib/utility/api/exports/electron.ts",
"lib/utility/api/module-list.ts",
"lib/utility/api/net.ts",
"lib/utility/init.ts",
"lib/utility/parent-port.ts",
"lib/worker/init.ts",
"package.json",
"tsconfig.electron.json",
"tsconfig.json",
"typings/internal-ambient.d.ts",
"typings/internal-electron.d.ts",
]
sandbox_bundle_deps = [
"build/esbuild/shims/browser-globals-shim.js",
"build/esbuild/shims/promise-shim.js",
"lib/common/api/native-image.ts",
"lib/common/define-properties.ts",
"lib/common/deprecate.ts",
"lib/common/ipc-messages.ts",
"lib/common/timers-shim.ts",
"lib/common/web-view-methods.ts",
"lib/renderer/api/context-bridge.ts",
"lib/renderer/api/crash-reporter.ts",
"lib/renderer/api/ipc-renderer.ts",
"lib/renderer/api/shared-texture.ts",
"lib/renderer/api/web-frame.ts",
"lib/renderer/api/web-utils.ts",
"lib/renderer/common-init.ts",
"lib/renderer/inspector.ts",
"lib/renderer/ipc-native-setup.ts",
"lib/renderer/ipc-renderer-bindings.ts",
"lib/renderer/ipc-renderer-internal-utils.ts",
"lib/renderer/ipc-renderer-internal.ts",
"lib/renderer/security-warnings.ts",
"lib/renderer/web-frame-init.ts",
"lib/renderer/web-view/guest-view-internal.ts",
"lib/renderer/web-view/web-view-attributes.ts",
"lib/renderer/web-view/web-view-constants.ts",
"lib/renderer/web-view/web-view-element.ts",
"lib/renderer/web-view/web-view-impl.ts",
"lib/renderer/web-view/web-view-init.ts",
"lib/renderer/window-setup.ts",
"lib/sandboxed_renderer/api/exports/electron.ts",
"lib/sandboxed_renderer/api/module-list.ts",
"lib/sandboxed_renderer/init.ts",
"lib/sandboxed_renderer/pre-init.ts",
"lib/sandboxed_renderer/preload.ts",
"package.json",
"tsconfig.electron.json",
"tsconfig.json",
"typings/internal-ambient.d.ts",
"typings/internal-electron.d.ts",
]
isolated_bundle_deps = [
"build/esbuild/shims/browser-globals-shim.js",
"build/esbuild/shims/promise-shim.js",
"lib/common/web-view-methods.ts",
"lib/isolated_renderer/init.ts",
"lib/renderer/web-view/web-view-attributes.ts",
"lib/renderer/web-view/web-view-constants.ts",
"lib/renderer/web-view/web-view-element.ts",
"lib/renderer/web-view/web-view-impl.ts",
"package.json",
"tsconfig.electron.json",
"tsconfig.json",
"typings/internal-ambient.d.ts",
"typings/internal-electron.d.ts",
]
browser_bundle_deps = [
"build/esbuild/shims/promise-shim.js",
"lib/browser/api/app.ts",
"lib/browser/api/auto-updater.ts",
"lib/browser/api/auto-updater/auto-updater-msix.ts",
"lib/browser/api/auto-updater/auto-updater-native.ts",
"lib/browser/api/auto-updater/auto-updater-win.ts",
"lib/browser/api/auto-updater/msix-update-win.ts",
"lib/browser/api/auto-updater/squirrel-update-win.ts",
"lib/browser/api/base-window.ts",
"lib/browser/api/browser-view.ts",
"lib/browser/api/browser-window.ts",
"lib/browser/api/clipboard.ts",
"lib/browser/api/content-tracing.ts",
"lib/browser/api/crash-reporter.ts",
"lib/browser/api/desktop-capturer.ts",
"lib/browser/api/dialog.ts",
"lib/browser/api/exports/electron.ts",
"lib/browser/api/global-shortcut.ts",
"lib/browser/api/in-app-purchase.ts",
"lib/browser/api/ipc-main.ts",
"lib/browser/api/menu-item-roles.ts",
"lib/browser/api/menu-item.ts",
"lib/browser/api/menu-utils.ts",
"lib/browser/api/menu.ts",
"lib/browser/api/message-channel.ts",
"lib/browser/api/module-list.ts",
"lib/browser/api/native-theme.ts",
"lib/browser/api/net-fetch.ts",
"lib/browser/api/net-log.ts",
"lib/browser/api/net.ts",
"lib/browser/api/notification.ts",
"lib/browser/api/power-monitor.ts",
"lib/browser/api/power-save-blocker.ts",
"lib/browser/api/protocol.ts",
"lib/browser/api/push-notifications.ts",
"lib/browser/api/safe-storage.ts",
"lib/browser/api/screen.ts",
"lib/browser/api/service-worker-main.ts",
"lib/browser/api/session.ts",
"lib/browser/api/share-menu.ts",
"lib/browser/api/shared-texture.ts",
"lib/browser/api/system-preferences.ts",
"lib/browser/api/touch-bar.ts",
"lib/browser/api/tray.ts",
"lib/browser/api/utility-process.ts",
"lib/browser/api/view.ts",
"lib/browser/api/views/image-view.ts",
"lib/browser/api/web-contents-view.ts",
"lib/browser/api/web-contents.ts",
"lib/browser/api/web-frame-main.ts",
"lib/browser/default-menu.ts",
"lib/browser/devtools.ts",
"lib/browser/guest-view-manager.ts",
"lib/browser/guest-window-manager.ts",
"lib/browser/init.ts",
"lib/browser/ipc-dispatch.ts",
"lib/browser/ipc-main-impl.ts",
"lib/browser/ipc-main-internal-utils.ts",
"lib/browser/ipc-main-internal.ts",
"lib/browser/message-port-main.ts",
"lib/browser/parse-features-string.ts",
"lib/browser/rpc-server.ts",
"lib/browser/web-view-events.ts",
"lib/common/api/module-list.ts",
"lib/common/api/native-image.ts",
"lib/common/api/net-client-request.ts",
"lib/common/api/shell.ts",
"lib/common/define-properties.ts",
"lib/common/deprecate.ts",
"lib/common/init.ts",
"lib/common/ipc-messages.ts",
"lib/common/timers-shim.ts",
"lib/common/web-view-methods.ts",
"lib/common/webpack-globals-provider.ts",
"package.json",
"tsconfig.electron.json",
"tsconfig.json",
@@ -310,6 +439,8 @@ auto_filenames = {
]
renderer_bundle_deps = [
"build/esbuild/shims/node-globals-shim.js",
"build/esbuild/shims/promise-shim.js",
"lib/common/api/module-list.ts",
"lib/common/api/native-image.ts",
"lib/common/api/shell.ts",
@@ -317,8 +448,8 @@ auto_filenames = {
"lib/common/deprecate.ts",
"lib/common/init.ts",
"lib/common/ipc-messages.ts",
"lib/common/timers-shim.ts",
"lib/common/web-view-methods.ts",
"lib/common/webpack-provider.ts",
"lib/renderer/api/clipboard.ts",
"lib/renderer/api/context-bridge.ts",
"lib/renderer/api/crash-reporter.ts",
@@ -352,6 +483,8 @@ auto_filenames = {
]
worker_bundle_deps = [
"build/esbuild/shims/node-globals-shim.js",
"build/esbuild/shims/promise-shim.js",
"lib/common/api/module-list.ts",
"lib/common/api/native-image.ts",
"lib/common/api/shell.ts",
@@ -359,7 +492,7 @@ auto_filenames = {
"lib/common/deprecate.ts",
"lib/common/init.ts",
"lib/common/ipc-messages.ts",
"lib/common/webpack-provider.ts",
"lib/common/timers-shim.ts",
"lib/renderer/api/clipboard.ts",
"lib/renderer/api/context-bridge.ts",
"lib/renderer/api/crash-reporter.ts",
@@ -381,6 +514,7 @@ auto_filenames = {
]
node_bundle_deps = [
"build/esbuild/shims/promise-shim.js",
"lib/node/asar-fs-wrapper.ts",
"lib/node/init.ts",
"package.json",
@@ -391,6 +525,7 @@ auto_filenames = {
]
utility_bundle_deps = [
"build/esbuild/shims/promise-shim.js",
"lib/browser/api/net-fetch.ts",
"lib/browser/api/system-preferences.ts",
"lib/browser/message-port-main.ts",
@@ -398,7 +533,7 @@ auto_filenames = {
"lib/common/define-properties.ts",
"lib/common/deprecate.ts",
"lib/common/init.ts",
"lib/common/webpack-globals-provider.ts",
"lib/common/timers-shim.ts",
"lib/utility/api/exports/electron.ts",
"lib/utility/api/module-list.ts",
"lib/utility/api/net.ts",
@@ -412,10 +547,11 @@ auto_filenames = {
]
preload_realm_bundle_deps = [
"build/esbuild/shims/browser-globals-shim.js",
"build/esbuild/shims/promise-shim.js",
"lib/common/api/native-image.ts",
"lib/common/define-properties.ts",
"lib/common/ipc-messages.ts",
"lib/common/webpack-globals-provider.ts",
"lib/preload_realm/api/exports/electron.ts",
"lib/preload_realm/api/module-list.ts",
"lib/preload_realm/init.ts",

View File

@@ -5,23 +5,27 @@ import type { ClientRequestConstructorOptions } from 'electron/main';
const { isOnline } = process._linkedBinding('electron_common_net');
export function request (options: ClientRequestConstructorOptions | string, callback?: (message: IncomingMessage) => void) {
function request (options: ClientRequestConstructorOptions | string, callback?: (message: IncomingMessage) => void) {
if (!app.isReady()) {
throw new Error('net module can only be used after app is ready');
}
return new ClientRequest(options, callback);
}
export function fetch (input: RequestInfo, init?: RequestInit): Promise<Response> {
function fetch (input: RequestInfo, init?: RequestInit): Promise<Response> {
return session.defaultSession.fetch(input, init);
}
export function resolveHost (host: string, options?: Electron.ResolveHostOptions): Promise<Electron.ResolvedHost> {
function resolveHost (host: string, options?: Electron.ResolveHostOptions): Promise<Electron.ResolvedHost> {
return session.defaultSession.resolveHost(host, options);
}
exports.isOnline = isOnline;
Object.defineProperty(exports, 'online', {
get: () => isOnline()
});
module.exports = {
request,
fetch,
resolveHost,
isOnline,
get online () {
return isOnline();
}
};

View File

@@ -181,7 +181,7 @@ delete process.appCodeLoaded;
if (packagePath) {
// Finally load app's main.js and transfer control to C++.
if ((packageJson.type === 'module' && !mainStartupScript.endsWith('.cjs')) || mainStartupScript.endsWith('.mjs')) {
const { runEntryPointWithESMLoader } = __non_webpack_require__('internal/modules/run_main') as typeof import('@node/lib/internal/modules/run_main');
const { runEntryPointWithESMLoader } = __non_webpack_require__('internal/modules/run_main');
const main = (require('url') as typeof url).pathToFileURL(path.join(packagePath, mainStartupScript));
runEntryPointWithESMLoader(async (cascadedLoader: any) => {
try {

View File

@@ -1,4 +1,4 @@
import timers = require('timers');
import * as timers from 'timers';
import * as util from 'util';
import type * as stream from 'stream';
@@ -41,15 +41,15 @@ function wrap <T extends AnyFn> (func: T, wrapper: (fn: AnyFn) => T) {
// initiatively activate the uv loop once process.nextTick and setImmediate is
// called.
process.nextTick = wrapWithActivateUvLoop(process.nextTick);
global.setImmediate = timers.setImmediate = wrapWithActivateUvLoop(timers.setImmediate);
global.setImmediate = wrapWithActivateUvLoop(timers.setImmediate);
global.clearImmediate = timers.clearImmediate;
// setTimeout needs to update the polling timeout of the event loop, when
// called under Chromium's event loop the node's event loop won't get a chance
// to update the timeout, so we have to force the node's event loop to
// recalculate the timeout in the process.
timers.setTimeout = wrapWithActivateUvLoop(timers.setTimeout);
timers.setInterval = wrapWithActivateUvLoop(timers.setInterval);
const wrappedSetTimeout = wrapWithActivateUvLoop(timers.setTimeout);
const wrappedSetInterval = wrapWithActivateUvLoop(timers.setInterval);
// Update the global version of the timer apis to use the above wrapper
// only in the process that runs node event loop alongside chromium
@@ -57,8 +57,8 @@ timers.setInterval = wrapWithActivateUvLoop(timers.setInterval);
// are deleted in these processes, see renderer/init.js for reference.
if (process.type === 'browser' ||
process.type === 'utility') {
global.setTimeout = timers.setTimeout;
global.setInterval = timers.setInterval;
global.setTimeout = wrappedSetTimeout;
global.setInterval = wrappedSetInterval;
}
if (process.platform === 'win32') {

View File

@@ -1,5 +1,5 @@
// Drop-in replacement for timers-browserify@1.4.2.
// Provides the Node.js 'timers' API surface for renderer/web webpack bundles
// Provides the Node.js 'timers' API surface for renderer/web bundles
// without relying on window.postMessage (which the newer timers-browserify 2.x
// polyfill uses and can interfere with Electron IPC).

View File

@@ -1,8 +0,0 @@
// Captures original globals into a scope to ensure that userland modifications do
// not impact Electron. Note that users doing:
//
// global.Promise.resolve = myFn
//
// Will mutate this captured one as well and that is OK.
export const Promise = global.Promise;

View File

@@ -1,18 +0,0 @@
// This file provides the global, process and Buffer variables to internal
// Electron code once they have been deleted from the global scope.
//
// It does this through the ProvidePlugin in the webpack.config.base.js file
// Check out the Module.wrapper override in renderer/init.ts for more
// information on how this works and why we need it
// Rip global off of window (which is also global) so that webpack doesn't
// auto replace it with a looped reference to this file
const _global = typeof globalThis !== 'undefined' ? globalThis.global : (self || window).global;
const process = _global.process;
const Buffer = _global.Buffer;
export {
_global,
process,
Buffer
};

View File

@@ -52,20 +52,20 @@ const {
getValidatedPath,
getOptions,
getDirent
} = __non_webpack_require__('internal/fs/utils') as typeof import('@node/lib/internal/fs/utils');
} = __non_webpack_require__('internal/fs/utils');
const {
assignFunctionName
} = __non_webpack_require__('internal/util') as typeof import('@node/lib/internal/util');
} = __non_webpack_require__('internal/util');
const {
validateBoolean,
validateFunction
} = __non_webpack_require__('internal/validators') as typeof import('@node/lib/internal/validators');
} = __non_webpack_require__('internal/validators');
// In the renderer node internals use the node global URL but we do not set that to be
// the global URL instance. We need to do instanceof checks against the internal URL impl
const { URL: NodeURL } = __non_webpack_require__('internal/url') as typeof import('@node/lib/internal/url');
// the global URL instance. We need to do instanceof checks against the internal URL impl.
const { URL: NodeURL } = __non_webpack_require__('internal/url');
// Separate asar package's path from full path.
const splitPath = (archivePathOrBuffer: string | Buffer | URL) => {

View File

@@ -29,8 +29,9 @@ Module._load = function (request: string) {
// code with JavaScript.
//
// Note 3: We provide the equivalent extra variables internally through the
// webpack ProvidePlugin in webpack.config.base.js. If you add any extra
// variables to this wrapper please ensure to update that plugin as well.
// esbuild inject shim in build/esbuild/shims/node-globals-shim.js. If you
// add any extra variables to this wrapper please ensure to update that shim
// as well.
Module.wrapper = [
'(function (exports, require, module, __filename, __dirname, process, global, Buffer) { ' +
// By running the code in a new closure, it would be possible for the module
@@ -65,9 +66,9 @@ require('@electron/internal/renderer/common-init');
if (nodeIntegration) {
// Export node bindings to global.
const { makeRequireFunction } = __non_webpack_require__('internal/modules/helpers') as typeof import('@node/lib/internal/modules/helpers');
const { makeRequireFunction } = __non_webpack_require__('internal/modules/helpers');
global.module = new Module('electron/js2c/renderer_init');
global.require = makeRequireFunction(global.module) as NodeRequire;
global.require = makeRequireFunction(global.module);
// Set the __filename to the path of html file if it is file: protocol.
if (window.location.protocol === 'file:') {
@@ -152,7 +153,7 @@ if (cjsPreloads.length) {
}
}
if (esmPreloads.length) {
const { runEntryPointWithESMLoader } = __non_webpack_require__('internal/modules/run_main') as typeof import('@node/lib/internal/modules/run_main');
const { runEntryPointWithESMLoader } = __non_webpack_require__('internal/modules/run_main');
runEntryPointWithESMLoader(async (cascadedLoader: any) => {
// Load the preload scripts.

View File

@@ -5,18 +5,20 @@ import type { ClientRequestConstructorOptions, IncomingMessage } from 'electron/
const { isOnline, resolveHost } = process._linkedBinding('electron_common_net');
export function request (options: ClientRequestConstructorOptions | string, callback?: (message: IncomingMessage) => void) {
function request (options: ClientRequestConstructorOptions | string, callback?: (message: IncomingMessage) => void) {
return new ClientRequest(options, callback);
}
export function fetch (input: RequestInfo, init?: RequestInit): Promise<Response> {
function fetch (input: RequestInfo, init?: RequestInit): Promise<Response> {
return fetchWithSession(input, init, undefined, request);
}
exports.resolveHost = resolveHost;
exports.isOnline = isOnline;
Object.defineProperty(exports, 'online', {
get: () => isOnline()
});
module.exports = {
request,
fetch,
resolveHost,
isOnline,
get online () {
return isOnline();
}
};

View File

@@ -36,7 +36,7 @@ parentPort.on('removeListener', (name: string) => {
});
// Finally load entry script.
const { runEntryPointWithESMLoader } = __non_webpack_require__('internal/modules/run_main') as typeof import('@node/lib/internal/modules/run_main');
const { runEntryPointWithESMLoader } = __non_webpack_require__('internal/modules/run_main');
const mainEntry = pathToFileURL(entryScript);
runEntryPointWithESMLoader(async (cascadedLoader: any) => {

View File

@@ -13,9 +13,9 @@ require('@electron/internal/common/init');
const { hasSwitch, getSwitchValue } = process._linkedBinding('electron_common_command_line');
// Export node bindings to global.
const { makeRequireFunction } = __non_webpack_require__('internal/modules/helpers') as typeof import('@node/lib/internal/modules/helpers');
const { makeRequireFunction } = __non_webpack_require__('internal/modules/helpers');
global.module = new Module('electron/js2c/worker_init');
global.require = makeRequireFunction(global.module) as NodeRequire;
global.require = makeRequireFunction(global.module);
// See WebWorkerObserver::WorkerScriptReadyForEvaluation.
if ((globalThis as any).blinkfetch) {

View File

@@ -23,10 +23,12 @@
"@types/temp": "^0.9.4",
"@typescript-eslint/eslint-plugin": "^8.32.1",
"@typescript-eslint/parser": "^8.7.0",
"@typescript/native-preview": "^7.0.0-dev.20260324.1",
"@xmldom/xmldom": "^0.8.11",
"buffer": "^6.0.3",
"chalk": "^4.1.0",
"check-for-leaks": "^1.2.1",
"esbuild": "^0.25.0",
"eslint": "^8.57.1",
"eslint-config-standard": "^17.1.0",
"eslint-plugin-import": "^2.32.0",
@@ -43,20 +45,15 @@
"markdownlint-cli2": "^0.18.0",
"minimist": "^1.2.8",
"node-gyp": "^11.4.2",
"null-loader": "^4.0.1",
"pre-flight": "^2.0.0",
"process": "^0.11.10",
"semver": "^7.6.3",
"stream-json": "^1.9.1",
"tap-xunit": "^2.4.1",
"temp": "^0.9.4",
"ts-loader": "^8.0.2",
"ts-node": "6.2.0",
"typescript": "^5.8.3",
"url": "^0.11.4",
"webpack": "^5.104.1",
"webpack-cli": "^6.0.1",
"wrapper-webpack-plugin": "^2.2.0",
"yaml": "^2.8.1"
},
"private": true,
@@ -93,8 +90,9 @@
"repl": "node ./script/start.js --interactive",
"start": "node ./script/start.js",
"test": "node ./script/spec-runner.js",
"tsc": "tsc",
"webpack": "webpack"
"tsc": "tsgo",
"tsc-check": "node script/typecheck.js",
"bundle": "node build/esbuild/bundle.js"
},
"license": "MIT",
"author": "Electron Community",

View File

@@ -1,6 +1,5 @@
import * as cp from 'node:child_process';
import * as fs from 'node:fs';
import * as os from 'node:os';
import * as path from 'node:path';
const rootPath = path.resolve(__dirname, '..');
@@ -16,52 +15,24 @@ const allDocs = fs.readdirSync(path.resolve(__dirname, '../docs/api'))
const typingFiles = fs.readdirSync(path.resolve(__dirname, '../typings')).map(child => `typings/${child}`);
const main = async () => {
const webpackTargets = [
{
name: 'sandbox_bundle_deps',
config: 'webpack.config.sandboxed_renderer.js'
},
{
name: 'isolated_bundle_deps',
config: 'webpack.config.isolated_renderer.js'
},
{
name: 'browser_bundle_deps',
config: 'webpack.config.browser.js'
},
{
name: 'renderer_bundle_deps',
config: 'webpack.config.renderer.js'
},
{
name: 'worker_bundle_deps',
config: 'webpack.config.worker.js'
},
{
name: 'node_bundle_deps',
config: 'webpack.config.node.js'
},
{
name: 'utility_bundle_deps',
config: 'webpack.config.utility.js'
},
{
name: 'preload_realm_bundle_deps',
config: 'webpack.config.preload_realm.js'
}
const bundleTargets = [
{ name: 'sandbox_bundle_deps', config: 'sandboxed_renderer.js' },
{ name: 'isolated_bundle_deps', config: 'isolated_renderer.js' },
{ name: 'browser_bundle_deps', config: 'browser.js' },
{ name: 'renderer_bundle_deps', config: 'renderer.js' },
{ name: 'worker_bundle_deps', config: 'worker.js' },
{ name: 'node_bundle_deps', config: 'node.js' },
{ name: 'utility_bundle_deps', config: 'utility.js' },
{ name: 'preload_realm_bundle_deps', config: 'preload_realm.js' }
];
const webpackTargetsWithDeps = await Promise.all(webpackTargets.map(async webpackTarget => {
const tmpDir = await fs.promises.mkdtemp(path.resolve(os.tmpdir(), 'electron-filenames-'));
const targetsWithDeps = await Promise.all(bundleTargets.map(async bundleTarget => {
const child = cp.spawn('node', [
'./node_modules/webpack-cli/bin/cli.js',
'--config', `./build/webpack/${webpackTarget.config}`,
'--stats', 'errors-only',
'--output-path', tmpDir,
'--output-filename', `${webpackTarget.name}.measure.js`,
'--env', 'PRINT_WEBPACK_GRAPH'
'./build/esbuild/bundle.js',
'--config', `./build/esbuild/configs/${bundleTarget.config}`,
'--print-graph'
], {
cwd: path.resolve(__dirname, '..')
cwd: rootPath
});
let output = '';
child.stdout.on('data', chunk => {
@@ -71,32 +42,33 @@ const main = async () => {
await new Promise<void>((resolve, reject) => child.on('exit', (code) => {
if (code !== 0) {
console.error(output);
return reject(new Error(`Failed to list webpack dependencies for entry: ${webpackTarget.name}`));
return reject(new Error(`Failed to list bundle dependencies for entry: ${bundleTarget.name}`));
}
resolve();
}));
const webpackTargetWithDeps = {
...webpackTarget,
return {
...bundleTarget,
dependencies: (JSON.parse(output) as string[])
// Remove whitespace
.map(line => line.trim())
// Get the relative path
.map(line => path.relative(rootPath, line).replace(/\\/g, '/'))
// Only care about files in //electron
.map(line => path.relative(rootPath, path.resolve(rootPath, line)).replace(/\\/g, '/'))
.filter(line => !line.startsWith('..'))
// Only care about our own files
.filter(line => !line.startsWith('node_modules'))
// All webpack builds depend on the tsconfig and package json files
.concat(['tsconfig.json', 'tsconfig.electron.json', 'package.json', ...typingFiles])
// Make the generated list easier to read
.sort()
};
await fs.promises.rm(tmpDir, { force: true, recursive: true });
return webpackTargetWithDeps;
}));
// The typecheck step runs tsgo over tsconfig.electron.json which includes
// the whole lib/ + typings/ trees. For GN dependency tracking, list the
// union of every bundle's dependency set (lib files) plus typings, and
// dedupe.
const typecheckSources = Array.from(new Set([
...targetsWithDeps.flatMap(t => t.dependencies),
...typingFiles
])).sort();
fs.writeFileSync(
gniPath,
`# THIS FILE IS AUTO-GENERATED, PLEASE DO NOT EDIT BY HAND
@@ -105,7 +77,11 @@ auto_filenames = {
${allDocs.map(doc => ` "${doc}",`).join('\n')}
]
${webpackTargetsWithDeps.map(target => ` ${target.name} = [
typecheck_sources = [
${typecheckSources.map(src => ` "${src}",`).join('\n')}
]
${targetsWithDeps.map(target => ` ${target.name} = [
${target.dependencies.map(dep => ` "${dep}",`).join('\n')}
]`).join('\n\n')}
}

View File

@@ -1,6 +1,6 @@
import { Octokit } from '@octokit/rest';
import * as assert from 'node:assert';
import { strict as assert } from 'node:assert';
import { createGitHubTokenStrategy } from './github-token';
import { ELECTRON_ORG, ELECTRON_REPO } from './types';

View File

@@ -1,6 +1,6 @@
import * as minimist from 'minimist';
import * as streamChain from 'stream-chain';
import * as streamJson from 'stream-json';
import minimist = require('minimist');
import streamChain = require('stream-chain');
import streamJson = require('stream-json');
import { ignore as streamJsonIgnore } from 'stream-json/filters/Ignore';
import { streamArray as streamJsonStreamArray } from 'stream-json/streamers/StreamArray';

58
script/typecheck.js Normal file
View File

@@ -0,0 +1,58 @@
#!/usr/bin/env node
// Runs `tsgo --noEmit -p <tsconfig>` and writes a stamp file on success,
// so GN can track typecheck results as a build output.
//
// Usage: node script/typecheck.js --tsconfig <path> --stamp <path>
'use strict';
const cp = require('node:child_process');
const fs = require('node:fs');
const path = require('node:path');
function parseArgs (argv) {
const out = {};
for (let i = 0; i < argv.length; i++) {
const a = argv[i];
if (a.startsWith('--')) {
const next = argv[i + 1];
if (next === undefined || next.startsWith('--')) {
out[a.slice(2)] = true;
} else {
out[a.slice(2)] = next;
i++;
}
}
}
return out;
}
const args = parseArgs(process.argv.slice(2));
if (!args.tsconfig || !args.stamp) {
console.error('Usage: typecheck.js --tsconfig <path> --stamp <path>');
process.exit(1);
}
const electronRoot = path.resolve(__dirname, '..');
// Resolve tsgo's bin entry directly from the package's `bin` map and run it
// via the current Node executable. We can't `require.resolve` the bin path
// (the package's `exports` field doesn't expose it) and we can't spawn
// `node_modules/.bin/tsgo` directly on Windows (it's a `.cmd` shim).
const tsgoPkgPath = require.resolve('@typescript/native-preview/package.json', {
paths: [electronRoot]
});
const tsgoPkg = JSON.parse(fs.readFileSync(tsgoPkgPath, 'utf8'));
const tsgoEntry = path.resolve(path.dirname(tsgoPkgPath), tsgoPkg.bin.tsgo);
const child = cp.spawnSync(
process.execPath,
[tsgoEntry, '--noEmit', '-p', path.resolve(args.tsconfig)],
{ cwd: electronRoot, stdio: 'inherit' }
);
if (child.status !== 0) {
process.exit(child.status || 1);
}
fs.mkdirSync(path.dirname(args.stamp), { recursive: true });
fs.writeFileSync(args.stamp, '');

View File

@@ -4,6 +4,7 @@
#include "shell/common/gin_helper/wrappable.h"
#include "base/task/sequenced_task_runner.h"
#include "gin/object_template_builder.h"
#include "gin/public/isolate_holder.h"
#include "shell/common/gin_helper/dictionary.h"
@@ -90,7 +91,22 @@ void WrappableBase::SecondWeakCallback(
if (gin::IsolateHolder::DestroyedMicrotasksRunner()) {
return;
}
delete static_cast<WrappableBase*>(data.GetInternalField(0));
// Defer destruction to a posted task. V8's second-pass weak callbacks run
// inside a DisallowJavascriptExecutionScope (they may touch the V8 API but
// must not invoke JS). Several Electron Wrappables (e.g. WebContents) emit
// JS events from their destructors, so deleting synchronously here can
// crash with "Invoke in DisallowJavascriptExecutionScope" — see
// https://github.com/electron/electron/issues/47420. Posting via the
// current sequence's task runner ensures the destructor runs once V8 has
// left the GC scope. If no task runner is available (e.g. early/late in
// process lifetime), fall back to synchronous deletion.
auto* wrappable = static_cast<WrappableBase*>(data.GetInternalField(0));
if (base::SequencedTaskRunner::HasCurrentDefault()) {
base::SequencedTaskRunner::GetCurrentDefault()->DeleteSoon(FROM_HERE,
wrappable);
} else {
delete wrappable;
}
}
DeprecatedWrappableBase::DeprecatedWrappableBase() = default;
@@ -126,9 +142,19 @@ void DeprecatedWrappableBase::SecondWeakCallback(
const v8::WeakCallbackInfo<DeprecatedWrappableBase>& data) {
if (gin::IsolateHolder::DestroyedMicrotasksRunner())
return;
// See WrappableBase::SecondWeakCallback for why deletion is posted: V8's
// second-pass weak callbacks run inside a DisallowJavascriptExecutionScope,
// and several Wrappables emit JS events from their destructors.
// https://github.com/electron/electron/issues/47420
DeprecatedWrappableBase* wrappable = data.GetParameter();
if (wrappable)
if (!wrappable)
return;
if (base::SequencedTaskRunner::HasCurrentDefault()) {
base::SequencedTaskRunner::GetCurrentDefault()->DeleteSoon(FROM_HERE,
wrappable);
} else {
delete wrappable;
}
}
v8::MaybeLocal<v8::Object> DeprecatedWrappableBase::GetWrapperImpl(

View File

@@ -6,6 +6,7 @@
#define ELECTRON_SHELL_COMMON_GIN_HELPER_WRAPPABLE_BASE_H_
#include "base/memory/raw_ptr.h"
#include "base/task/sequenced_task_runner_helpers.h"
#include "v8/include/v8-forward.h"
namespace gin {
@@ -75,6 +76,11 @@ class DeprecatedWrappableBase {
DeprecatedWrappableBase();
virtual ~DeprecatedWrappableBase();
// SecondWeakCallback posts destruction via DeleteSoon so that destructors
// (which may emit JS events) run outside V8's GC scope. DeleteSoon needs
// access to the protected destructor.
friend class base::DeleteHelper<DeprecatedWrappableBase>;
// Overrides of this method should be declared final and not overridden again.
virtual gin::ObjectTemplateBuilder GetObjectTemplateBuilder(
v8::Isolate* isolate);

View File

@@ -53,7 +53,8 @@ v8::MaybeLocal<v8::Value> CompileAndCall(
context, v8::Null(isolate), arguments->size(), arguments->data());
// This will only be caught when something has gone terrible wrong as all
// electron scripts are wrapped in a try {} catch {} by webpack
// electron scripts are wrapped in a try {} catch {} by the esbuild bundler
// (see build/esbuild/bundle.js applyWrappers).
if (try_catch.HasCaught()) {
std::string msg = "no error message";
if (!try_catch.Message().IsEmpty()) {

View File

@@ -0,0 +1,29 @@
const { app, WebContentsView } = require('electron');
const v8 = require('node:v8');
// Force V8 to schedule incremental-marking finalization steps as foreground
// tasks on every allocation. Those tasks run inside V8's
// DisallowJavascriptExecutionScope. Prior to the fix in
// shell/common/gin_helper/wrappable.cc, gin_helper::SecondWeakCallback would
// `delete` the Wrappable synchronously inside that scope, and Wrappables
// whose destructors emit JS events (WebContents emits 'will-destroy' /
// 'destroyed') would crash with "Invoke in DisallowJavascriptExecutionScope".
//
// Regression test for https://github.com/electron/electron/issues/47420.
v8.setFlagsFromString('--stress-incremental-marking');
app.whenReady().then(() => {
// Leak several WebContentsView instances so at least one wrapper's
// collection lands in a foreground GC task during app.quit()'s uv_run
// drain. Three is enough for the crash to reproduce 10/10 on a Linux
// testing build before the fix.
// eslint-disable-next-line no-new
new WebContentsView();
// eslint-disable-next-line no-new
new WebContentsView();
// eslint-disable-next-line no-new
new WebContentsView();
app.quit();
});

View File

@@ -0,0 +1,18 @@
/* eslint-disable no-self-compare, import/enforce-node-protocol-usage */
// Regression test fixture for the esbuild __toCommonJS identity break.
// Each pair must be the exact same object reference — see the
// "module identity" describe block in spec/modules-spec.ts. The comparisons
// are intentionally self-referential and intentionally use both the bare and
// `node:`-prefixed module names, which is why no-self-compare and
// import/enforce-node-protocol-usage are disabled for this file.
const { ipcRenderer } = require('electron');
ipcRenderer.send('require-identity', {
electron: require('electron') === require('electron'),
electronCommon: require('electron/common') === require('electron'),
events: require('events') === require('node:events'),
timers: require('timers') === require('node:timers'),
url: require('url') === require('node:url'),
ipcRenderer: require('electron').ipcRenderer === require('electron').ipcRenderer,
contextBridge: require('electron').contextBridge === require('electron').contextBridge
});

View File

@@ -284,6 +284,41 @@ describe('modules support', () => {
expect(result).to.equal('function');
});
});
// Regression test for the esbuild __toCommonJS identity break: esbuild's
// runtime helper for `require()`-of-ESM allocates a fresh wrapper on
// every call (see evanw/esbuild@f4ff26d3). The bundle driver patches it
// with a WeakMap-memoized variant so module identity matches webpack's
// __webpack_require__ cache semantics. Without the patch, every getter
// on `require('electron')` that resolves to a non-default-exporting ESM
// module would yield a fresh namespace on each access.
describe('module identity', () => {
it('returns the same object for repeated require("electron") accesses in the main process', () => {
const electron = require('electron');
for (const key of Object.keys(electron)) {
// Touching `electron.net` before app ready throws — handled by the
// sandboxed-preload identity test below; everything else must be
// strictly equal across two reads.
if (key === 'net') continue;
expect((electron as any)[key]).to.equal((electron as any)[key],
`require('electron').${key} must be identity-stable across reads`);
}
});
it('returns the same object for repeated require() in a sandboxed preload', async () => {
const w = new BrowserWindow({
show: false,
webPreferences: { sandbox: true, contextIsolation: false, preload: path.join(fixtures, 'module', 'preload-require-identity.js') }
});
const result = once(w.webContents.ipc, 'require-identity');
await w.loadURL('about:blank');
const [, identities] = await result;
for (const [name, same] of Object.entries(identities)) {
expect(same).to.equal(true, `${name} must be identity-stable in sandboxed preload`);
}
w.destroy();
});
});
});
describe('esm', () => {

View File

@@ -2,8 +2,8 @@ const childProcess = require('node:child_process');
const path = require('node:path');
const typeCheck = () => {
const tscExec = path.resolve(require.resolve('typescript'), '../../bin/tsc');
const tscChild = childProcess.spawn(process.execPath, [tscExec, '--project', './ts-smoke/tsconfig.json'], {
const tsgoExec = path.resolve(require.resolve('@typescript/native-preview/package.json'), '..', 'bin', 'tsgo.js');
const tscChild = childProcess.spawn(process.execPath, [tsgoExec, '--project', './ts-smoke/tsconfig.json'], {
cwd: path.resolve(__dirname, '../')
});
tscChild.stdout.on('data', d => console.log(d.toString()));

View File

@@ -3,7 +3,7 @@
"compilerOptions": {
"rootDir": "default_app",
"module": "ESNext",
"moduleResolution": "node"
"moduleResolution": "bundler"
},
"include": [
"default_app",

View File

@@ -1,7 +1,10 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"rootDir": "lib"
// rootDir intentionally omitted: some lib/ files pull type declarations
// out of ../third_party/electron_node via the `@node/*` path alias (see
// lib/node/asar-fs-wrapper.ts) and a rootDir of 'lib' rejects those
// as outside-rootDir imports.
},
"include": [
"lib",

View File

@@ -10,14 +10,12 @@
"sourceMap": true,
"experimentalDecorators": true,
"strict": true,
"baseUrl": ".",
"allowJs": true,
"noUnusedLocals": true,
"outDir": "ts-gen",
"typeRoots" : ["./node_modules/@types", "./spec/node_modules/@types"],
"paths": {
"@electron/internal/*": ["lib/*"],
"@node/*": ["../third_party/electron_node/*"]
"@electron/internal/*": ["./lib/*"]
}
},
"exclude": [

View File

@@ -1,7 +1,37 @@
/// <reference types="webpack/module" />
declare const BUILDFLAG: (flag: boolean) => boolean;
// esbuild build/esbuild/bundle.js rewrites calls to this identifier into
// literal `require()` calls so that consumers can reach Node internal modules
// (e.g. 'internal/modules/helpers') without the bundler trying to resolve
// them. Overloads below pin the known internal-module IDs to narrow types;
// the final catch-all signature keeps less-common paths usable at the cost
// of no static type information.
interface NodeInternalModules {
'internal/modules/helpers': {
makeRequireFunction: (mod: NodeModule) => NodeRequire;
};
'internal/modules/run_main': {
runEntryPointWithESMLoader: (cb: (cascadedLoader: any) => any) => Promise<void>;
};
'internal/fs/utils': {
getValidatedPath: (path: any, ...args: any[]) => string;
getOptions: (options: any, defaultOptions?: any) => any;
getDirent: (path: string, name: string | Buffer, type: number, callback?: Function) => any;
};
'internal/util': {
assignFunctionName: (name: string | symbol, fn: Function) => Function;
};
'internal/validators': {
validateBoolean: (value: unknown, name: string) => asserts value is boolean;
validateFunction: (value: unknown, name: string) => void;
};
'internal/url': {
URL: typeof URL;
};
}
declare function __non_webpack_require__<K extends keyof NodeInternalModules>(id: K): NodeInternalModules[K];
declare function __non_webpack_require__(id: string): unknown;
declare namespace NodeJS {
interface ModuleInternal extends NodeJS.Module {
new(id: string, parent?: NodeJS.Module | null): NodeJS.Module;
@@ -282,7 +312,7 @@ declare namespace NodeJS {
}
}
declare module NodeJS {
declare namespace NodeJS {
interface Global {
require: NodeRequire;
module: NodeModule;

1463
yarn.lock

File diff suppressed because it is too large Load Diff