use crate::mal_prelude::*;
use core::cell::UnsafeCell;
use core::fmt;
use std::io::Write as _;
use bun_alloc::AllocError;
use bun_ast::{ImportKind, ImportRecord};
use bun_ast::{Ref, Stmt};
use bun_collections::{ArrayHashMap, AutoBitSet, VecExt};
use bun_core::{FeatureFlags, Output};
use bun_ast::Index;
use bun_core::{immutable as strings, string_joiner::StringJoiner};
use bun_sourcemap as source_map;
use crate::analyze_transpiled_module;
use crate::bun_css;
use crate::bun_fs;
use crate::Graph::Graph;
use crate::html_import_manifest as HTMLImportManifest;
use crate::options::{self, Loader};
use crate::{
AdditionalFile, CompileResult, LinkerContext, LinkerGraph, PartRange, PathTemplate,
cheap_prefix_normalizer,
};
use crate::IndexInt;
pub struct ChunkImport {
pub chunk_index: u32,
pub import_kind: ImportKind,
}
pub struct Chunk {
pub unique_key: &'static [u8],
pub files_with_parts_in_chunk: ArrayHashMap<IndexInt, core::sync::atomic::AtomicUsize>,
pub entry_bits: AutoBitSet,
pub final_rel_path: Box<[u8]>,
pub template: PathTemplate,
pub cross_chunk_imports: Vec<ChunkImport>,
pub content: Content,
pub entry_point: EntryPoint,
pub output_source_map: source_map::SourceMapPieces,
pub intermediate_output: IntermediateOutput,
pub isolated_hash: u64,
pub renamer: bun_renamer::ChunkRenamer,
pub compile_results_for_chunk: CompileResultSlots,
pub metafile_chunk_json: Box<[u8]>,
pub flags: Flags,
}
bitflags::bitflags! {
#[derive(Clone, Copy, Default)]
pub struct Flags: u8 {
const IS_EXECUTABLE = 1 << 0;
const HAS_HTML_CHUNK = 1 << 1;
const IS_BROWSER_CHUNK_FROM_SERVER_BUILD = 1 << 2;
}
}
impl Default for Content {
fn default() -> Self {
Content::Javascript(JavaScriptChunk::default())
}
}
unsafe impl Send for Chunk {}
unsafe impl Sync for Chunk {}
#[derive(Default)]
#[repr(transparent)]
pub struct CompileResultSlots(Box<[UnsafeCell<CompileResult>]>);
unsafe impl Sync for CompileResultSlots {}
impl CompileResultSlots {
pub fn new(len: usize) -> Self {
let mut v = Vec::with_capacity(len);
v.resize_with(len, || UnsafeCell::new(CompileResult::default()));
Self(v.into_boxed_slice())
}
#[inline]
pub fn len(&self) -> usize {
self.0.len()
}
#[inline]
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
#[inline]
pub fn iter(&self) -> impl ExactSizeIterator<Item = &CompileResult> + '_ {
self.0.iter().map(|c| unsafe { &*c.get() })
}
}
impl core::ops::Index<usize> for CompileResultSlots {
type Output = CompileResult;
#[inline]
fn index(&self, i: usize) -> &CompileResult {
unsafe { &*self.0[i].get() }
}
}
impl Default for Chunk {
fn default() -> Self {
Chunk {
unique_key: b"",
files_with_parts_in_chunk: ArrayHashMap::new(),
entry_bits: AutoBitSet::init_empty(0).expect("static AutoBitSet"),
final_rel_path: Box::default(),
template: PathTemplate::default(),
cross_chunk_imports: Vec::new(),
content: Content::default(),
entry_point: EntryPoint::default(),
output_source_map: source_map::SourceMapPieces::default(),
intermediate_output: IntermediateOutput::default(),
isolated_hash: u64::MAX,
renamer: bun_renamer::ChunkRenamer::default(),
compile_results_for_chunk: CompileResultSlots::default(),
metafile_chunk_json: Box::default(),
flags: Flags::default(),
}
}
}
impl Chunk {
#[inline]
pub unsafe fn write_compile_result_slot(chunk: *mut Chunk, i: usize, result: CompileResult) {
unsafe {
let slots: *mut CompileResultSlots =
core::ptr::addr_of_mut!((*chunk).compile_results_for_chunk);
let cells: *mut [UnsafeCell<CompileResult>] =
core::ptr::read(slots.cast::<*mut [UnsafeCell<CompileResult>]>());
debug_assert!(
i < cells.len(),
"compile_results_for_chunk slot out of bounds"
);
let cell: *mut UnsafeCell<CompileResult> =
cells.cast::<UnsafeCell<CompileResult>>().add(i);
*cell.cast::<CompileResult>() = result;
}
}
#[inline]
pub fn is_entry_point(&self) -> bool {
self.entry_point.is_entry_point()
}
pub fn closing_tag_for_content(&self) -> &'static [u8] {
match self.content {
Content::Javascript(_) => b"</script",
Content::Css(_) => b"</style",
Content::Html => unreachable!(),
}
}
pub fn get_js_chunk_for_html<'a>(&self, chunks: &'a mut [Chunk]) -> Option<&'a mut Chunk> {
let entry_point_id = self.entry_point.entry_point_id();
for other in chunks.iter_mut() {
if matches!(other.content, Content::Javascript(_)) {
if other.entry_point.entry_point_id() == entry_point_id {
return Some(other);
}
}
}
None
}
pub fn get_css_chunk_for_html<'a>(&self, chunks: &'a mut [Chunk]) -> Option<&'a mut Chunk> {
let entry_point_id = self.entry_point.entry_point_id();
let css_idx: Option<usize> = 'find: {
for other in chunks.iter() {
if let Content::Javascript(js) = &other.content {
if other.entry_point.entry_point_id() == entry_point_id {
let css_chunk_indices = &js.css_chunks[..];
if !css_chunk_indices.is_empty() {
break 'find Some(css_chunk_indices[0] as usize);
}
break 'find None;
}
}
}
None
};
if let Some(idx) = css_idx {
return Some(&mut chunks[idx]);
}
for other in chunks.iter_mut() {
if matches!(other.content, Content::Css(_)) {
if other.entry_point.entry_point_id() == entry_point_id {
return Some(other);
}
}
}
None
}
#[inline]
pub fn entry_bits(&self) -> &AutoBitSet {
&self.entry_bits
}
}
#[derive(Clone, Copy, Default)]
pub(crate) struct Order {
pub source_index: IndexInt,
pub distance: u32,
pub tie_breaker: u32,
}
impl Order {
pub(crate) fn less_than(_ctx: Order, a: Order, b: Order) -> bool {
(a.distance < b.distance) || (a.distance == b.distance && a.tie_breaker < b.tie_breaker)
}
pub(crate) fn sort(a: &mut [Order]) {
a.sort_unstable_by(|a, b| {
if Order::less_than(Order::default(), *a, *b) {
core::cmp::Ordering::Less
} else if Order::less_than(Order::default(), *b, *a) {
core::cmp::Ordering::Greater
} else {
core::cmp::Ordering::Equal
}
});
}
}
#[derive(Default)]
pub enum IntermediateOutput {
Pieces(OutputPieces),
Joiner(StringJoiner<'static>),
#[default]
Empty,
}
pub struct OutputPieces {
pieces: Vec<OutputPiece>,
_buffer: Box<[u8]>,
}
impl OutputPieces {
#[inline]
pub(crate) fn new(pieces: Vec<OutputPiece>, buffer: Box<[u8]>) -> Self {
OutputPieces {
pieces,
_buffer: buffer,
}
}
#[inline]
pub(crate) fn slice(&self) -> &[OutputPiece] {
&self.pieces
}
#[inline]
pub(crate) fn len(&self) -> usize {
self.pieces.len()
}
}
pub struct CodeResult {
pub buffer: Box<[u8]>,
pub shifts: Vec<source_map::SourceMapShifts>,
}
type DynAlloc = ();
#[inline]
fn alloc_buf(_arena: DynAlloc, n: usize) -> Result<Box<[u8]>, AllocError> {
let mut v: Vec<u8> = Vec::new();
v.try_reserve_exact(n).map_err(|_| AllocError)?;
v.resize(n, 0);
Ok(v.into_boxed_slice())
}
#[inline]
fn additional_output_file_index(f: &AdditionalFile) -> usize {
match *f {
AdditionalFile::OutputFile(i) => i as usize,
AdditionalFile::SourceIndex(_) => {
unreachable!("asset additional_files entry must be .output_file")
}
}
}
impl IntermediateOutput {
pub fn allocator_for_size(_size: usize) -> &'static DynAlloc {
&()
}
fn count_closing_tags(content: &[u8], close_tag: &[u8]) -> usize {
let tag_suffix = &close_tag[2..];
let mut count: usize = 0;
let mut remaining = content;
while let Some(idx) = strings::index_of(remaining, b"</") {
remaining = &remaining[idx + 2..];
if remaining.len() >= tag_suffix.len()
&& strings::eql_case_insensitive_ascii_ignore_length(
&remaining[..tag_suffix.len()],
tag_suffix,
)
{
count += 1;
remaining = &remaining[tag_suffix.len()..];
}
}
count
}
fn memcpy_escaping_closing_tags(dest: &mut [u8], content: &[u8], close_tag: &[u8]) -> usize {
let tag_suffix = &close_tag[2..];
let mut remaining = content;
let mut dst: usize = 0;
while let Some(idx) = strings::index_of(remaining, b"</") {
dest[dst..][..idx].copy_from_slice(&remaining[..idx]);
dst += idx;
remaining = &remaining[idx + 2..];
if remaining.len() >= tag_suffix.len()
&& strings::eql_case_insensitive_ascii_ignore_length(
&remaining[..tag_suffix.len()],
tag_suffix,
)
{
dest[dst] = b'<';
dest[dst + 1] = b'\\';
dest[dst + 2] = b'/';
dst += 3;
} else {
dest[dst] = b'<';
dest[dst + 1] = b'/';
dst += 2;
}
}
dest[dst..][..remaining.len()].copy_from_slice(remaining);
dst += remaining.len();
dst
}
pub fn get_size(&self) -> usize {
match self {
IntermediateOutput::Pieces(pieces) => {
let mut total: usize = 0;
for piece in pieces.slice() {
total += piece.data.len();
}
total
}
IntermediateOutput::Joiner(joiner) => joiner.len,
IntermediateOutput::Empty => 0,
}
}
#[allow(clippy::too_many_arguments)]
pub fn code<'d>(
&mut self,
allocator_to_use: Option<&DynAlloc>,
parse_graph: &Graph,
linker_graph: &LinkerGraph<'_>,
import_prefix: &[u8],
chunk: &Chunk,
chunks: &[Chunk],
display_size: impl Into<Option<&'d mut usize>>,
force_absolute_path: bool,
enable_source_map_shifts: bool,
) -> Result<CodeResult, AllocError> {
let display_size: Option<&mut usize> = display_size.into();
if enable_source_map_shifts {
self.code_with_source_map_shifts::<true>(
allocator_to_use,
parse_graph,
linker_graph,
import_prefix,
chunk,
chunks,
display_size,
force_absolute_path,
None,
)
} else {
self.code_with_source_map_shifts::<false>(
allocator_to_use,
parse_graph,
linker_graph,
import_prefix,
chunk,
chunks,
display_size,
force_absolute_path,
None,
)
}
}
#[allow(clippy::too_many_arguments)]
pub fn code_standalone<'d>(
&mut self,
allocator_to_use: Option<&DynAlloc>,
parse_graph: &Graph,
linker_graph: &LinkerGraph<'_>,
import_prefix: &[u8],
chunk: &Chunk,
chunks: &[Chunk],
display_size: impl Into<Option<&'d mut usize>>,
force_absolute_path: bool,
enable_source_map_shifts: bool,
standalone_chunk_contents: &[Option<Box<[u8]>>],
) -> Result<CodeResult, AllocError> {
let display_size: Option<&mut usize> = display_size.into();
if enable_source_map_shifts {
self.code_with_source_map_shifts::<true>(
allocator_to_use,
parse_graph,
linker_graph,
import_prefix,
chunk,
chunks,
display_size,
force_absolute_path,
Some(standalone_chunk_contents),
)
} else {
self.code_with_source_map_shifts::<false>(
allocator_to_use,
parse_graph,
linker_graph,
import_prefix,
chunk,
chunks,
display_size,
force_absolute_path,
Some(standalone_chunk_contents),
)
}
}
#[allow(clippy::too_many_arguments)]
pub fn code_with_source_map_shifts<const ENABLE_SOURCE_MAP_SHIFTS: bool>(
&mut self,
allocator_to_use: Option<&DynAlloc>,
graph: &Graph,
linker_graph: &LinkerGraph<'_>,
import_prefix: &[u8],
chunk: &Chunk,
chunks: &[Chunk],
display_size: Option<&mut usize>,
force_absolute_path: bool,
standalone_chunk_contents: Option<&[Option<Box<[u8]>>]>,
) -> Result<CodeResult, AllocError> {
let additional_files = graph.input_files.items_additional_files();
let unique_key_for_additional_files =
graph.input_files.items_unique_key_for_additional_file();
let mut relative_platform_buf = bun_paths::path_buffer_pool::get();
let mut file_path_buf = bun_paths::path_buffer_pool::get();
match self {
IntermediateOutput::Pieces(pieces) => {
let entry_point_chunks_for_scb = linker_graph.files.items_entry_point_chunk_index();
let mut shift = source_map::SourceMapShifts {
after: Default::default(),
before: Default::default(),
};
let mut shifts: Vec<source_map::SourceMapShifts> = if ENABLE_SOURCE_MAP_SHIFTS {
Vec::with_capacity(pieces.len() as usize + 1)
} else {
Vec::new()
};
if ENABLE_SOURCE_MAP_SHIFTS {
shifts.push(shift);
}
let mut count: usize = 0;
let mut from_chunk_dir = bun_paths::resolve_path::dirname::<
bun_paths::platform::Posix,
>(&chunk.final_rel_path);
if from_chunk_dir == b"." {
from_chunk_dir = b"";
}
let urls_for_css: &[&[u8]] = if standalone_chunk_contents.is_some() {
graph.ast.items_url_for_css()
} else {
&[]
};
for piece in pieces.slice() {
count += piece.data.len();
match piece.query.kind() {
QueryKind::Chunk
| QueryKind::Asset
| QueryKind::Scb
| QueryKind::HtmlImport => {
let index = piece.query.index() as usize;
if let Some(scc) = standalone_chunk_contents {
match piece.query.kind() {
QueryKind::Chunk => {
if let Some(content) = scc[index].as_deref() {
count += content.len()
+ Self::count_closing_tags(
content,
chunks[index].closing_tag_for_content(),
);
continue;
}
}
QueryKind::Asset => {
if index < urls_for_css.len()
&& !urls_for_css[index].is_empty()
{
count += urls_for_css[index].len();
continue;
}
}
_ => {}
}
}
let file_path: &[u8] = match piece.query.kind() {
QueryKind::Asset => {
let files = &additional_files[index];
if !(files.len() > 0) {
Output::panic(format_args!(
"Internal error: missing asset file"
));
}
let output_file =
additional_output_file_index(files.slice().last().unwrap());
&graph.additional_output_files.as_slice()[output_file].dest_path
}
QueryKind::Chunk => &chunks[index].final_rel_path,
QueryKind::Scb => {
&chunks[entry_point_chunks_for_scb[index] as usize]
.final_rel_path
}
QueryKind::HtmlImport => {
count += bun_core::fmt::count(format_args!(
"{}",
HTMLImportManifest::format_escaped_json(
piece.query.index(),
graph,
chunks,
linker_graph,
)
));
continue;
}
QueryKind::None => unreachable!(),
};
let cheap_normalizer = cheap_prefix_normalizer(
import_prefix,
if from_chunk_dir.is_empty() || force_absolute_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();
}
QueryKind::None => {}
}
}
if let Some(amt) = display_size {
*amt = count;
}
let debug_id_len = if ENABLE_SOURCE_MAP_SHIFTS && FeatureFlags::SOURCE_MAP_DEBUG_ID
{
bun_core::fmt::count(format_args!(
"\n//# debugId={}\n",
source_map::DebugIDFormatter {
id: chunk.isolated_hash
}
))
} else {
0
};
let arena = allocator_to_use.unwrap_or_else(|| Self::allocator_for_size(count));
let mut total_buf = alloc_buf(*arena, count + debug_id_len)?;
let mut remain: &mut [u8] = &mut total_buf;
for piece in pieces.slice() {
let data = piece.data();
if ENABLE_SOURCE_MAP_SHIFTS {
let mut data_offset = source_map::LineColumnOffset::default();
data_offset.advance(data);
shift.before.add(data_offset);
shift.after.add(data_offset);
}
if !data.is_empty() {
remain[..data.len()].copy_from_slice(data);
}
remain = &mut remain[data.len()..];
match piece.query.kind() {
QueryKind::Asset
| QueryKind::Chunk
| QueryKind::Scb
| QueryKind::HtmlImport => {
let index = piece.query.index() as usize;
if let Some(scc) = standalone_chunk_contents {
let inline_content: Option<&[u8]> = match piece.query.kind() {
QueryKind::Chunk => scc[index].as_deref(),
QueryKind::Asset => {
if index < urls_for_css.len()
&& !urls_for_css[index].is_empty()
{
Some(urls_for_css[index])
} else {
None
}
}
_ => None,
};
if let Some(content) = inline_content {
if ENABLE_SOURCE_MAP_SHIFTS {
match piece.query.kind() {
QueryKind::Chunk => {
shift.before.advance(chunks[index].unique_key)
}
QueryKind::Asset => shift
.before
.advance(&unique_key_for_additional_files[index]),
_ => {}
}
shift.after.advance(content);
shifts.push(shift);
}
if piece.query.kind() == QueryKind::Chunk {
let written = Self::memcpy_escaping_closing_tags(
remain,
content,
chunks[index].closing_tag_for_content(),
);
remain = &mut remain[written..];
} else {
remain[..content.len()].copy_from_slice(content);
remain = &mut remain[content.len()..];
}
continue;
}
}
let file_path: &[u8] = match piece.query.kind() {
QueryKind::Asset => 'brk: {
let files = &additional_files[index];
debug_assert!(files.len() > 0);
let output_file =
additional_output_file_index(files.slice().last().unwrap());
if ENABLE_SOURCE_MAP_SHIFTS {
shift
.before
.advance(&unique_key_for_additional_files[index]);
}
break 'brk &graph.additional_output_files.as_slice()
[output_file]
.dest_path;
}
QueryKind::Chunk => 'brk: {
let piece_chunk = &chunks[index];
if ENABLE_SOURCE_MAP_SHIFTS {
shift.before.advance(piece_chunk.unique_key);
}
break 'brk &piece_chunk.final_rel_path;
}
QueryKind::Scb => 'brk: {
let piece_chunk =
&chunks[entry_point_chunks_for_scb[index] as usize];
if ENABLE_SOURCE_MAP_SHIFTS {
shift.before.advance(piece_chunk.unique_key);
}
break 'brk &piece_chunk.final_rel_path;
}
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();
if ENABLE_SOURCE_MAP_SHIFTS {
shift.before.advance(chunk.unique_key);
shift.after.advance(&remain[..written]);
shifts.push(shift);
}
remain = &mut remain[written..];
continue;
}
_ => unreachable!(),
};
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(
import_prefix,
if from_chunk_dir.is_empty() || force_absolute_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()..];
if ENABLE_SOURCE_MAP_SHIFTS {
shift.after.advance(cheap_normalizer[1]);
}
}
if ENABLE_SOURCE_MAP_SHIFTS {
shifts.push(shift);
}
}
QueryKind::None => {}
}
}
if ENABLE_SOURCE_MAP_SHIFTS && FeatureFlags::SOURCE_MAP_DEBUG_ID {
let mut cursor: &mut [u8] = remain;
let before_len = cursor.len();
write!(
&mut cursor,
"\n//# debugId={}\n",
source_map::DebugIDFormatter {
id: chunk.isolated_hash
}
)
.unwrap_or_else(|_| panic!("unexpected NoSpaceLeft error from bufPrint"));
let written = before_len - cursor.len();
remain = &mut remain[written..];
}
debug_assert!(remain.is_empty());
debug_assert!(total_buf.len() == count + debug_id_len);
Ok(CodeResult {
buffer: total_buf,
shifts: if ENABLE_SOURCE_MAP_SHIFTS {
shifts
} else {
Vec::new()
},
})
}
IntermediateOutput::Joiner(joiner) => {
let arena =
allocator_to_use.unwrap_or_else(|| Self::allocator_for_size(joiner.len));
if let Some(amt) = display_size {
*amt = joiner.len;
}
let buffer = 'brk: {
if ENABLE_SOURCE_MAP_SHIFTS && FeatureFlags::SOURCE_MAP_DEBUG_ID {
let mut debug_id_fmt = Vec::new();
write!(
&mut debug_id_fmt,
"\n//# debugId={}\n",
source_map::DebugIDFormatter {
id: chunk.isolated_hash
}
)
.ok();
let _ = arena; break 'brk joiner.done_with_end(&debug_id_fmt)?;
}
let _ = arena;
break 'brk joiner.done()?;
};
Ok(CodeResult {
buffer,
shifts: Vec::new(),
})
}
IntermediateOutput::Empty => Ok(CodeResult {
buffer: Box::default(),
shifts: Vec::new(),
}),
}
}
}
pub(crate) struct OutputPiece {
data: bun_ptr::RawSlice<u8>,
pub query: Query,
}
impl OutputPiece {
pub(crate) fn data(&self) -> &[u8] {
self.data.slice()
}
pub(crate) fn init(data_slice: &[u8], query: Query) -> OutputPiece {
OutputPiece {
data: bun_ptr::RawSlice::new(data_slice),
query,
}
}
}
#[repr(transparent)]
#[derive(Clone, Copy, PartialEq, Eq)]
pub struct Query(u32);
impl Query {
const INDEX_MASK: u32 = (1 << 29) - 1;
pub const NONE: Query = Query(0);
pub fn new(index: u32, kind: QueryKind) -> Query {
debug_assert!(index <= Self::INDEX_MASK);
Query((index & Self::INDEX_MASK) | ((kind as u32) << 29))
}
#[inline]
pub fn index(self) -> u32 {
self.0 & Self::INDEX_MASK
}
#[inline]
pub fn kind(self) -> QueryKind {
match (self.0 >> 29) as u8 {
0 => QueryKind::None,
1 => QueryKind::Asset,
2 => QueryKind::Chunk,
3 => QueryKind::Scb,
4 => QueryKind::HtmlImport,
_ => unreachable!("Query: invalid kind tag"),
}
}
}
#[repr(u8)]
#[derive(Clone, Copy, PartialEq, Eq)]
pub enum QueryKind {
None = 0,
Asset = 1,
Chunk = 2,
Scb = 3,
HtmlImport = 4,
}
impl QueryKind {
#[inline]
pub(crate) const fn letter(self) -> u8 {
match self {
QueryKind::Asset => b'A',
QueryKind::Chunk => b'C',
QueryKind::Scb => b'S',
QueryKind::HtmlImport => b'H',
QueryKind::None => unreachable!(),
}
}
#[inline]
pub(crate) const fn from_letter(b: u8) -> Option<Self> {
match b {
b'A' => Some(QueryKind::Asset),
b'C' => Some(QueryKind::Chunk),
b'S' => Some(QueryKind::Scb),
b'H' => Some(QueryKind::HtmlImport),
_ => None,
}
}
}
pub(crate) const UNIQUE_KEY_PREFIX_LEN: usize = 16;
pub(crate) const UNIQUE_KEY_LEN: usize = UNIQUE_KEY_PREFIX_LEN + 1 + 8;
#[derive(Clone, Copy)]
pub(crate) struct UniqueKey {
pub prefix: u64,
pub kind: QueryKind,
pub index: u32,
}
impl fmt::Display for UniqueKey {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"{}{}{:08}",
bun_core::fmt::hex_int_lower::<16>(self.prefix),
self.kind.letter() as char,
self.index,
)
}
}
#[repr(transparent)]
#[derive(Clone, Copy, Default, PartialEq, Eq)]
pub struct EntryPoint(u64);
pub(crate) type EntryPointId = u32;
impl EntryPoint {
const ENTRY_POINT_ID_MASK: u64 = (1 << 30) - 1;
const IS_ENTRY_POINT_BIT: u64 = 1 << 62;
pub fn entry_point(source_index: u32, entry_point_id: EntryPointId) -> Self {
EntryPoint(Self::non_entry_point(source_index, entry_point_id).0 | Self::IS_ENTRY_POINT_BIT)
}
pub fn non_entry_point(source_index: u32, entry_point_id: EntryPointId) -> Self {
debug_assert!((entry_point_id as u64) <= Self::ENTRY_POINT_ID_MASK);
EntryPoint(
(source_index as u64) | (((entry_point_id as u64) & Self::ENTRY_POINT_ID_MASK) << 32),
)
}
#[inline]
pub fn source_index(self) -> u32 {
self.0 as u32
}
#[inline]
pub fn entry_point_id(self) -> u32 {
((self.0 >> 32) & Self::ENTRY_POINT_ID_MASK) as u32
}
#[inline]
pub fn is_entry_point(self) -> bool {
self.0 & Self::IS_ENTRY_POINT_BIT != 0
}
#[inline]
pub fn set_source_index(&mut self, v: u32) {
self.0 = (self.0 & !0xFFFF_FFFF) | (v as u64);
}
#[inline]
pub fn set_entry_point_id(&mut self, v: EntryPointId) {
debug_assert!((v as u64) <= Self::ENTRY_POINT_ID_MASK);
self.0 = (self.0 & !(Self::ENTRY_POINT_ID_MASK << 32))
| (((v as u64) & Self::ENTRY_POINT_ID_MASK) << 32);
}
#[inline]
pub fn set_is_entry_point(&mut self, v: bool) {
self.0 = (self.0 & !Self::IS_ENTRY_POINT_BIT) | ((v as u64) << 62);
}
}
#[derive(Default)]
pub struct JavaScriptChunk {
pub files_in_chunk_order: Box<[IndexInt]>,
pub parts_in_chunk_in_order: Box<[PartRange]>,
pub exports_to_other_chunks: ArrayHashMap<Ref, &'static [u8]>,
pub imports_from_other_chunks: ImportsFromOtherChunks,
pub cross_chunk_prefix_stmts: Vec<Stmt>,
pub cross_chunk_suffix_stmts: Vec<Stmt>,
pub css_chunks: Box<[u32]>,
pub module_info_bytes: Option<Box<[u8]>>,
pub module_info: Option<Box<analyze_transpiled_module::ModuleInfo>>,
}
pub struct CssChunk {
pub imports_in_chunk_in_order: Vec<CssImportOrder>,
pub asts: Box<[bun_css::BundlerStyleSheet]>,
}
impl Drop for CssChunk {
fn drop(&mut self) {
let mut asts = core::mem::take(&mut self.asts).into_vec();
unsafe { asts.set_len(0) };
}
}
pub type CssImportKind = CssImportOrderKind;
pub struct CssImportOrder {
pub conditions: Vec<bun_css::ImportConditions>,
pub condition_import_records: Vec<ImportRecord>,
pub kind: CssImportOrderKind,
}
impl Drop for CssImportOrder {
fn drop(&mut self) {
let _ = core::mem::ManuallyDrop::new(core::mem::take(&mut self.conditions));
}
}
#[derive(strum::IntoStaticStr)]
pub enum CssImportOrderKind {
#[strum(serialize = "layers")]
Layers(Layers),
#[strum(serialize = "external_path")]
ExternalPath(bun_fs::Path<'static>),
#[strum(serialize = "source_index")]
SourceIndex(Index),
}
pub enum Layers {
Borrowed(bun_ptr::BackRef<Vec<bun_css::LayerName>>),
Owned(Vec<bun_css::LayerName>),
}
impl Layers {
#[inline]
pub(crate) fn inner(&self) -> &Vec<bun_css::LayerName> {
match self {
Layers::Borrowed(p) => p.get(),
Layers::Owned(b) => b,
}
}
#[inline]
pub(crate) fn borrow(p: core::ptr::NonNull<Vec<bun_css::LayerName>>) -> Self {
Layers::Borrowed(bun_ptr::BackRef::from(p))
}
#[inline]
pub(crate) fn replace(&mut self, new: Vec<bun_css::LayerName>) {
*self = Layers::Owned(new);
}
pub(crate) fn to_owned(&mut self) -> &mut Vec<bun_css::LayerName> {
if let Layers::Borrowed(p) = *self {
*self = Layers::Owned(p.deep_clone_with(|l| l.clone()));
}
match self {
Layers::Owned(b) => b,
Layers::Borrowed(_) => unreachable!(),
}
}
}
impl CssImportOrder {
pub(crate) fn hash<H: bun_core::Hasher + ?Sized>(&self, hasher: &mut H) {
let tag: u8 = match &self.kind {
CssImportOrderKind::Layers(_) => 0,
CssImportOrderKind::ExternalPath(_) => 1,
CssImportOrderKind::SourceIndex(_) => 2,
};
bun_core::write_any_to_hasher(hasher, tag);
match &self.kind {
CssImportOrderKind::Layers(layers) => {
for layer in layers.inner().slice() {
for (i, layer_name) in layer.v.slice().iter().enumerate() {
let is_last = i == layers.inner().len() as usize - 1;
if is_last {
hasher.update(layer_name);
} else {
hasher.update(layer_name);
hasher.update(b".");
}
}
}
hasher.update(b"\x00");
}
CssImportOrderKind::ExternalPath(path) => hasher.update(path.text),
CssImportOrderKind::SourceIndex(idx) => {
bun_core::write_any_to_hasher(hasher, idx.get())
}
}
}
#[allow(dead_code)]
pub(crate) fn fmt<'a, 'ctx>(
&'a self,
ctx: &'a LinkerContext<'ctx>,
) -> CssImportOrderDebug<'a, 'ctx> {
CssImportOrderDebug { inner: self, ctx }
}
}
#[allow(dead_code)]
pub(crate) struct CssImportOrderDebug<'a, 'ctx> {
inner: &'a CssImportOrder,
ctx: &'a LinkerContext<'ctx>,
}
impl<'a, 'ctx> fmt::Display for CssImportOrderDebug<'a, 'ctx> {
fn fmt(&self, writer: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(writer, "{} = ", <&'static str>::from(&self.inner.kind))?;
match &self.inner.kind {
CssImportOrderKind::Layers(layers) => {
write!(writer, "[")?;
let l = layers.inner();
for (i, layer) in l.slice_const().iter().enumerate() {
if i > 0 {
write!(writer, ", ")?;
}
write!(writer, "\"{}\"", layer)?;
}
write!(writer, "]")?;
}
CssImportOrderKind::ExternalPath(path) => {
write!(writer, "\"{}\"", bstr::BStr::new(&path.pretty))?;
}
CssImportOrderKind::SourceIndex(source_index) => {
let source =
&self.ctx.parse_graph().input_files.items_source()[source_index.get() as usize];
write!(
writer,
"{} ({})",
source_index.get(),
bstr::BStr::new(&source.path.text)
)?;
}
}
Ok(())
}
}
pub(crate) type ImportsFromOtherChunks = ArrayHashMap<IndexInt, cross_chunk_import::ItemList>;
#[derive(Default, Clone)]
pub struct CrossChunkImportItem {
pub export_alias: Box<[u8]>,
pub r#ref: Ref,
}
pub type CrossChunkImportItemList = Vec<CrossChunkImportItem>;
#[derive(Default)]
pub struct CrossChunkImport {
pub chunk_index: IndexInt,
pub sorted_import_items: core::mem::ManuallyDrop<CrossChunkImportItemList>,
}
pub mod cross_chunk_import {
pub(crate) type ItemList = super::CrossChunkImportItemList;
}
impl CrossChunkImportItem {
pub fn less_than(_: (), a: &CrossChunkImportItem, b: &CrossChunkImportItem) -> bool {
strings::order(&a.export_alias, &b.export_alias) == core::cmp::Ordering::Less
}
}
impl CrossChunkImport {
pub fn less_than(_: (), a: &CrossChunkImport, b: &CrossChunkImport) -> bool {
a.chunk_index < b.chunk_index
}
pub fn sorted_cross_chunk_imports(
list: &mut Vec<CrossChunkImport>,
chunks: &mut [Chunk],
imports_from_other_chunks: &mut ImportsFromOtherChunks,
) -> Result<(), bun_core::Error> {
list.clear();
list.reserve(imports_from_other_chunks.count());
for i in 0..imports_from_other_chunks.count() {
let chunk_index = imports_from_other_chunks.keys()[i];
let chunk = &mut chunks[chunk_index as usize];
let exports_to_other_chunks = &chunk.content.javascript().exports_to_other_chunks;
let import_items = &mut imports_from_other_chunks.values_mut()[i];
for item in import_items.slice_mut() {
item.export_alias = (*exports_to_other_chunks.get(&item.r#ref).unwrap()).into();
debug_assert!(!item.export_alias.is_empty());
}
import_items
.slice_mut()
.sort_by(|a, b| strings::order(&a.export_alias, &b.export_alias));
list.push(CrossChunkImport {
chunk_index,
sorted_import_items: import_items.shallow_copy(),
});
}
list.sort_by_key(|a| a.chunk_index);
Ok(())
}
}
#[allow(clippy::large_enum_variant)]
pub enum Content {
Javascript(JavaScriptChunk),
Css(CssChunk),
Html,
}
impl Content {
#[inline]
pub fn is_javascript(&self) -> bool {
matches!(self, Content::Javascript(_))
}
#[inline]
pub fn is_css(&self) -> bool {
matches!(self, Content::Css(_))
}
#[inline]
pub fn is_html(&self) -> bool {
matches!(self, Content::Html)
}
bun_core::enum_unwrap!(pub Content, Javascript => fn javascript / javascript_mut -> JavaScriptChunk);
bun_core::enum_unwrap!(pub Content, Css => fn css / css_mut -> CssChunk);
pub fn sourcemap(&self, default: options::SourceMapOption) -> options::SourceMapOption {
match self {
Content::Javascript(_) => default,
Content::Css(_) => options::SourceMapOption::None, Content::Html => options::SourceMapOption::None,
}
}
pub fn loader(&self) -> Loader {
match self {
Content::Javascript(_) => Loader::Js,
Content::Css(_) => Loader::Css,
Content::Html => Loader::Html,
}
}
pub fn ext(&self) -> &'static [u8] {
match self {
Content::Javascript(_) => b"js",
Content::Css(_) => b"css",
Content::Html => b"html",
}
}
}
pub use crate::DeferredBatchTask::DeferredBatchTask;
pub use crate::ParseTask;
pub use crate::ThreadPool;
pub mod bun_renamer {
pub use bun_js_printer::renamer::*;
#[derive(Default)]
pub enum ChunkRenamer {
#[default]
None,
Number(Box<bun_js_printer::renamer::NumberRenamer>),
Minify(Box<bun_js_printer::renamer::MinifyRenamer>),
}
impl ChunkRenamer {
pub(crate) fn name_for_symbol(&mut self, ref_: bun_ast::Ref) -> &[u8] {
match self {
ChunkRenamer::None => unreachable!("ChunkRenamer not initialized"),
ChunkRenamer::Number(r) => r.name_for_symbol(ref_),
ChunkRenamer::Minify(r) => r.name_for_symbol(ref_),
}
}
pub(crate) fn as_renamer(&mut self) -> bun_js_printer::renamer::Renamer<'_, '_> {
match self {
ChunkRenamer::None => unreachable!("ChunkRenamer not initialized"),
ChunkRenamer::Number(r) => bun_js_printer::renamer::Renamer::NumberRenamer(r),
ChunkRenamer::Minify(r) => bun_js_printer::renamer::Renamer::MinifyRenamer(r),
}
}
}
}