From 700a14f0157968ef803e61978208f4260a34a82e Mon Sep 17 00:00:00 2001 From: robobun <117481402+robobun@users.noreply.github.com> Date: Thu, 13 Aug 2026 06:22:01 +0000 Subject: [PATCH 1/8] bundler: define __require in iife output The printer rewrites require(expr), require.resolve(expr), uncalled require references and the import.meta.main lowering to the runtime's __require in every output format except cjs, but the parser only imported the runtime part defining it for esm. In iife output the part was tree-shaken and the bundle threw "__require is not defined". Enable the runtime require import for iife as well. For target node the iife runtime uses the same require shim as the browser, since an iife can neither import node:module nor read import.meta.url; this also fixes external require() calls in node iife output, whose runtime part was printed as a reference to itself. import.meta.main records the __require use in iife output and, for node, compares against the host module binding instead of __require.module, which only exists as the esm lowering. Fixes #24540 --- src/bundler/ParseTask.rs | 35 ++++-- src/bundler/bundle_v2.rs | 5 +- src/js_parser/p.rs | 5 +- src/js_printer/lib.rs | 18 ++- test/bundler/bundler_cjs.test.ts | 172 ++++++++++++++++++++++++++ test/bundler/bundler_edgecase.test.ts | 36 ++++++ 6 files changed, 256 insertions(+), 15 deletions(-) diff --git a/src/bundler/ParseTask.rs b/src/bundler/ParseTask.rs index 403b8c582d3e..9c8eb4eb1798 100644 --- a/src/bundler/ParseTask.rs +++ b/src/bundler/ParseTask.rs @@ -298,8 +298,11 @@ impl ParseTask { /// Re-export of `parse_worker::get_runtime_source` as an associated fn so /// callers can spell it `ParseTask::get_runtime_source`. #[inline] - pub(crate) fn get_runtime_source(target: options::Target) -> RuntimeSource { - parse_worker::get_runtime_source(target) + pub(crate) fn get_runtime_source( + target: options::Target, + output_format: options::Format, + ) -> RuntimeSource { + parse_worker::get_runtime_source(target, output_format) } } @@ -392,6 +395,9 @@ pub(crate) struct RuntimeSource { // Previously, Bun inlined `import.meta.require` at all usages. This broke // code that called `fn.toString()` and parsed the code outside a module // context. +// +// This is also right for iife output: Bun loads the `// @bun` output as an ES +// module, where `import.meta.require` is the only `require` there is. const RUNTIME_REQUIRE_BUN: &str = "export var __require = import.meta.require;"; const RUNTIME_REQUIRE_NODE: &str = "\ @@ -410,6 +416,10 @@ export var __require = /* @__PURE__ */ createRequire(import.meta.url); // // When bundling to node, esbuild picks this code path as well, but `globalThis.require` // is not always defined there. The `createRequire` call approach is more reliable. +// +// iife output for node uses this too (like esbuild): a script can neither `import` +// node:module nor read `import.meta.url`, and node loads the iife as CommonJS, where +// `require` is in scope for this shim to forward to. const RUNTIME_REQUIRE_OTHER: &str = "\ export var __require = /* @__PURE__ */ (x => typeof require !== 'undefined' ? require : @@ -505,7 +515,10 @@ export var __callDispose = (stack, error, hasError) => { pub mod parse_worker { use super::*; - fn get_runtime_source_comptime(target: options::Target) -> RuntimeSource { + fn get_runtime_source_comptime( + target: options::Target, + output_format: options::Format, + ) -> RuntimeSource { // The runtime module is the shared `runtime.js` body plus a per-target // `__require`/`__using` tail. Concatenating at compile time would embed // four copies of the 13 KB body, so each variant is assembled once on @@ -520,7 +533,8 @@ pub mod parse_worker { let variant = match target { options::Target::Bun => Variant::Bun, options::Target::BunMacro => Variant::BunMacro, - options::Target::Node => Variant::Node, + // node iife output uses the ambient-require shim (see RUNTIME_REQUIRE_OTHER). + options::Target::Node if output_format != options::Format::Iife => Variant::Node, _ => Variant::Other, }; static SOURCES: [bun_core::Once>; 4] = [ @@ -595,8 +609,11 @@ pub mod parse_worker { RuntimeSource { parse_task, source } } - pub(crate) fn get_runtime_source(target: options::Target) -> RuntimeSource { - get_runtime_source_comptime(target) + pub(crate) fn get_runtime_source( + target: options::Target, + output_format: options::Format, + ) -> RuntimeSource { + get_runtime_source_comptime(target, output_format) } // ─────────────────────────────────────────────────────────────────────────── @@ -2494,8 +2511,12 @@ pub mod parse_worker { opts.features.lower_using = !target.is_bun(); opts.features.hot_module_reloading = output_format == options::Format::InternalBakeDev && !task.source_index.is_runtime(); + // The printer emits the runtime's `__require` for every output format except cjs + // (see `runtime_require_ref` in the linker); this is what makes a file that uses it + // import the part defining it. opts.features.auto_polyfill_require = - output_format == options::Format::Esm && !opts.features.hot_module_reloading; + matches!(output_format, options::Format::Esm | options::Format::Iife) + && !opts.features.hot_module_reloading; opts.features.react_fast_refresh = topts.react_fast_refresh && loader.is_jsx() && !source.path.is_node_module(); opts.features.react_compiler = if topts.react_compiler.is_enabled() diff --git a/src/bundler/bundle_v2.rs b/src/bundler/bundle_v2.rs index 9a35e60e4b0f..d95221ad4db5 100644 --- a/src/bundler/bundle_v2.rs +++ b/src/bundler/bundle_v2.rs @@ -3214,7 +3214,10 @@ pub mod bv2_impl { /// Common prelude shared by all enqueue_entry_points_* variants: add the runtime task. fn enqueue_entry_points_common(&mut self) -> Result<(), Error> { // Add the runtime - let rt = ParseTask::get_runtime_source(self.transpiler.options.target); + let rt = ParseTask::get_runtime_source( + self.transpiler.options.target, + self.transpiler.options.output_format, + ); self.graph.input_files.append(crate::Graph::InputFile { source: rt.source, loader: Loader::Js, diff --git a/src/js_parser/p.rs b/src/js_parser/p.rs index 191cee597832..2f377050d5bb 100644 --- a/src/js_parser/p.rs +++ b/src/js_parser/p.rs @@ -5272,7 +5272,10 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O // // The printer can handle this for us, but we need to reference // a handle to the `__require` function. - if self.options.lower_import_meta_main_for_node_js { + // + // The non-ESM lowering is `__require.main == module`, so iife output needs + // `__require` as well (for cjs output this is a no-op and `require` is printed). + if self.options.lower_import_meta_main_for_node_js || !self.options.output_format.is_esm() { self.record_usage_of_runtime_require(); } Expr { diff --git a/src/js_printer/lib.rs b/src/js_printer/lib.rs index e64591d3ff8c..5496dfd00c6d 100644 --- a/src/js_printer/lib.rs +++ b/src/js_printer/lib.rs @@ -3031,12 +3031,18 @@ pub(crate) mod __gated_printer { } if self.options.target == bun_ast::Target::Node { - // "__require.module" - if let Some(require) = self.options.require_ref { - self.print_symbol(require); - self.print(b".module"); - } else { - self.print(b"module"); + match self.options.require_ref { + // "__require.module": ESM output has no `module` binding, + // so this compares against the createRequire() function's + // own (undefined) `.module`. cjs and iife output are loaded + // as CommonJS scripts, where the host's `module` exists. + Some(require) + if self.options.module_type == bundle_opts::Format::Esm => + { + self.print_symbol(require); + self.print(b".module"); + } + _ => self.print(b"module"), } } else if self.options.commonjs_module_ref.is_valid() { self.print_symbol(self.options.commonjs_module_ref); diff --git a/test/bundler/bundler_cjs.test.ts b/test/bundler/bundler_cjs.test.ts index aee7fb02d9fd..99fb9821f462 100644 --- a/test/bundler/bundler_cjs.test.ts +++ b/test/bundler/bundler_cjs.test.ts @@ -597,4 +597,176 @@ describe("bundler", () => { stdout: "loaded ok", }, }); + + // ============================================================================ + // __require in iife output. Anything the printer rewrites to the runtime's + // `__require` (a require() with a non-literal argument, require.resolve(), + // an uncalled `require` reference, an external require) needs the runtime + // part that defines it to be kept in the bundle, for every target. + // ============================================================================ + + const dynamicRequireFiles = { + "/entry.js": /* js */ ` + import value from "./dyn.cjs"; + console.log(value); + `, + "/dyn.cjs": /* js */ ` + var ext = ".cjs"; + module.exports = require("./dep-runtime" + ext).value; + `, + }; + const dynamicRequireRuntimeFiles = { + "/dep-runtime.cjs": /* js */ `module.exports = { value: "required at runtime" };`, + }; + + // Test 29: the node flavor of __require (createRequire(import.meta.url)) cannot + // exist inside an iife, so node gets the same shim as the browser and the + // shim forwards to the host's require when the iife is loaded as CommonJS. + itBundled("cjs/__require_iife_dynamic_require_target_node", { + files: dynamicRequireFiles, + runtimeFiles: dynamicRequireRuntimeFiles, + target: "node", + format: "iife", + onAfterBundle(api) { + api.expectFile("/out.js").toMatch(/var __require = /); + api.expectFile("/out.js").not.toContain("createRequire"); + api.expectFile("/out.js").not.toContain("import.meta"); + }, + run: [{ runtime: "node", stdout: "required at runtime" }, { stdout: "required at runtime" }], + }); + + // Test 30: target bun keeps its import.meta.require flavor. Bun loads the + // `// @bun` output as an ES module, where that is the only require available. + itBundled("cjs/__require_iife_dynamic_require_target_bun", { + files: dynamicRequireFiles, + runtimeFiles: dynamicRequireRuntimeFiles, + target: "bun", + format: "iife", + onAfterBundle(api) { + api.expectFile("/out.js").toContain("var __require = import.meta.require;"); + }, + run: { + stdout: "required at runtime", + }, + }); + + // Test 31: target browser + itBundled("cjs/__require_iife_dynamic_require_target_browser", { + files: dynamicRequireFiles, + runtimeFiles: dynamicRequireRuntimeFiles, + target: "browser", + format: "iife", + onAfterBundle(api) { + api.expectFile("/out.js").toMatch(/var __require = /); + }, + run: [{ runtime: "node", stdout: "required at runtime" }, { stdout: "required at runtime" }], + }); + + // Test 32: same as above with the renamer running over the runtime part + itBundled("cjs/__require_iife_dynamic_require_minified", { + files: dynamicRequireFiles, + runtimeFiles: dynamicRequireRuntimeFiles, + target: "browser", + format: "iife", + minifyIdentifiers: true, + minifySyntax: true, + minifyWhitespace: true, + run: [{ runtime: "node", stdout: "required at runtime" }, { stdout: "required at runtime" }], + }); + + // Test 33: without a host require() the shim throws esbuild's error instead of + // the bundle failing on the missing __require binding. `new Function` runs the + // iife in the global scope, where the module-scoped `require` is not visible. + itBundled("cjs/__require_iife_dynamic_require_without_host_require", { + files: { + ...dynamicRequireFiles, + "/test.js": /* js */ ` + import { readFileSync } from "node:fs"; + const code = readFileSync(new URL("./out.js", import.meta.url), "utf8"); + try { + new Function(code)(); + } catch (e) { + console.log(e.message); + } + `, + }, + runtimeFiles: dynamicRequireRuntimeFiles, + target: "browser", + format: "iife", + run: { + file: "/test.js", + stdout: 'Dynamic require of "./dep-runtime.cjs" is not supported', + }, + }); + + // Test 34: require.resolve(), an uncalled `require` and require.main all print + // through the same __require binding + itBundled("cjs/__require_iife_require_reference_forms", { + files: { + "/entry.js": /* js */ ` + import value from "./forms.cjs"; + console.log(value.join("\\n")); + `, + "/forms.cjs": /* js */ ` + var ext = ".cjs"; + var indirect = require; + module.exports = [ + indirect("./dep-runtime" + ext).value, + require.resolve("./dep-runtime" + ext).endsWith("dep-runtime.cjs"), + typeof require.main, + ]; + `, + }, + runtimeFiles: dynamicRequireRuntimeFiles, + target: "node", + format: "iife", + run: [ + { runtime: "node", stdout: "required at runtime\ntrue\nobject" }, + { stdout: "required at runtime\ntrue\nobject" }, + ], + }); + + // Test 35: a require() of an external package already pulled __require in for + // iife output, but target node defined it with an import of node:module that + // the iife turned into `__require("node:module")`, a reference to itself. + itBundled("cjs/__require_iife_external_require_target_node", { + files: { + "/entry.js": /* js */ ` + import value from "./dep.cjs"; + console.log(value); + `, + "/dep.cjs": /* js */ ` + module.exports = require("ext").value; + `, + }, + runtimeFiles: { + "/node_modules/ext/index.js": /* js */ `module.exports = { value: "external package" };`, + }, + external: ["ext"], + target: "node", + format: "iife", + onAfterBundle(api) { + api.expectFile("/out.js").toContain('__require("ext")'); + api.expectFile("/out.js").not.toContain('__require("node:module")'); + api.expectFile("/out.js").not.toContain("import.meta"); + }, + run: [{ runtime: "node", stdout: "external package" }, { stdout: "external package" }], + }); + + // Test 36: esm output for node is unchanged by the iife shim + itBundled("cjs/__require_esm_dynamic_require_target_node", { + files: dynamicRequireFiles, + runtimeFiles: dynamicRequireRuntimeFiles, + target: "node", + format: "esm", + outfile: "/out.mjs", + onAfterBundle(api) { + api.expectFile("/out.mjs").toContain('import { createRequire } from "node:module";'); + api.expectFile("/out.mjs").toContain("createRequire(import.meta.url)"); + }, + run: { + runtime: "node", + stdout: "required at runtime", + }, + }); }); diff --git a/test/bundler/bundler_edgecase.test.ts b/test/bundler/bundler_edgecase.test.ts index 7fe388d3a623..d55f4529d90a 100644 --- a/test/bundler/bundler_edgecase.test.ts +++ b/test/bundler/bundler_edgecase.test.ts @@ -2146,6 +2146,42 @@ describe("bundler", () => { api.expectFile("/out.js").toMatch(/[^\.:]module/); // `.module` and `node:module` are not ok. }, }); + // In iife output `import.meta.main` is lowered to a comparison against the + // runtime's `__require`, so the runtime part defining it has to be kept. The + // iife is loaded as a CommonJS script, so the host's `module` is the thing + // to compare against (as in cjs output), not the `__require.module` form the + // esm lowering uses for node. + const importMetaMainIIFEFiles = { + "/entry.ts": /* js */ ` + import {other} from './other'; + console.log(capture(import.meta.main), capture(require.main === module), ...other); + `, + "/other.ts": /* js */ ` + globalThis['ca' + 'pture'] = x => x; + + export const other = [capture(require.main === module), capture(import.meta.main)]; + `, + }; + itBundled("edgecase/ImportMetaMainIIFE", { + files: importMetaMainIIFEFiles, + format: "iife", + capture: ["false", "false", "__require.main == module", "__require.main == module"], + onAfterBundle(api) { + api.expectFile("/out.js").toMatch(/var __require = /); + }, + run: [{ stdout: "true true false false" }, { runtime: "node", stdout: "true true false false" }], + }); + itBundled("edgecase/ImportMetaMainIIFETargetNode", { + files: importMetaMainIIFEFiles, + target: "node", + format: "iife", + capture: ["false", "false", "__require.main == module", "__require.main == module"], + onAfterBundle(api) { + api.expectFile("/out.js").toMatch(/var __require = /); + api.expectFile("/out.js").not.toContain("createRequire"); + }, + run: [{ stdout: "true true false false" }, { runtime: "node", stdout: "true true false false" }], + }); itBundled("edgecase/IdentifierInEnum#13081", { files: { "/entry.ts": ` From 62feed99bb76fe9dafccd5d713dbcb48567c5fdd Mon Sep 17 00:00:00 2001 From: robobun <117481402+robobun@users.noreply.github.com> Date: Thu, 13 Aug 2026 18:23:59 +0000 Subject: [PATCH 2/8] bundler: finish the import.meta.main lowering for iife output Decide in one place (ParseTask) which entry points get import.meta.main lowered: esm output for node and iife output for every target but bun, whose `// @bun` iife is loaded as an ES module where import.meta.main works and no `module` binding exists. The printer keeps it verbatim for that combination instead of emitting a comparison that throws. When the iife lowering prints the host's `module`, the parser declares an unbound `module` in the entry point's scope so the renamer keeps bundled bindings off that name, as cjs output already does through its reserved names; otherwise a top-level `module` binding anywhere in the bundle silently made import.meta.main false. The lowering prints as an == expression, so parenthesize it like one: `!import.meta.main` was printed as `!x.main == module` (also in the existing esm lowering for node). Tests cover all three iife targets as main and when required from another module, the inverted forms, a shadowing `module` binding and the esm/node parenthesization. --- src/bundler/ParseTask.rs | 12 ++- src/bundler/transpiler.rs | 2 +- src/js_parser/p.rs | 23 +++++- src/js_parser/parse/parse_entry.rs | 11 ++- src/js_printer/lib.rs | 63 ++++++++++------ test/bundler/bundler_edgecase.test.ts | 105 ++++++++++++++++++++++---- 6 files changed, 172 insertions(+), 44 deletions(-) diff --git a/src/bundler/ParseTask.rs b/src/bundler/ParseTask.rs index 9c8eb4eb1798..4693f7dedcf3 100644 --- a/src/bundler/ParseTask.rs +++ b/src/bundler/ParseTask.rs @@ -2607,8 +2607,16 @@ pub mod parse_worker { // in which we inline `true`. if topts.inline_entrypoint_import_meta_main || !task.is_entry_point { opts.import_meta_main_value = Some(task.is_entry_point && !topts.has_dev_server()); - } else if target == options::Target::Node { - opts.lower_import_meta_main_for_node_js = true; + } else { + // Must agree with `EImportMetaMain` in the printer. cjs output lowers it too, + // but to the host's own `require`, which needs nothing from the parser. + opts.lower_import_meta_main = match output_format { + options::Format::Esm => target == options::Target::Node, + // Bun loads its `// @bun` iife as an ES module (see post_process_js_chunk), + // so `import.meta.main` is kept as is there. + options::Format::Iife => !target.is_bun(), + options::Format::Cjs | options::Format::InternalBakeDev => false, + }; } opts.tree_shaking = if task.source_index.is_runtime() { diff --git a/src/bundler/transpiler.rs b/src/bundler/transpiler.rs index c84341e5fa3a..7770d169a61e 100644 --- a/src/bundler/transpiler.rs +++ b/src/bundler/transpiler.rs @@ -1595,7 +1595,7 @@ impl<'a> Transpiler<'a> { output_format: p_opts::Format::Esm, transform_only: self.options.transform_only, import_meta_main_value: None, - lower_import_meta_main_for_node_js: false, + lower_import_meta_main: false, framework: None, repl_mode: self.options.repl_mode, lower_toml_datetimes: false, diff --git a/src/js_parser/p.rs b/src/js_parser/p.rs index 2f377050d5bb..6dc894e880bf 100644 --- a/src/js_parser/p.rs +++ b/src/js_parser/p.rs @@ -239,6 +239,10 @@ pub struct P<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> { pub(crate) dirname_ref: Ref, pub(crate) import_meta_ref: Ref, pub(crate) hmr_api_ref: Ref, + /// Unbound `module` created by `value_for_import_meta_main` for iife output. It is + /// never printed; being unbound, it makes the renamer keep every bundled binding off + /// that name, so the `module` the lowering prints is the host's. + pub(crate) import_meta_main_host_module_ref: Ref, /// If bake is enabled and this is a server-side file, we want to use /// special `Response` class inside the `bun:app` built-in module to @@ -5273,10 +5277,22 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O // The printer can handle this for us, but we need to reference // a handle to the `__require` function. // - // The non-ESM lowering is `__require.main == module`, so iife output needs - // `__require` as well (for cjs output this is a no-op and `require` is printed). - if self.options.lower_import_meta_main_for_node_js || !self.options.output_format.is_esm() { + // An iife is loaded as a CommonJS script instead, so there it becomes + // + // var import_meta_main = __require.main == module; + // + // where `module` is the host's. cjs output reserves that name for every + // bundle (see `compute_initial_reserved_names`); for the iife an unbound + // `module` in this file's scope reserves it the same way, so a bundled + // binding of that name gets renamed instead of being compared against. + if self.options.lower_import_meta_main { self.record_usage_of_runtime_require(); + if self.options.output_format == options::Format::Iife + && self.import_meta_main_host_module_ref.is_empty() + { + self.import_meta_main_host_module_ref = + self.declare_generated_symbol(js_ast::symbol::Kind::Unbound, b"module"); + } } Expr { loc, @@ -8673,6 +8689,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O dirname_ref: Ref::NONE, import_meta_ref: Ref::NONE, hmr_api_ref: Ref::NONE, + import_meta_main_host_module_ref: Ref::NONE, response_ref: Ref::NONE, bun_app_namespace_ref: Ref::NONE, bundler_feature_flag_ref: Ref::NONE, diff --git a/src/js_parser/parse/parse_entry.rs b/src/js_parser/parse/parse_entry.rs index 4bb47e5e0c0a..1daa51687f27 100644 --- a/src/js_parser/parse/parse_entry.rs +++ b/src/js_parser/parse/parse_entry.rs @@ -92,7 +92,10 @@ pub struct Options<'a> { /// Used for inlining the state of import.meta.main during visiting pub import_meta_main_value: Option, - pub lower_import_meta_main_for_node_js: bool, + /// The printer is going to lower `import.meta.main` to a comparison built on the + /// runtime's `__require` (esm output for node, iife output unless targeting bun), + /// so the file has to import it. See `P::value_for_import_meta_main`. + pub lower_import_meta_main: bool, /// When using react fast refresh or server components, the framework is /// able to customize what import sources are used. @@ -135,7 +138,7 @@ impl<'a> Default for Options<'a> { output_format: options::Format::Esm, transform_only: false, import_meta_main_value: None, - lower_import_meta_main_for_node_js: false, + lower_import_meta_main: false, framework: None, repl_mode: false, lower_toml_datetimes: false, @@ -219,7 +222,7 @@ impl<'a> Options<'a> { output_format: self.output_format, transform_only: self.transform_only, import_meta_main_value: self.import_meta_main_value, - lower_import_meta_main_for_node_js: self.lower_import_meta_main_for_node_js, + lower_import_meta_main: self.lower_import_meta_main, framework: self.framework, repl_mode: self.repl_mode, lower_toml_datetimes: self.lower_toml_datetimes, @@ -291,7 +294,7 @@ impl<'a> Options<'a> { output_format: options::Format::Esm, transform_only: false, import_meta_main_value: None, - lower_import_meta_main_for_node_js: false, + lower_import_meta_main: false, framework: None, repl_mode: false, lower_toml_datetimes: loader == options::Loader::Toml, diff --git a/src/js_printer/lib.rs b/src/js_printer/lib.rs index 5496dfd00c6d..f677e2c3abbd 100644 --- a/src/js_printer/lib.rs +++ b/src/js_printer/lib.rs @@ -2994,11 +2994,16 @@ pub(crate) mod __gated_printer { } } ExprData::EImportMetaMain(data) => { - if self.options.module_type == bundle_opts::Format::Esm - && self.options.target != bun_ast::Target::Node - { + // Must agree with `lower_import_meta_main` in the bundler's ParseTask. + let keep_import_meta_main = match self.options.module_type { // Node.js doesn't support import.meta.main - // Most of the time, leave it in there + bundle_opts::Format::Esm => self.options.target != bun_ast::Target::Node, + // Bun loads its `// @bun` iife as an ES module, where import.meta.main + // works and the `module` binding printed below does not exist. + bundle_opts::Format::Iife => self.options.target.is_bun(), + bundle_opts::Format::Cjs | bundle_opts::Format::InternalBakeDev => false, + }; + if keep_import_meta_main { if data.inverted { self.add_source_mapping(expr.loc); self.print(b"!"); @@ -3015,6 +3020,14 @@ pub(crate) mod __gated_printer { self.options.module_type != bundle_opts::Format::InternalBakeDev ); + // This prints as an `==` / `!=` expression, so it needs the same + // parentheses one would: `!import.meta.main` must not become + // `!x.main == module`. + let wrap = level.gte(Level::Equals); + if wrap { + self.print(b"("); + } + self.print_space_before_identifier(); self.add_source_mapping(expr.loc); @@ -3030,24 +3043,32 @@ pub(crate) mod __gated_printer { self.print_whitespacer(ws!(b".main == ")); } - if self.options.target == bun_ast::Target::Node { - match self.options.require_ref { - // "__require.module": ESM output has no `module` binding, - // so this compares against the createRequire() function's - // own (undefined) `.module`. cjs and iife output are loaded - // as CommonJS scripts, where the host's `module` exists. - Some(require) - if self.options.module_type == bundle_opts::Format::Esm => - { - self.print_symbol(require); - self.print(b".module"); - } - _ => self.print(b"module"), + match self.options.require_ref { + // "__require.module": ESM output (node only, see above) has no + // `module` binding, so this compares against the createRequire() + // function's own (undefined) `.module`. + Some(require) + if self.options.module_type == bundle_opts::Format::Esm => + { + self.print_symbol(require); + self.print(b".module"); } - } else if self.options.commonjs_module_ref.is_valid() { - self.print_symbol(self.options.commonjs_module_ref); - } else { - self.print(b"module"); + // The host's `module`: cjs output reserves the name in the + // renamer, iife output through the unbound symbol the parser + // declares in `value_for_import_meta_main`. + _ if self.options.module_type == bundle_opts::Format::Iife + || self.options.target == bun_ast::Target::Node => + { + self.print(b"module"); + } + _ if self.options.commonjs_module_ref.is_valid() => { + self.print_symbol(self.options.commonjs_module_ref); + } + _ => self.print(b"module"), + } + + if wrap { + self.print(b")"); } } } diff --git a/test/bundler/bundler_edgecase.test.ts b/test/bundler/bundler_edgecase.test.ts index d55f4529d90a..4afb25909a25 100644 --- a/test/bundler/bundler_edgecase.test.ts +++ b/test/bundler/bundler_edgecase.test.ts @@ -2146,15 +2146,20 @@ describe("bundler", () => { api.expectFile("/out.js").toMatch(/[^\.:]module/); // `.module` and `node:module` are not ok. }, }); - // In iife output `import.meta.main` is lowered to a comparison against the - // runtime's `__require`, so the runtime part defining it has to be kept. The - // iife is loaded as a CommonJS script, so the host's `module` is the thing - // to compare against (as in cjs output), not the `__require.module` form the - // esm lowering uses for node. - const importMetaMainIIFEFiles = { + // The entry point's four forms are lowered (or, where the output supports + // import.meta.main, kept); `other` is not an entry point, so its two fold to + // `false`. The bundle is run twice: as the main module and required from + // runner.cjs, which flips every entry point value. + const importMetaMainLoweringFiles = { "/entry.ts": /* js */ ` import {other} from './other'; - console.log(capture(import.meta.main), capture(require.main === module), ...other); + console.log( + capture(import.meta.main), + capture(require.main === module), + capture(!import.meta.main), + capture(require.main !== module), + ...other, + ); `, "/other.ts": /* js */ ` globalThis['ca' + 'pture'] = x => x; @@ -2162,25 +2167,99 @@ describe("bundler", () => { export const other = [capture(require.main === module), capture(import.meta.main)]; `, }; + const importMetaMainRunner = { + "/runner.cjs": /* js */ `require("./out.js");`, + }; + const importMetaMainAsMain = "true true false false false false"; + const importMetaMainWhenRequired = "false false true true false false"; + // node and browsers load an iife as a CommonJS script, so the lowering + // compares against the host's `module`, the same as cjs output does (the + // `__require.module` operand is only for esm output, which has no `module`). + const importMetaMainIIFECapture = [ + "false", + "false", + "__require.main == module", + "__require.main == module", + "!(__require.main == module)", + "__require.main != module", + ]; + const importMetaMainIIFERuns = [ + { stdout: importMetaMainAsMain }, + { runtime: "node" as const, stdout: importMetaMainAsMain }, + { file: "/runner.cjs", stdout: importMetaMainWhenRequired }, + { file: "/runner.cjs", runtime: "node" as const, stdout: importMetaMainWhenRequired }, + ]; itBundled("edgecase/ImportMetaMainIIFE", { - files: importMetaMainIIFEFiles, + files: importMetaMainLoweringFiles, + runtimeFiles: importMetaMainRunner, format: "iife", - capture: ["false", "false", "__require.main == module", "__require.main == module"], + capture: importMetaMainIIFECapture, onAfterBundle(api) { api.expectFile("/out.js").toMatch(/var __require = /); }, - run: [{ stdout: "true true false false" }, { runtime: "node", stdout: "true true false false" }], + run: importMetaMainIIFERuns, }); itBundled("edgecase/ImportMetaMainIIFETargetNode", { - files: importMetaMainIIFEFiles, + files: importMetaMainLoweringFiles, + runtimeFiles: importMetaMainRunner, target: "node", format: "iife", - capture: ["false", "false", "__require.main == module", "__require.main == module"], + capture: importMetaMainIIFECapture, onAfterBundle(api) { api.expectFile("/out.js").toMatch(/var __require = /); api.expectFile("/out.js").not.toContain("createRequire"); }, - run: [{ stdout: "true true false false" }, { runtime: "node", stdout: "true true false false" }], + run: importMetaMainIIFERuns, + }); + // Bun loads the `// @bun` iife as an ES module, where import.meta.main works + // and neither `require` nor `module` exist, so nothing is lowered there. + itBundled("edgecase/ImportMetaMainIIFETargetBun", { + files: importMetaMainLoweringFiles, + runtimeFiles: importMetaMainRunner, + target: "bun", + format: "iife", + capture: ["false", "false", "import.meta.main", "import.meta.main", "!import.meta.main", "!import.meta.main"], + onAfterBundle(api) { + api.expectFile("/out.js").not.toContain("__require"); + }, + run: [{ stdout: importMetaMainAsMain }, { file: "/runner.cjs", stdout: importMetaMainWhenRequired }], + }); + // The esm lowering for node prints as a binary expression too, so it needs + // parentheses under a unary operator. + itBundled("edgecase/ImportMetaMainInvertedTargetNode", { + files: importMetaMainLoweringFiles, + target: "node", + capture: [ + "false", + "false", + "__require.main == __require.module", + "__require.main == __require.module", + "!(__require.main == __require.module)", + "__require.main != __require.module", + ], + run: { runtime: "node", stdout: importMetaMainAsMain }, + }); + // The `module` the iife lowering prints has to be the host's even when the + // bundle declares bindings of that name: they get renamed, as in cjs output. + itBundled("edgecase/ImportMetaMainIIFEShadowedModuleBinding", { + files: { + "/entry.ts": /* js */ ` + import {sibling} from './sibling'; + const module = { name: "entry" }; + console.log(import.meta.main, module.name, sibling); + `, + "/sibling.ts": /* js */ ` + let module = { name: "sibling" }; + export const sibling = module.name; + `, + }, + format: "iife", + onAfterBundle(api) { + api.expectFile("/out.js").toContain("__require.main == module"); + api.expectFile("/out.js").toMatch(/var module2 = \{/); + api.expectFile("/out.js").toMatch(/var module3 = \{/); + }, + run: [{ stdout: "true entry sibling" }, { runtime: "node", stdout: "true entry sibling" }], }); itBundled("edgecase/IdentifierInEnum#13081", { files: { From 316eb446f68fa1b37330b72fce839c35e0d01870 Mon Sep 17 00:00:00 2001 From: robobun <117481402+robobun@users.noreply.github.com> Date: Thu, 13 Aug 2026 18:34:13 +0000 Subject: [PATCH 3/8] bundler: trim oversized comments --- src/bundler/ParseTask.rs | 18 ++++++------------ src/js_parser/p.rs | 16 +++++----------- src/js_parser/parse/parse_entry.rs | 5 ++--- src/js_printer/lib.rs | 16 +++++----------- 4 files changed, 18 insertions(+), 37 deletions(-) diff --git a/src/bundler/ParseTask.rs b/src/bundler/ParseTask.rs index 4693f7dedcf3..0458e53d8966 100644 --- a/src/bundler/ParseTask.rs +++ b/src/bundler/ParseTask.rs @@ -396,8 +396,7 @@ pub(crate) struct RuntimeSource { // code that called `fn.toString()` and parsed the code outside a module // context. // -// This is also right for iife output: Bun loads the `// @bun` output as an ES -// module, where `import.meta.require` is the only `require` there is. +// iife output uses this too: Bun loads the `// @bun` iife as an ES module. const RUNTIME_REQUIRE_BUN: &str = "export var __require = import.meta.require;"; const RUNTIME_REQUIRE_NODE: &str = "\ @@ -417,9 +416,7 @@ export var __require = /* @__PURE__ */ createRequire(import.meta.url); // When bundling to node, esbuild picks this code path as well, but `globalThis.require` // is not always defined there. The `createRequire` call approach is more reliable. // -// iife output for node uses this too (like esbuild): a script can neither `import` -// node:module nor read `import.meta.url`, and node loads the iife as CommonJS, where -// `require` is in scope for this shim to forward to. +// node iife output uses this too: a script has no `import` or `import.meta.url`. const RUNTIME_REQUIRE_OTHER: &str = "\ export var __require = /* @__PURE__ */ (x => typeof require !== 'undefined' ? require : @@ -2511,9 +2508,8 @@ pub mod parse_worker { opts.features.lower_using = !target.is_bun(); opts.features.hot_module_reloading = output_format == options::Format::InternalBakeDev && !task.source_index.is_runtime(); - // The printer emits the runtime's `__require` for every output format except cjs - // (see `runtime_require_ref` in the linker); this is what makes a file that uses it - // import the part defining it. + // Must match `runtime_require_ref` in the linker: every format but cjs + // prints the runtime's `__require`. opts.features.auto_polyfill_require = matches!(output_format, options::Format::Esm | options::Format::Iife) && !opts.features.hot_module_reloading; @@ -2608,12 +2604,10 @@ pub mod parse_worker { if topts.inline_entrypoint_import_meta_main || !task.is_entry_point { opts.import_meta_main_value = Some(task.is_entry_point && !topts.has_dev_server()); } else { - // Must agree with `EImportMetaMain` in the printer. cjs output lowers it too, - // but to the host's own `require`, which needs nothing from the parser. + // Must agree with `EImportMetaMain` in the printer. opts.lower_import_meta_main = match output_format { options::Format::Esm => target == options::Target::Node, - // Bun loads its `// @bun` iife as an ES module (see post_process_js_chunk), - // so `import.meta.main` is kept as is there. + // Bun loads its `// @bun` iife as an ES module, so import.meta.main works there. options::Format::Iife => !target.is_bun(), options::Format::Cjs | options::Format::InternalBakeDev => false, }; diff --git a/src/js_parser/p.rs b/src/js_parser/p.rs index 6dc894e880bf..de67f1c1e3c4 100644 --- a/src/js_parser/p.rs +++ b/src/js_parser/p.rs @@ -239,9 +239,8 @@ pub struct P<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> { pub(crate) dirname_ref: Ref, pub(crate) import_meta_ref: Ref, pub(crate) hmr_api_ref: Ref, - /// Unbound `module` created by `value_for_import_meta_main` for iife output. It is - /// never printed; being unbound, it makes the renamer keep every bundled binding off - /// that name, so the `module` the lowering prints is the host's. + /// Unbound `module` declared by `value_for_import_meta_main`; reserves the + /// name so the iife lowering compares against the host's `module`. pub(crate) import_meta_main_host_module_ref: Ref, /// If bake is enabled and this is a server-side file, we want to use @@ -5277,14 +5276,9 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O // The printer can handle this for us, but we need to reference // a handle to the `__require` function. // - // An iife is loaded as a CommonJS script instead, so there it becomes - // - // var import_meta_main = __require.main == module; - // - // where `module` is the host's. cjs output reserves that name for every - // bundle (see `compute_initial_reserved_names`); for the iife an unbound - // `module` in this file's scope reserves it the same way, so a bundled - // binding of that name gets renamed instead of being compared against. + // An iife runs as CommonJS, so there it prints `__require.main == module` + // with the host's `module`; the unbound symbol reserves the name in the + // renamer (cjs does the same via `compute_initial_reserved_names`). if self.options.lower_import_meta_main { self.record_usage_of_runtime_require(); if self.options.output_format == options::Format::Iife diff --git a/src/js_parser/parse/parse_entry.rs b/src/js_parser/parse/parse_entry.rs index 1daa51687f27..b3d7dfa04b10 100644 --- a/src/js_parser/parse/parse_entry.rs +++ b/src/js_parser/parse/parse_entry.rs @@ -92,9 +92,8 @@ pub struct Options<'a> { /// Used for inlining the state of import.meta.main during visiting pub import_meta_main_value: Option, - /// The printer is going to lower `import.meta.main` to a comparison built on the - /// runtime's `__require` (esm output for node, iife output unless targeting bun), - /// so the file has to import it. See `P::value_for_import_meta_main`. + /// The printer will lower `import.meta.main` via the runtime's `__require`; + /// see `P::value_for_import_meta_main`. pub lower_import_meta_main: bool, /// When using react fast refresh or server components, the framework is diff --git a/src/js_printer/lib.rs b/src/js_printer/lib.rs index f677e2c3abbd..128fa36bd313 100644 --- a/src/js_printer/lib.rs +++ b/src/js_printer/lib.rs @@ -2998,8 +2998,7 @@ pub(crate) mod __gated_printer { let keep_import_meta_main = match self.options.module_type { // Node.js doesn't support import.meta.main bundle_opts::Format::Esm => self.options.target != bun_ast::Target::Node, - // Bun loads its `// @bun` iife as an ES module, where import.meta.main - // works and the `module` binding printed below does not exist. + // Bun loads its `// @bun` iife as an ES module, so import.meta.main works. bundle_opts::Format::Iife => self.options.target.is_bun(), bundle_opts::Format::Cjs | bundle_opts::Format::InternalBakeDev => false, }; @@ -3020,9 +3019,7 @@ pub(crate) mod __gated_printer { self.options.module_type != bundle_opts::Format::InternalBakeDev ); - // This prints as an `==` / `!=` expression, so it needs the same - // parentheses one would: `!import.meta.main` must not become - // `!x.main == module`. + // Prints as an `==` / `!=` expression, so parenthesize like one. let wrap = level.gte(Level::Equals); if wrap { self.print(b"("); @@ -3044,18 +3041,15 @@ pub(crate) mod __gated_printer { } match self.options.require_ref { - // "__require.module": ESM output (node only, see above) has no - // `module` binding, so this compares against the createRequire() - // function's own (undefined) `.module`. + // esm output has no `module` binding; compare against `__require.module`. Some(require) if self.options.module_type == bundle_opts::Format::Esm => { self.print_symbol(require); self.print(b".module"); } - // The host's `module`: cjs output reserves the name in the - // renamer, iife output through the unbound symbol the parser - // declares in `value_for_import_meta_main`. + // The host's `module`; the renamer reserves the name + // (see `value_for_import_meta_main`). _ if self.options.module_type == bundle_opts::Format::Iife || self.options.target == bun_ast::Target::Node => { From 1505b6b99a346816f8f54197e96a6b8a8e2a0a91 Mon Sep 17 00:00:00 2001 From: robobun <117481402+robobun@users.noreply.github.com> Date: Thu, 13 Aug 2026 19:27:05 +0000 Subject: [PATCH 4/8] bundler: parenthesize kept inverted import.meta.main under higher precedence --- src/js_printer/lib.rs | 8 ++++++++ test/bundler/bundler_edgecase.test.ts | 21 +++++++++++++++++++++ 2 files changed, 29 insertions(+) diff --git a/src/js_printer/lib.rs b/src/js_printer/lib.rs index 128fa36bd313..6a2935d7e9da 100644 --- a/src/js_printer/lib.rs +++ b/src/js_printer/lib.rs @@ -3003,6 +3003,11 @@ pub(crate) mod __gated_printer { bundle_opts::Format::Cjs | bundle_opts::Format::InternalBakeDev => false, }; if keep_import_meta_main { + // When inverted this prints a `!` prefix, so parenthesize like one. + let wrap = data.inverted && level.gte(Level::Prefix); + if wrap { + self.print(b"("); + } if data.inverted { self.add_source_mapping(expr.loc); self.print(b"!"); @@ -3014,6 +3019,9 @@ pub(crate) mod __gated_printer { mi.flags.contains_import_meta = true; } self.print(b"import.meta.main"); + if wrap { + self.print(b")"); + } } else { debug_assert!( self.options.module_type != bundle_opts::Format::InternalBakeDev diff --git a/test/bundler/bundler_edgecase.test.ts b/test/bundler/bundler_edgecase.test.ts index 4afb25909a25..f5f2c9d4ad58 100644 --- a/test/bundler/bundler_edgecase.test.ts +++ b/test/bundler/bundler_edgecase.test.ts @@ -2261,6 +2261,27 @@ describe("bundler", () => { }, run: [{ stdout: "true entry sibling" }, { runtime: "node", stdout: "true entry sibling" }], }); + // A kept inverted import.meta.main prints a `!` prefix, so as a member + // expression target it needs parentheses: `(!import.meta.main).toString()`, + // not `!import.meta.main.toString()`. + const importMetaMainInvertedMemberFiles = { + "/entry.ts": /* js */ ` + globalThis['ca' + 'pture'] = x => x; + console.log(capture((require.main !== module).toString().length)); + `, + }; + itBundled("edgecase/ImportMetaMainInvertedMemberTarget", { + files: importMetaMainInvertedMemberFiles, + capture: ["(!import.meta.main).toString().length"], + run: { stdout: "5" }, + }); + itBundled("edgecase/ImportMetaMainInvertedMemberTargetIIFEBun", { + files: importMetaMainInvertedMemberFiles, + format: "iife", + target: "bun", + capture: ["(!import.meta.main).toString().length"], + run: { stdout: "5" }, + }); itBundled("edgecase/IdentifierInEnum#13081", { files: { "/entry.ts": ` From 76758f784b3916e14a792ab4489db97fa2218a43 Mon Sep 17 00:00:00 2001 From: robobun <117481402+robobun@users.noreply.github.com> Date: Thu, 13 Aug 2026 20:00:12 +0000 Subject: [PATCH 5/8] bundler: parenthesize kept inverted import.meta.main left of ** --- src/js_printer/lib.rs | 4 ++++ test/bundler/bundler_edgecase.test.ts | 24 ++++++++++++++++-------- 2 files changed, 20 insertions(+), 8 deletions(-) diff --git a/src/js_printer/lib.rs b/src/js_printer/lib.rs index 6a2935d7e9da..20e45b3fffa9 100644 --- a/src/js_printer/lib.rs +++ b/src/js_printer/lib.rs @@ -1617,6 +1617,10 @@ pub(crate) mod __gated_printer { ExprData::EAwait(_) | ExprData::EUndefined(_) | ExprData::ENumber(_) => { v.left_level = Level::Call; } + // When kept and inverted, this prints a `!` prefix + ExprData::EImportMetaMain(m) if m.inverted => { + v.left_level = Level::Call; + } ExprData::EBoolean(_) | ExprData::EBranchBoolean(_) => { // When minifying, booleans are printed as "!0 and "!1" if self.options.minify_syntax { diff --git a/test/bundler/bundler_edgecase.test.ts b/test/bundler/bundler_edgecase.test.ts index f5f2c9d4ad58..2020286eb48e 100644 --- a/test/bundler/bundler_edgecase.test.ts +++ b/test/bundler/bundler_edgecase.test.ts @@ -2261,26 +2261,34 @@ describe("bundler", () => { }, run: [{ stdout: "true entry sibling" }, { runtime: "node", stdout: "true entry sibling" }], }); - // A kept inverted import.meta.main prints a `!` prefix, so as a member - // expression target it needs parentheses: `(!import.meta.main).toString()`, - // not `!import.meta.main.toString()`. + // A kept inverted import.meta.main prints a `!` prefix, so it needs + // parentheses as a member expression target (`(!import.meta.main).toString()`, + // not `!import.meta.main.toString()`) and as the left operand of `**` + // (`!x ** 2` is a SyntaxError). const importMetaMainInvertedMemberFiles = { "/entry.ts": /* js */ ` globalThis['ca' + 'pture'] = x => x; - console.log(capture((require.main !== module).toString().length)); + console.log( + capture((require.main !== module).toString().length), + capture((require.main !== module) ** 2), + ); `, }; + const importMetaMainInvertedMemberCapture = [ + "(!import.meta.main).toString().length", + "(!import.meta.main) ** 2", + ]; itBundled("edgecase/ImportMetaMainInvertedMemberTarget", { files: importMetaMainInvertedMemberFiles, - capture: ["(!import.meta.main).toString().length"], - run: { stdout: "5" }, + capture: importMetaMainInvertedMemberCapture, + run: { stdout: "5 0" }, }); itBundled("edgecase/ImportMetaMainInvertedMemberTargetIIFEBun", { files: importMetaMainInvertedMemberFiles, format: "iife", target: "bun", - capture: ["(!import.meta.main).toString().length"], - run: { stdout: "5" }, + capture: importMetaMainInvertedMemberCapture, + run: { stdout: "5 0" }, }); itBundled("edgecase/IdentifierInEnum#13081", { files: { From b693e88db0980f930b6a414f2129e15314a510d9 Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Thu, 13 Aug 2026 20:02:44 +0000 Subject: [PATCH 6/8] [autofix.ci] apply automated fixes --- test/bundler/bundler_edgecase.test.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/test/bundler/bundler_edgecase.test.ts b/test/bundler/bundler_edgecase.test.ts index 2020286eb48e..12077719bf23 100644 --- a/test/bundler/bundler_edgecase.test.ts +++ b/test/bundler/bundler_edgecase.test.ts @@ -2274,10 +2274,7 @@ describe("bundler", () => { ); `, }; - const importMetaMainInvertedMemberCapture = [ - "(!import.meta.main).toString().length", - "(!import.meta.main) ** 2", - ]; + const importMetaMainInvertedMemberCapture = ["(!import.meta.main).toString().length", "(!import.meta.main) ** 2"]; itBundled("edgecase/ImportMetaMainInvertedMemberTarget", { files: importMetaMainInvertedMemberFiles, capture: importMetaMainInvertedMemberCapture, From 7056a691512252a811fc643524bca69651fa981b Mon Sep 17 00:00:00 2001 From: robobun <117481402+robobun@users.noreply.github.com> Date: Thu, 13 Aug 2026 20:20:04 +0000 Subject: [PATCH 7/8] bundler: test the lowered import.meta.main precedence positions for node esm --- test/bundler/bundler_edgecase.test.ts | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/test/bundler/bundler_edgecase.test.ts b/test/bundler/bundler_edgecase.test.ts index 12077719bf23..54d7b3661d3f 100644 --- a/test/bundler/bundler_edgecase.test.ts +++ b/test/bundler/bundler_edgecase.test.ts @@ -2287,6 +2287,16 @@ describe("bundler", () => { capture: importMetaMainInvertedMemberCapture, run: { stdout: "5 0" }, }); + // The lowered form prints as `==` / `!=` and needs the same parentheses. + itBundled("edgecase/ImportMetaMainInvertedMemberTargetNode", { + files: importMetaMainInvertedMemberFiles, + target: "node", + capture: [ + "(__require.main != __require.module).toString().length", + "(__require.main != __require.module) ** 2", + ], + run: { runtime: "node", stdout: "5 0" }, + }); itBundled("edgecase/IdentifierInEnum#13081", { files: { "/entry.ts": ` From 6e11ff576625a7f3bd721697ad901816ef437b88 Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Thu, 13 Aug 2026 20:22:17 +0000 Subject: [PATCH 8/8] [autofix.ci] apply automated fixes --- test/bundler/bundler_edgecase.test.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/test/bundler/bundler_edgecase.test.ts b/test/bundler/bundler_edgecase.test.ts index 54d7b3661d3f..905be616392f 100644 --- a/test/bundler/bundler_edgecase.test.ts +++ b/test/bundler/bundler_edgecase.test.ts @@ -2291,10 +2291,7 @@ describe("bundler", () => { itBundled("edgecase/ImportMetaMainInvertedMemberTargetNode", { files: importMetaMainInvertedMemberFiles, target: "node", - capture: [ - "(__require.main != __require.module).toString().length", - "(__require.main != __require.module) ** 2", - ], + capture: ["(__require.main != __require.module).toString().length", "(__require.main != __require.module) ** 2"], run: { runtime: "node", stdout: "5 0" }, }); itBundled("edgecase/IdentifierInEnum#13081", {