Skip to content
Closed
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
170 changes: 115 additions & 55 deletions src/bundler/Chunk.rs
Original file line number Diff line number Diff line change
Expand Up @@ -510,6 +510,24 @@ fn additional_output_file_index(f: &AdditionalFile) -> usize {
}
}

/// How the path that replaces a placeholder is written into the output.
#[derive(Clone, Copy)]
enum SpliceEscape {
/// Copied as is.
Raw,
/// The placeholder is the body of a `"..."` CSS string.
CssString,
}

impl SpliceEscape {
fn for_chunk(chunk: &Chunk) -> Self {
match chunk.content {
Content::Css(_) => Self::CssString,
Content::Javascript(_) | Content::Html => Self::Raw,
}
}
}

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 @@ -571,6 +589,47 @@ impl IntermediateOutput {
dst
}

/// The (prefix, path) pair that replaces a placeholder, shared by the sizing and writing pass.
fn spliced_path_parts<'a>(
file_path: &[u8],
import_prefix: &'a [u8],
from_chunk_dir: &[u8],
use_outdir_relative_path: bool,
file_path_posix: &'a mut Vec<u8>,
) -> [&'a [u8]; 2] {
// Normalize Windows paths to '/' in a copy because the source is borrowed immutably.
file_path_posix.clear();
file_path_posix.extend_from_slice(file_path);
bun_paths::resolve_path::platform_to_posix_in_place::<u8>(file_path_posix);
let file_path: &'a [u8] = file_path_posix;
cheap_prefix_normalizer(
import_prefix,
if use_outdir_relative_path {
file_path
} else {
bun_paths::resolve_path::relative_platform::<bun_paths::platform::Posix, false>(
from_chunk_dir,
file_path,
)
},
)
}

/// Writes one of the slices from `spliced_path_parts`.
fn write_spliced_path<W: bun_io::Write + ?Sized>(
writer: &mut W,
path: &[u8],
escape: SpliceEscape,
) -> bun_io::Result<()> {
match escape {
SpliceEscape::Raw => writer.write_all(path),
// esbuild copies the path as is, so a `"` in it ends the string.
SpliceEscape::CssString => {
bun_css::css_parser::serializer::serialize_string_contents(path, writer)
}
}
}

