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
52 changes: 42 additions & 10 deletions bench/sourcemap/code-frame-bench.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,21 +22,34 @@ const dir = fs.mkdtempSync(path.join(os.tmpdir(), "code-frame-bench-"));
process.on("exit", () => fs.rmSync(dir, { recursive: true, force: true }));

type Places = { sites: (() => Error)[]; long: () => Error };
type Shape = {
/** One line of padding, without its line break. */
line?: string;
lineBreak?: string;
/** Where the places are, from 0 (the first lines) to 1 (the last lines). */
at?: number;
/** Text before everything else. */
head?: string;
};

// `sites[i]()` returns an error thrown on its own line. `long()` throws on a
// line of 5000 bytes. Comment lines before and after them pad the source to
// `bytes`, so the places are in the middle of a large source.
function placesSource(bytes: number): string {
let places = "export const sites: (() => Error)[] = [\n";
// `bytes`, so the places are in the middle of a large source unless `at` says
// otherwise.
function placesSource(bytes: number, shape: Shape = {}): string {
const { line = "// padding padding padding padding padding padding padding padding", lineBreak = "\n", at = 0.5 } = shape;
let places = "export const sites: (() => Error)[] = [" + lineBreak;
for (let i = 0; i <= KEPT; i++) {
places += ` () => { try { throw new Error("site ${i}"); } catch (e) { return e as Error; } },\n`;
places += ` () => { try { throw new Error("site ${i}"); } catch (e) { return e as Error; } },` + lineBreak;
}
places += "];\n";
places += `export function long(): Error { try { throw new Error("long"); } catch (e) { return e as Error; } } // ${Buffer.alloc(5000, "p")}\n`;
const line = "// padding padding padding padding padding padding padding padding\n";
const lines = Math.max(0, bytes - places.length) / 2 / line.length;
const padding = Buffer.alloc(Math.floor(lines) * line.length, line).toString();
return padding + places + padding;
places += "];" + lineBreak;
places += `export function long(): Error { try { throw new Error("long"); } catch (e) { return e as Error; } } // ${Buffer.alloc(5000, "p")}${lineBreak}`;
const head = shape.head ?? "";
const padding = line + lineBreak;
const lines = Math.floor(Math.max(0, bytes - places.length - head.length) / Buffer.byteLength(padding));
const before = Math.floor(lines * at);
const repeat = (count: number) => Buffer.alloc(count * Buffer.byteLength(padding), padding).toString();
return head + repeat(before) + places + repeat(lines - before);
}

// Microseconds per call of `fn(i)`. A pass is `callsPerPass` calls, and only
Expand Down Expand Up @@ -98,3 +111,22 @@ for (const [size, bytes] of [
const bundle = path.join(dir, "out", `${size}-bundled.js`);
report(`${size} bundle with an external source map`, bundle, fs.statSync(bundle + ".map").size);
}

// More shapes of a large module that bun transpiled. The printer reads the file
// and finds the lines that end at the error, so what comes before the error
// counts. Each print is of another place than the `KEPT` prints before it, so
// each one loads.
for (const [name, shape] of [
["places in the first lines", { at: 0 }],
["places in the last lines", { at: 1 }],
["tabs", { line: "//\tpadding padding padding padding\tpadding padding padding" }],
["text that is not ASCII", { line: "// " + Buffer.alloc(64, "é").toString() }],
["one character that is not ASCII, places in the last lines", { head: "// é\n", at: 1 }],
["lines that end in CRLF", { lineBreak: "\r\n" }],
] as [string, Shape][]) {
const source = path.join(dir, `shape-${name.replace(/[^a-z]+/gi, "-")}.ts`);
fs.writeFileSync(source, placesSource(LARGE_BYTES, shape));
const { sites } = require(source) as Places;
for (let i = 0; i <= KEPT; i++) Bun.inspect(sites[i]());
console.log(`[${flag}] large module, ${name.padEnd(58)} ${measure(KEPT + 1, i => Bun.inspect(sites[i]())).padStart(16)}`);
}
11 changes: 11 additions & 0 deletions src/bundler/options.rs
Original file line number Diff line number Diff line change
Expand Up @@ -467,6 +467,17 @@ pub struct LoaderResult<'a> {
pub package_json: Option<&'a PackageJSON>,
}

impl LoaderResult<'_> {
/// The loader that `fetch_without_on_load_plugins` and the error printer take for a name that gives none. Not the default of a load through the module loader.
pub fn default_loader(&self) -> Loader {
if self.is_main {
Loader::Js
} else {
Loader::File
}
}
}

