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
41 changes: 32 additions & 9 deletions src/bundler/ParseTask.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}

Expand Down Expand Up @@ -392,6 +395,8 @@ 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.
//
// iife output uses this too: Bun loads the `// @bun` iife as an ES module.
Comment thread
robobun marked this conversation as resolved.
const RUNTIME_REQUIRE_BUN: &str = "export var __require = import.meta.require;";

const RUNTIME_REQUIRE_NODE: &str = "\
Expand All @@ -410,6 +415,8 @@ 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.
//
// node iife output uses this too: a script has no `import` or `import.meta.url`.
Comment thread
robobun marked this conversation as resolved.
const RUNTIME_REQUIRE_OTHER: &str = "\
export var __require = /* @__PURE__ */ (x =>
typeof require !== 'undefined' ? require :
Expand Down Expand Up @@ -505,7 +512,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
Expand All @@ -520,7 +530,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<Box<[u8]>>; 4] = [
Expand Down Expand Up @@ -595,8 +606,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)
}

// ───────────────────────────────────────────────────────────────────────────
Expand Down Expand Up @@ -2494,8 +2508,11 @@ 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();
// Must match `runtime_require_ref` in the linker: every format but cjs
// prints the runtime's `__require`.
Comment thread
robobun marked this conversation as resolved.
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()
Expand Down Expand Up @@ -2586,8 +2603,14 @@ 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.
opts.lower_import_meta_main = match output_format {
options::Format::Esm => target == options::Target::Node,
// 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,
};
}

opts.tree_shaking = if task.source_index.is_runtime() {
Expand Down
5 changes: 4 additions & 1 deletion src/bundler/bundle_v2.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
2 changes: 1 addition & 1 deletion src/bundler/transpiler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
16 changes: 15 additions & 1 deletion src/js_parser/p.rs
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,9 @@ 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` declared by `value_for_import_meta_main`; reserves the
/// name so the iife lowering compares against the host's `module`.
Comment thread
robobun marked this conversation as resolved.
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
Expand Down Expand Up @@ -5272,8 +5275,18 @@ 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 {
//
// 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`).
Comment thread
robobun marked this conversation as resolved.
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,
Expand Down Expand Up @@ -8670,6 +8683,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,
Expand Down
10 changes: 6 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,9 @@ 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 will lower `import.meta.main` via the runtime's `__require`;
/// see `P::value_for_import_meta_main`.
Comment thread
robobun marked this conversation as resolved.
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 @@ -135,7 +137,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,
Expand Down Expand Up @@ -219,7 +221,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,
Expand Down Expand Up @@ -291,7 +293,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,
Expand Down
57 changes: 45 additions & 12 deletions src/js_printer/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -2994,11 +2998,20 @@ 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, so import.meta.main works.
bundle_opts::Format::Iife => self.options.target.is_bun(),
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);
Comment thread
robobun marked this conversation as resolved.
if wrap {
self.print(b"(");
}
if data.inverted {
self.add_source_mapping(expr.loc);
self.print(b"!");
Comment thread
robobun marked this conversation as resolved.
Expand All @@ -3010,11 +3023,20 @@ 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
);

// Prints as an `==` / `!=` expression, so parenthesize like one.
let wrap = level.gte(Level::Equals);
if wrap {
self.print(b"(");
}

self.print_space_before_identifier();
self.add_source_mapping(expr.loc);

Expand All @@ -3030,18 +3052,29 @@ pub(crate) mod __gated_printer {
self.print_whitespacer(ws!(b".main == "));
}

if self.options.target == bun_ast::Target::Node {
// "__require.module"
if let Some(require) = self.options.require_ref {
match self.options.require_ref {
// 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");
} else {
}
// The host's `module`; the renamer reserves the name
// (see `value_for_import_meta_main`).
Comment thread
robobun marked this conversation as resolved.
_ if self.options.module_type == bundle_opts::Format::Iife
|| self.options.target == bun_ast::Target::Node =>
{
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");
_ if self.options.commonjs_module_ref.is_valid() => {
self.print_symbol(self.options.commonjs_module_ref);
}
_ => self.print(b"module"),
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}

if wrap {
self.print(b")");
}
}
}
Expand Down
Loading
Loading