#[allow(clippy::too_many_arguments)]
pub fn code<'d>(
&mut self,
Expand All @@ -590,6 +649,7 @@ impl IntermediateOutput {
shift_tracking: SourceMapShiftTracking,
) -> Result<CodeResult, AllocError> {
let display_size: Option<&mut usize> = display_size.into();
let escape = SpliceEscape::for_chunk(chunk);
match shift_tracking {
SourceMapShiftTracking::Enabled => self.code_with_source_map_shifts::<true>(
allocator_to_use,
Expand All @@ -601,6 +661,7 @@ impl IntermediateOutput {
display_size,
reference_path_style,
None,
escape,
),
SourceMapShiftTracking::Disabled => self.code_with_source_map_shifts::<false>(
allocator_to_use,
Expand All @@ -612,10 +673,33 @@ impl IntermediateOutput {
display_size,
reference_path_style,
None,
escape,
),
}
}

/// `code()` for the metafile, where `chunk` only supplies the directory paths are relative to.
pub(crate) fn code_for_metafile(
&mut self,
parse_graph: &Graph,
linker_graph: &LinkerGraph<'_>,
chunk: &Chunk,
chunks: &[Chunk],
) -> Result<CodeResult, AllocError> {
self.code_with_source_map_shifts::<false>(
None,
parse_graph,
linker_graph,
b"",
chunk,
chunks,
None,
ReferencePathStyle::ImporterRelative,
None,
SpliceEscape::Raw,
)
}

/// Like `code()` but with standalone HTML support.
/// When `standalone_chunk_contents` is provided, chunk piece references are
/// resolved to inline code content instead of file paths. Asset references
Expand All @@ -638,6 +722,7 @@ impl IntermediateOutput {
standalone_chunk_contents: &[Option<Box<[u8]>>],
) -> Result<CodeResult, AllocError> {
let display_size: Option<&mut usize> = display_size.into();
let escape = SpliceEscape::for_chunk(chunk);
match shift_tracking {
SourceMapShiftTracking::Enabled => self.code_with_source_map_shifts::<true>(
allocator_to_use,
Expand All @@ -649,6 +734,7 @@ impl IntermediateOutput {
display_size,
reference_path_style,
Some(standalone_chunk_contents),
escape,
),
SourceMapShiftTracking::Disabled => self.code_with_source_map_shifts::<false>(
allocator_to_use,
Expand All @@ -660,12 +746,13 @@ impl IntermediateOutput {
display_size,
reference_path_style,
Some(standalone_chunk_contents),
escape,
),
}
}

#[allow(clippy::too_many_arguments)]
pub(crate) fn code_with_source_map_shifts<const ENABLE_SOURCE_MAP_SHIFTS: bool>(
fn code_with_source_map_shifts<const ENABLE_SOURCE_MAP_SHIFTS: bool>(
&mut self,
allocator_to_use: Option<&DynAlloc>,
graph: &Graph,
Expand All @@ -677,6 +764,7 @@ impl IntermediateOutput {
display_size: Option<&mut usize>,
reference_path_style: ReferencePathStyle,
standalone_chunk_contents: Option<&[Option<Box<[u8]>>]>,
escape: SpliceEscape,
) -> Result<CodeResult, AllocError> {
// `Graph.input_files` SoA accessors live in `Graph::InputFileColumns`;
// `LinkerGraph.files` SoA (`items_entry_point_chunk_index`) lands with
Expand Down Expand Up @@ -801,27 +889,18 @@ impl IntermediateOutput {
QueryKind::None | QueryKind::ChunkId => unreachable!(),
};

// Normalize like the write pass below so the counted length matches.
let file_path: &[u8] = {
file_path_posix.clear();
file_path_posix.extend_from_slice(file_path);
bun_paths::resolve_path::platform_to_posix_in_place::<u8>(
&mut file_path_posix,
);
&file_path_posix
};
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::<
bun_paths::platform::Posix,
false,
>(from_chunk_dir, file_path)
},
);
count += cheap_normalizer[0].len() + cheap_normalizer[1].len();
from_chunk_dir,
use_outdir_relative_path,
&mut file_path_posix,
) {
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 @@ -994,43 +1073,24 @@ impl IntermediateOutput {
_ => unreachable!(),
};

// Normalize Windows paths to '/' in a copy because the source is borrowed immutably.
let file_path: &[u8] = {
file_path_posix.clear();
file_path_posix.extend_from_slice(file_path);
bun_paths::resolve_path::platform_to_posix_in_place::<u8>(
&mut file_path_posix,
);
&file_path_posix
};
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::<
bun_paths::platform::Posix,
false,
>(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_posix,
) {
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
16 changes: 2 additions & 14 deletions src/bundler/linker_context/MetafileBuilder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ use bun_ast::ExportsKind;
use bun_ast::ImportKind;
use bun_ast::ImportRecordFlags;

use crate::chunk::{Content as ChunkContent, ReferencePathStyle, SourceMapShiftTracking};
use crate::chunk::Content as ChunkContent;
use crate::options::Loader;
use crate::{Chunk, Index, LinkerContext};

Expand Down Expand Up @@ -454,19 +454,7 @@ pub(crate) fn generate(c: &mut LinkerContext, chunks: &mut [Chunk]) -> crate::Re
)?;

// Get final output with all chunk references resolved.
// `code()` takes the dummy chunk and the full slice as `&`, so pass
// `&chunks[0]` directly — overlapping shared borrows are fine.
let code_result = intermediate.code(
None,
parse_graph,
&c.graph,
b"", // no import prefix for metafile
&chunks[0],
chunks,
None, // no display size
ReferencePathStyle::ImporterRelative,
SourceMapShiftTracking::Disabled,
)?;
let code_result = intermediate.code_for_metafile(parse_graph, &c.graph, &chunks[0], chunks)?;

Ok(code_result.buffer)
}
Expand Down
11 changes: 9 additions & 2 deletions src/css/css_parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5763,11 +5763,18 @@ pub mod serializer {
writer: &mut W,
) -> bun_io::Result<()> {
writer.write_all(b"\"")?;
let mut sw = CssStringWriter::new(writer);
sw.write_str(value)?;
serialize_string_contents(value, writer)?;
writer.write_all(b"\"")
}

/// Write the inside of a double-quoted CSS string token, without the quotes.
pub fn serialize_string_contents<W: WriteAll + ?Sized>(
value: &[u8],
writer: &mut W,
) -> bun_io::Result<()> {
CssStringWriter::new(writer).write_str(value)
}

pub(crate) fn serialize_dimension(
value: f32,
unit: &'static [u8],
Expand Down
29 changes: 26 additions & 3 deletions src/css/printer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -333,6 +333,14 @@ impl<'a> Printer<'a> {

#[inline]
pub(crate) fn get_import_record_url(&mut self, import_record_idx: u32) -> PrintResult<&[u8]> {
self.resolve_import_record_url(import_record_idx)
.map(|resolved| resolved.url)
}

pub(crate) fn resolve_import_record_url(
&mut self,
import_record_idx: u32,
) -> PrintResult<ResolvedImportRecordUrl<'_>> {
let Some(import_info) = &self.import_info else {
return Err(self.add_no_import_record_error());
};
Expand Down Expand Up @@ -367,24 +375,39 @@ impl<'a> Printer<'a> {
Some(i) => &suffix[i as usize..],
None => b"",
};
return Ok(with_suffix(urls_for_css, fragment));
return Ok(ResolvedImportRecordUrl {
url: with_suffix(urls_for_css, fragment),
is_placeholder: false,
});
}
// It is a chunk URL (copied asset): keep the full `?query#fragment`.
let unique_key_for_additional_file =
import_info.ast_unique_key_for_additional_file[record.source_index.get() as usize];
if !unique_key_for_additional_file.is_empty() {
return Ok(with_suffix(unique_key_for_additional_file, suffix));
return Ok(ResolvedImportRecordUrl {
url: with_suffix(unique_key_for_additional_file, suffix),
is_placeholder: true,
});
}
}
// External URL stays as-is
Ok(record.path.text)
Ok(ResolvedImportRecordUrl {
url: record.path.text,
is_placeholder: false,
})
}

pub(crate) fn context(&self) -> Option<&css::StyleContext<'a>> {
self.ctx
}
}

pub(crate) struct ResolvedImportRecordUrl<'u> {
pub(crate) url: &'u [u8],
/// A unique key the bundler fills in after printing. Always quoted, as in esbuild#3410.
pub(crate) is_placeholder: bool,
}

