Skip to content
Open
Show file tree
Hide file tree
Changes from 2 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
215 changes: 142 additions & 73 deletions src/bundler/Chunk.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ use crate::bun_css;
use crate::bun_fs;

use crate::Graph::Graph;
use crate::html_import_manifest as HTMLImportManifest;
use crate::HTMLImportManifest;
use crate::options::{self, Loader};
use crate::{
AdditionalFile, CompileResult, LinkerContext, LinkerGraph, PartRange, PathTemplate,
Expand Down Expand Up @@ -459,6 +459,37 @@ fn additional_output_file_index(f: &AdditionalFile) -> usize {
}
}

/// How the text that replaces a placeholder has to be written into the chunk.
#[derive(Clone, Copy)]
enum SpliceEscape {
/// CSS and HTML chunks: written as-is; their placeholders sit in `url()`s
/// and attributes, not in JS string literals.
Comment thread
robobun marked this conversation as resolved.
Outdated
Raw,
/// JS chunks: every placeholder (import path, asset path, HTML import
/// manifest) is the body of a double-quoted string literal, so the
/// replacement is escaped the way the printer would have escaped it.
/// `ascii_only` is set for chunks that start with `// @bun` (see
/// postProcessJSChunk): the runtime loads those as Latin-1 without
/// re-parsing them, so the printer keeps them ASCII and the splices must too.
Comment thread
robobun marked this conversation as resolved.
Outdated
JsString { ascii_only: bool },
}

impl SpliceEscape {
fn for_chunk(chunk: &Chunk, linker_graph: &LinkerGraph<'_>) -> Self {
if !chunk.content.is_javascript() {
return Self::Raw;
}
Self::JsString {
ascii_only: linker_graph.ast.items_target()[chunk.entry_point.source_index() as usize]
.is_bun(),
}
}

fn ascii_only(self) -> bool {
matches!(self, Self::JsString { ascii_only: true })
}
}

impl IntermediateOutput {
pub(crate) fn allocator_for_size(_size: usize) -> &'static DynAlloc {
// mimalloc serves large allocations via mmap already, so the global
Expand Down Expand Up @@ -520,6 +551,61 @@ impl IntermediateOutput {
dst
}

/// The two slices (prefix, path) that replace a chunk/asset placeholder.
/// `code_with_source_map_shifts` calls this once to size its buffer and once
/// to fill it, and `write_spliced_path` escapes what this returns, so both
/// passes have to go through here to count the same bytes.
Comment thread
robobun marked this conversation as resolved.
Outdated
fn spliced_path_parts<'a>(
file_path: &[u8],
import_prefix: &'a [u8],
from_chunk_dir: &[u8],
use_outdir_relative_path: bool,
file_path_buf: &'a mut [u8],
relative_platform_buf: &'a mut [u8],
) -> [&'a [u8]; 2] {
// normalize windows paths to '/'
// The source slices are reachable only
// through `&Graph` / `&[Chunk]` here; materialising `&mut` from a
// shared-provenance pointer is UB regardless of whether the write
// happens. Copy into a pooled scratch buffer and normalise that.
Comment thread
robobun marked this conversation as resolved.
Outdated
let file_path: &'a [u8] = {
let dst = &mut file_path_buf[..file_path.len()];
dst.copy_from_slice(file_path);
bun_paths::resolve_path::platform_to_posix_in_place::<u8>(dst);
dst
};
cheap_prefix_normalizer(
import_prefix,
if use_outdir_relative_path {
file_path
} else {
bun_paths::resolve_path::relative_platform_buf::<bun_paths::platform::Posix, false>(
relative_platform_buf,
from_chunk_dir,
file_path,
)
},
)
}

/// Writes one of the slices from `spliced_path_parts`.
fn write_spliced_path<W: bun_io::Write>(
writer: &mut W,
path: &[u8],
escape: SpliceEscape,
) -> Result<(), crate::Error> {
match escape {
SpliceEscape::Raw => writer.write_all(path)?,
SpliceEscape::JsString { ascii_only } => {
bun_js_printer::write_pre_quoted_string_inner::<
_,
{ bun_js_printer::Encoding::Utf8 },
>(path, writer, b'"', ascii_only, false)?
}
}
Ok(())
}

