pub mod build_api;
pub mod bytecode;
pub mod macro_context;
pub mod js_bundler_plugin_seam;
pub mod transpiler_cache_seam;
pub mod dev_server;
pub mod hmr;
pub mod vm_loader;
pub use bun_bundler::*;
#[used]
static BAO_BUNDLER_ANCHOR: unsafe extern "C" fn() = __force_link_entry;
unsafe extern "C" {
fn __force_link_entry();
}
#[inline(never)]
pub fn force_link_macro_seam() {
static SLOT: core::sync::atomic::AtomicUsize = core::sync::atomic::AtomicUsize::new(0);
SLOT.store(
macro_context::__bun_macro_context_init as *const () as usize,
core::sync::atomic::Ordering::Relaxed,
);
}
#[derive(Debug)]
pub struct BundleOutput {
pub code: String,
pub source_map: Option<String>,
}
pub fn build(entrypoint: &str, minify: bool, _target: &str) -> Result<BundleOutput, String> {
let path = std::path::Path::new(entrypoint);
let source = std::fs::read_to_string(path)
.map_err(|e| format!("Error reading {}: {}", entrypoint, e))?;
let fname = path.to_str().unwrap_or(entrypoint);
let transpiled = if minify {
bun_transpiler::transpile_ts_drop_comments(&source, fname).unwrap_or_else(|_| source)
} else {
bun_transpiler::transpile_ts(&source, fname).unwrap_or_else(|_| source)
};
let code = if minify {
collapse_whitespace(&transpiled)
} else {
transpiled
};
Ok(BundleOutput {
code,
source_map: None,
})
}
fn collapse_whitespace(input: &str) -> String {
let bytes = input.as_bytes();
let mut out = String::with_capacity(bytes.len());
let mut i = 0;
let mut in_string = false;
let mut delim = b'\0';
let mut prev_was_ws = false;
while i < bytes.len() {
let b = bytes[i];
if in_string {
out.push(b as char);
if b == b'\\' && i + 1 < bytes.len() {
out.push(bytes[i + 1] as char);
i += 2;
continue;
}
if b == delim {
in_string = false;
}
i += 1;
prev_was_ws = false;
continue;
}
match b {
b'\'' | b'"' | b'`' => {
in_string = true;
delim = b;
out.push(b as char);
prev_was_ws = false;
i += 1;
}
b' ' | b'\t' | b'\n' | b'\r' => {
if !prev_was_ws {
out.push(' ');
prev_was_ws = true;
}
i += 1;
}
_ => {
out.push(b as char);
prev_was_ws = false;
i += 1;
}
}
}
out.trim().to_string()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn bytecode_symbol_linked() {
let result = bytecode::generate_cached_bytecode_for_test();
assert!(
result.is_none(),
"Phase 1: SM bytecode cache should return None"
);
}
#[test]
fn build_passthrough_no_minify() {
let input = "let x = 1;\nlet y = 2;\n";
let dir = std::env::temp_dir().join("bao_bundler_test_build_no_minify");
std::fs::create_dir_all(&dir).ok();
let path = dir.join("test_no_minify.js");
std::fs::write(&path, input).unwrap();
let result = build(&path.to_string_lossy(), false, "bundle").unwrap();
assert!(result.code.contains("let x = 1"));
assert!(result.code.contains("let y = 2"));
assert!(result.source_map.is_none());
std::fs::remove_dir_all(&dir).ok();
}
#[test]
fn build_with_minify_removes_comments() {
let input = "let x = 1; // comment\nlet y = 2;";
let dir = std::env::temp_dir().join("bao_bundler_test_build_minify");
std::fs::create_dir_all(&dir).ok();
let path = dir.join("test_minify.js");
std::fs::write(&path, input).unwrap();
let result = build(&path.to_string_lossy(), true, "bundle").unwrap();
assert!(!result.code.contains("comment"));
assert!(result.code.contains("let"));
std::fs::remove_dir_all(&dir).ok();
}
#[test]
fn build_missing_file_returns_error() {
let result = build("/nonexistent/path/file.js", false, "bundle");
assert!(result.is_err());
let err = result.err().unwrap();
assert!(err.contains("Error reading"));
}
#[test]
fn build_output_has_no_source_map() {
let dir = std::env::temp_dir().join("bao_bundler_test_source_map");
std::fs::create_dir_all(&dir).ok();
let path = dir.join("test.js");
std::fs::write(&path, "let x = 1;").unwrap();
let result = build(&path.to_string_lossy(), false, "bundle").unwrap();
assert!(result.source_map.is_none());
std::fs::remove_dir_all(&dir).ok();
}
#[test]
fn build_transpiles_typescript() {
let input =
"const x: number = 1;\nfunction add(a: number, b: number): number { return a + b; }\n";
let dir = std::env::temp_dir().join("bao_bundler_test_ts");
std::fs::create_dir_all(&dir).ok();
let path = dir.join("entry.ts");
std::fs::write(&path, input).unwrap();
let result = build(&path.to_string_lossy(), false, "bundle").unwrap();
assert!(!result.code.contains(": number"));
assert!(result.code.contains("const x = 1"));
assert!(result.code.contains("function add(a, b)"));
std::fs::remove_dir_all(&dir).ok();
}
#[test]
fn build_preserves_jsx_like_syntax() {
let input = "const el = <div className=\"app\">hello</div>;";
let dir = std::env::temp_dir().join("bao_bundler_test_jsx");
std::fs::create_dir_all(&dir).ok();
let path = dir.join("test.jsx");
std::fs::write(&path, input).unwrap();
let result = build(&path.to_string_lossy(), false, "bundle").unwrap();
assert!(result.code.contains("className"));
assert!(result.code.contains("hello"));
std::fs::remove_dir_all(&dir).ok();
}
#[test]
fn collapse_whitespace_collapses_runs() {
let input = "let x = 1;";
let result = collapse_whitespace(input);
assert_eq!(result, "let x = 1;");
}
#[test]
fn collapse_whitespace_empty_input() {
let result = collapse_whitespace("");
assert!(result.is_empty());
}
#[test]
fn collapse_whitespace_preserves_double_quoted_strings() {
let input = r#"let s = "hello world";"#;
let result = collapse_whitespace(input);
assert!(result.contains("\"hello world\""));
}
#[test]
fn collapse_whitespace_preserves_single_quoted_strings() {
let input = r"let s = 'hello world';";
let result = collapse_whitespace(input);
assert!(result.contains("'hello world'"));
}
#[test]
fn collapse_whitespace_preserves_template_literals() {
let input = "let s = `hello world`;";
let result = collapse_whitespace(input);
assert!(result.contains("`hello world`"));
}
#[test]
fn collapse_whitespace_preserves_escaped_delimiter() {
let input = r#"let s = "hello \"world\" inside";"#;
let result = collapse_whitespace(input);
assert!(result.contains("world"));
}
#[test]
fn collapse_whitespace_trims_edges() {
let input = " let x = 1; ";
let result = collapse_whitespace(input);
assert_eq!(result, "let x = 1;");
}
#[test]
fn e2e_bundle_simple_module() {
let input = r#"
// Entry module
function greet(name) {
return "Hello, " + name + "!";
}
console.log(greet("Bao"));
"#;
let dir = std::env::temp_dir().join("bao_bundler_e2e_simple");
std::fs::create_dir_all(&dir).ok();
let path = dir.join("entry.js");
std::fs::write(&path, input).unwrap();
let result = build(&path.to_string_lossy(), true, "bundle").unwrap();
assert!(!result.code.contains("// Entry"));
assert!(result.code.contains("greet"));
assert!(result.code.contains("Hello"));
std::fs::remove_dir_all(&dir).ok();
}
#[test]
fn e2e_bundle_preserves_esm_syntax() {
let input = "import { foo } from './bar.js';\nexport const baz = foo();\n";
let dir = std::env::temp_dir().join("bao_bundler_e2e_esm");
std::fs::create_dir_all(&dir).ok();
let path = dir.join("entry.mjs");
std::fs::write(&path, input).unwrap();
let result = build(&path.to_string_lossy(), false, "esm").unwrap();
assert!(result.code.contains("import"));
assert!(result.code.contains("export"));
std::fs::remove_dir_all(&dir).ok();
}
#[test]
fn e2e_bundle_minify_collapses_complex_js() {
let input = r#"
/* Application bundle */
const API_URL = "https://api.example.com";
async function fetchData(endpoint) {
const response = await fetch(API_URL + endpoint);
return response.json();
}
// Export for consumers
export { fetchData, API_URL };
"#;
let dir = std::env::temp_dir().join("bao_bundler_e2e_complex");
std::fs::create_dir_all(&dir).ok();
let path = dir.join("app.js");
std::fs::write(&path, input).unwrap();
let result = build(&path.to_string_lossy(), true, "bundle").unwrap();
assert!(!result.code.contains("/* Application bundle */"));
assert!(result.code.contains("fetchData"));
assert!(result.code.contains("API_URL"));
assert!(result.code.len() < input.len());
std::fs::remove_dir_all(&dir).ok();
}
#[test]
fn e2e_bundle_transpiles_ts_module() {
let input = "interface User { id: number; name: string; }\nexport const u: User = { id: 1, name: 'a' };\n";
let dir = std::env::temp_dir().join("bao_bundler_e2e_ts");
std::fs::create_dir_all(&dir).ok();
let path = dir.join("user.ts");
std::fs::write(&path, input).unwrap();
let result = build(&path.to_string_lossy(), false, "bundle").unwrap();
assert!(!result.code.contains("interface User"));
assert!(result.code.contains("export const u ="));
assert!(result.code.contains("name: 'a'"));
std::fs::remove_dir_all(&dir).ok();
}
}