/// Vite's import path suffixes, as in `./icon.svg?raw`.
#[derive(Clone, Copy)]
pub enum ImportSuffix {
Expand Down
16 changes: 1 addition & 15 deletions src/jsc/ConsoleObject.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1154,28 +1154,14 @@ pub fn write_trace(writer: &mut dyn bun_io::Write, global: &JSGlobalObject) {

let err = global.create_error_instance(format_args!("trace output"));
// `remap_zig_exception` populates `holder.zig_exception()` from `err`.
// `exception` and `&holder.need_to_clear_parser_arena_on_deinit` would be
// two simultaneous `&mut` into `holder`. Capture the flag in a local and
// write it back after.
let mut need_to_clear = holder.need_to_clear_parser_arena_on_deinit;
vm.remap_zig_exception(
holder.zig_exception(),
err,
None,
&mut need_to_clear,
&mut source_code,
false,
);
holder.need_to_clear_parser_arena_on_deinit = need_to_clear;
vm.remap_zig_exception(holder.zig_exception(), err, None, &mut source_code, false);

let mut adapter = DynWriteAdapter::new(writer);
let _ = VirtualMachine::print_stack_trace(
adapter.interface(),
&holder.zig_exception().stack,
Output::enable_ansi_colors_stderr(),
);

holder.deinit(vm);
}

// ───────────────────────────────────────────────────────────────────────────
Expand Down
16 changes: 1 addition & 15 deletions src/jsc/ModuleLoader.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
//! The `ModuleLoader` struct, `FetchFlags`, and the builtin-module lookup
//! The `ModuleLoader` struct and the builtin-module lookup
//! helpers. `transpile_source_code` / `fetch_builtin_module` and the
//! `Bun__transpile*` / `Bun__resolve*` C++ entry points live in
//! `bun_runtime::jsc_hooks` (they reach into `node::fs`, the transpiler, and
Expand Down Expand Up @@ -101,27 +101,13 @@ impl Drop for ArenaResetGuard {
}
}

/// Dumps the module source to a file in /tmp/bun-debug-src/{filepath}
#[derive(Copy, Clone, PartialEq, Eq)]
pub enum FetchFlags {
Transpile,
PrintSource,
}

impl FetchFlags {
pub const fn disable_transpiling(self) -> bool {
!matches!(self, FetchFlags::Transpile)
}
}

pub struct TranspileArgs<'a> {
pub specifier: &'a [u8],
pub referrer: &'a [u8],
pub input_specifier: &'a bun_core::String,
pub log: *mut bun_ast::Log,
pub virtual_source: Option<&'a bun_ast::Source<'a>>,
pub global_object: &'a JSGlobalObject,
pub flags: FetchFlags,
/// Raw so the `.wasm` re-entry can mutate `loader` and recurse.
pub extra: *mut TranspileExtra<'a>,
}
Expand Down
1 change: 0 additions & 1 deletion src/jsc/RuntimeTranspilerStore.rs
Original file line number Diff line number Diff line change
Expand Up @@ -604,7 +604,6 @@ impl TranspilerJob {
&referrer,
Some(loader),
&mut log,
crate::module_loader::FetchFlags::Transpile,
);
}

Expand Down
Loading
Loading