pub(crate) fn get_size(&self) -> usize {
match self {
IntermediateOutput::Pieces(pieces) => {
Expand Down Expand Up @@ -691,6 +777,8 @@ impl IntermediateOutput {
&[]
};

let escape = SpliceEscape::for_chunk(chunk, linker_graph);

for piece in pieces.slice() {
count += piece.data.len();

Expand Down Expand Up @@ -750,34 +838,35 @@ impl IntermediateOutput {
}

QueryKind::HtmlImport => {
count += bun_core::fmt::count(format_args!(
"{}",
HTMLImportManifest::format_escaped_json(
piece.query.index(),
graph,
chunks,
linker_graph,
)
));
let mut counter = bun_io::DiscardingWriter::new();
HTMLImportManifest::write_escaped_json(
piece.query.index(),
graph,
linker_graph,
chunks,
escape.ascii_only(),
&mut counter,
)
.expect("unreachable");
count += counter.count;
continue;
}
QueryKind::None => unreachable!(),
};

let cheap_normalizer = cheap_prefix_normalizer(
for part in Self::spliced_path_parts(
file_path,
import_prefix,
if use_outdir_relative_path {
file_path
} else {
bun_paths::resolve_path::relative_platform_buf::<
bun_paths::platform::Posix,
false,
>(
&mut relative_platform_buf[..], from_chunk_dir, file_path
)
},
);
count += cheap_normalizer[0].len() + cheap_normalizer[1].len();
from_chunk_dir,
use_outdir_relative_path,
&mut file_path_buf[..],
&mut relative_platform_buf[..],
) {
let mut counter = bun_io::DiscardingWriter::new();
Self::write_spliced_path(&mut counter, part, escape)
.expect("unreachable");
count += counter.count;
}
}
QueryKind::None => {}
}
Expand Down Expand Up @@ -911,17 +1000,20 @@ impl IntermediateOutput {
}

QueryKind::HtmlImport => {
let mut cursor: &mut [u8] = remain;
let before_len = cursor.len();
HTMLImportManifest::write_escaped_json(
piece.query.index(),
graph,
linker_graph,
chunks,
&mut cursor,
)
.expect("unreachable");
let written = before_len - cursor.len();
let written = {
let mut stream =
bun_io::FixedBufferStream::new_mut(&mut *remain);
HTMLImportManifest::write_escaped_json(
piece.query.index(),
graph,
linker_graph,
chunks,
escape.ascii_only(),
&mut stream,
)
.expect("unreachable");
stream.pos
};

if ENABLE_SOURCE_MAP_SHIFTS {
// The placeholder was an HtmlImport unique key, which has
Expand All @@ -936,48 +1028,25 @@ impl IntermediateOutput {
_ => unreachable!(),
};

// normalize windows paths to '/'
// The source slices are reachable only
// through `&Graph` / `&[Chunk]` here; materialising `&mut` from a
// shared-provenance pointer is UB regardless of whether the write
// happens. Copy into a pooled scratch buffer and normalise that.
let file_path: &[u8] = {
let n = file_path.len();
let dst = &mut file_path_buf[..n];
dst.copy_from_slice(file_path);
bun_paths::resolve_path::platform_to_posix_in_place::<u8>(dst);
dst
};
let cheap_normalizer = cheap_prefix_normalizer(
for part in Self::spliced_path_parts(
file_path,
import_prefix,
if use_outdir_relative_path {
file_path
} else {
bun_paths::resolve_path::relative_platform_buf::<
bun_paths::platform::Posix,
false,
>(
&mut relative_platform_buf[..], from_chunk_dir, file_path
)
},
);

if !cheap_normalizer[0].is_empty() {
remain[..cheap_normalizer[0].len()]
.copy_from_slice(cheap_normalizer[0]);
remain = &mut remain[cheap_normalizer[0].len()..];
if ENABLE_SOURCE_MAP_SHIFTS {
shift.after.advance(cheap_normalizer[0]);
}
}

if !cheap_normalizer[1].is_empty() {
remain[..cheap_normalizer[1].len()]
.copy_from_slice(cheap_normalizer[1]);
remain = &mut remain[cheap_normalizer[1].len()..];
from_chunk_dir,
use_outdir_relative_path,
&mut file_path_buf[..],
&mut relative_platform_buf[..],
) {
let written = {
let mut stream =
bun_io::FixedBufferStream::new_mut(&mut *remain);
Self::write_spliced_path(&mut stream, part, escape)
.expect("unreachable");
stream.pos
};
if ENABLE_SOURCE_MAP_SHIFTS {
shift.after.advance(cheap_normalizer[1]);
shift.after.advance(&remain[..written]);
}
remain = &mut remain[written..];
}

if ENABLE_SOURCE_MAP_SHIFTS {
Expand Down
Loading