Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion src/bundler/LinkerContext.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2219,7 +2219,10 @@ impl<'a> LinkerContext<'a> {
require_or_import_meta_for_source_callback:
js_printer::RequireOrImportMetaCallback::init(self),
line_offset_tables: Some(line_offset_table),
target: self.options.target,
// The file's target, not the bundle's: an entry point starting with
// `#!/usr/bin/env bun` is parsed for bun and its chunk gets the `// @bun`
// pragma, which is what decides how `import.meta.main` has to be printed.
target: ast.target,

hmr_ref: if self.options.output_format == Format::InternalBakeDev {
ast.wrapper_ref
Expand Down
4 changes: 2 additions & 2 deletions src/bundler/ParseTask.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2590,8 +2590,8 @@ 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 if !output_format.keeps_import_meta_main(target) {
opts.lower_import_meta_main = true;
}

opts.tree_shaking = if task.source_index.is_runtime() {
Expand Down
2 changes: 1 addition & 1 deletion src/bundler/transpiler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1564,7 +1564,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,
};
Expand Down
6 changes: 3 additions & 3 deletions src/js_parser/p.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5367,8 +5367,8 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O
}),
};
}
// Node.js does not have import.meta.main, so we end up lowering
// this to `require.main === module`, but with the ESM format,
// When the output has no import.meta.main (e.g. ESM for Node.js), this
// is lowered to `require.main === module`, but with the ESM format,
// both `require` and `module` are not present, so the code
// generation we need is:
//
Expand All @@ -5378,7 +5378,7 @@ 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 {
if self.options.lower_import_meta_main {
self.record_usage_of_runtime_require();
}
Expr {
Expand Down
12 changes: 8 additions & 4 deletions src/js_parser/parse/parse_entry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,11 @@ pub struct Options<'a> {

/// Used for inlining the state of import.meta.main during visiting
pub import_meta_main_value: Option<bool>,
pub lower_import_meta_main_for_node_js: bool,
/// The printer lowers this file's `import.meta.main` to `require.main == module`
/// (see `bun_options_types::Format::keeps_import_meta_main`), printing `require`
/// as the runtime's `__require` outside of cjs output, so the file has to record
/// a use of it.
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.
Expand Down Expand Up @@ -132,7 +136,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,
}
Expand Down Expand Up @@ -215,7 +219,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,
}
Expand Down Expand Up @@ -286,7 +290,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,
};
Expand Down
8 changes: 4 additions & 4 deletions src/js_printer/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2991,11 +2991,11 @@ pub(crate) mod __gated_printer {
}
}
ExprData::EImportMetaMain(data) => {
if self.options.module_type == bundle_opts::Format::Esm
&& self.options.target != bun_ast::Target::Node
if self
.options
.module_type
.keeps_import_meta_main(self.options.target)
{
// Node.js doesn't support import.meta.main
// Most of the time, leave it in there
if data.inverted {
self.add_source_mapping(expr.loc);
self.print(b"!");
Expand Down
15 changes: 15 additions & 0 deletions src/options_types/bundle_enums.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,21 @@ impl Format {
self == Format::Esm
}

/// Whether an entry point's `import.meta.main` is printed as written. When
/// this is false it is lowered to a `require.main == module` comparison,
/// which needs the output to be loaded as CommonJS.
///
/// Node has no `import.meta.main`. Output for bun is marked with a `// @bun`
/// pragma that makes bun load it as an ES module unless the format is cjs,
/// so an iife for bun has `import.meta.main` and no `module` binding.
pub fn keeps_import_meta_main(self, target: Target) -> bool {
match self {
Format::Esm => !target.is_node(),
Format::Iife => target.is_bun(),
Format::Cjs | Format::InternalBakeDev => false,
}
}

pub const MAP: __ComptimeStringMap_FORMAT_MAP = __ComptimeStringMap_FORMAT_MAP(());

// `to_js`/`from_js` live as extension-trait methods in the `*_jsc` crate.
Expand Down
70 changes: 70 additions & 0 deletions test/bundler/bundler_edgecase.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2124,6 +2124,76 @@ describe("bundler", () => {
api.expectFile("/out.js").not.toMatch(/[^\.:]module/); // `.module` and `node:module` are ok.
},
});
// Output for bun starts with a `// @bun` pragma, which makes bun load it as an
// ES module in every format but cjs. `import.meta.main` works there and
// `module` does not exist, so the `require.main == module` lowering is only
// right for cjs output.
const importMetaMainFiles = {
"/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/ImportMetaMainIIFETargetBun", {
files: importMetaMainFiles,
target: "bun",
format: "iife",
capture: ["false", "false", "import.meta.main", "import.meta.main"],
onAfterBundle(api) {
api.expectFile("/out.js").toStartWith("// @bun\n");
api.expectFile("/out.js").not.toContain("require");
api.expectFile("/out.js").not.toContain("module");
},
run: { stdout: "true true false false" },
});
itBundled("edgecase/ImportMetaMainCJSTargetBun", {
files: importMetaMainFiles,
target: "bun",
format: "cjs",
capture: ["false", "false", "require.main == module", "require.main == module"],
onAfterBundle(api) {
api.expectFile("/out.js").toStartWith("// @bun @bun-cjs\n");
},
run: { stdout: "true true false false" },
});
// A `#!/usr/bin/env bun` hashbang switches the entry point to target bun, so
// its output gets the pragma no matter which target the bundle was built for.
const importMetaMainBunHashbangFiles = {
"/entry.ts": /* js */ `
#!/usr/bin/env bun
import {other} from './other';
console.log(capture(import.meta.main), capture(require.main === module), ...other);
`,
"/other.ts": importMetaMainFiles["/other.ts"],
};
itBundled("edgecase/ImportMetaMainBunHashbangTargetNode", {
files: importMetaMainBunHashbangFiles,
target: "node",
capture: ["false", "false", "import.meta.main", "import.meta.main"],
onAfterBundle(api) {
api.expectFile("/out.js").toStartWith("#!/usr/bin/env bun\n// @bun\n");
api.expectFile("/out.js").not.toContain("require");
api.expectFile("/out.js").not.toContain("module");
},
run: { stdout: "true true false false" },
});
itBundled("edgecase/ImportMetaMainIIFEBunHashbangTargetNode", {
files: importMetaMainBunHashbangFiles,
target: "node",
format: "iife",
capture: ["false", "false", "import.meta.main", "import.meta.main"],
onAfterBundle(api) {
api.expectFile("/out.js").toStartWith("#!/usr/bin/env bun\n// @bun\n");
api.expectFile("/out.js").not.toContain("require");
api.expectFile("/out.js").not.toContain("module");
},
run: { stdout: "true true false false" },
});
itBundled("edgecase/build-cjs-module#20308", {
files: {
"/entry.ts": /* js */ `
Expand Down
Loading