/// `Printer` participates in `serializer::serialize_*<W: bun_io::Write>` so
/// `Token::to_css` and friends can write through it generically.
impl<'a> bun_io::Write for Printer<'a> {
Expand Down
11 changes: 7 additions & 4 deletions src/css/properties/custom.rs
Original file line number Diff line number Diff line change
Expand Up @@ -96,13 +96,16 @@ mod ext {
let is_internal = import_record.tag.is_internal();
// `get_import_record_url` reborrows &mut *dest, so capture
// `is_internal` first.
let url: &'static [u8] = {
let u = dest.get_import_record_url(this.import_record_idx)?;
let (url, is_placeholder): (&'static [u8], bool) = {
let resolved = dest.resolve_import_record_url(this.import_record_idx)?;
// SAFETY: import-record paths are arena/source-owned and outlive `dest`.
unsafe { &*std::ptr::from_ref::<[u8]>(u) }
(
unsafe { &*std::ptr::from_ref::<[u8]>(resolved.url) },
resolved.is_placeholder,
)
};

if dest.minify && !is_internal {
if dest.minify && !is_internal && !is_placeholder {
let mut buf: Vec<u8> = Vec::new();
// PERF(alloc) we could use stack fallback here?
let _ = Token::UnquotedUrl(url).to_css_generic(&mut buf);
Expand Down
Loading
Loading