use core::ffi::c_void;
use core::mem::offset_of;
use core::sync::atomic::{AtomicU32, Ordering};
use bun_alloc::Arena as Bump; use bun_ast::ImportRecord;
use bun_ast::{Loc, Location, Log, Msg, Source};
use bun_collections::VecExt;
use bun_core::strings;
use bun_core::{self, Error as AnyError, FeatureFlags, declare_scope, err, scoped_log};
use bun_sys::Fd;
use bun_threading::thread_pool as ThreadPoolLib;
use bun_ast::Index;
use bun_ast::{self as ast, E, Expr, G, Part};
use bun_js_parser as js_parser;
use crate::JSAst;
pub use bun_js_parser::parser::ParserOptions;
use crate::bun_css;
use crate::bun_fs as Fs;
use crate::bun_node_fallbacks as NodeFallbackModules;
use crate::bundle_v2::{self as bundler, BundleV2};
use crate::cache::{Entry as CacheEntry, ExternalFreeFunction};
use crate::html_scanner::HTMLScanner;
use crate::options::{self, Loader};
use crate::transpiler::Transpiler;
use crate::{ContentHasher, UseDirective, perf, target_from_hashbang};
use bun_resolver::fs::PathResolverExt as _;
use bun_resolver::{self as _resolver, Resolver};
declare_scope!(ParseTask, hidden);
#[allow(non_snake_case)]
mod EventLoop {
pub(super) type Task = bun_event_loop::ConcurrentTask::ConcurrentTask;
}
#[derive(bun_core::EnumTag)]
#[enum_tag(existing = ContentsOrFdTag)]
pub enum ContentsOrFd {
Fd { dir: Fd, file: Fd },
Contents(&'static [u8]),
}
#[derive(Copy, Clone, Eq, PartialEq, strum::IntoStaticStr)]
pub(crate) enum ContentsOrFdTag {
Fd,
Contents,
}
pub struct ParseTask {
pub path: Fs::Path<'static>,
pub secondary_path_for_commonjs_interop: Option<Fs::Path<'static>>,
pub contents_or_fd: ContentsOrFd,
pub external_free_function: ExternalFreeFunction,
pub side_effects: bun_ast::SideEffects,
pub loader: Option<Loader>,
pub jsx: options::jsx::Pragma,
pub source_index: Index,
pub task: ThreadPoolLib::Task,
pub io_task: ThreadPoolLib::Task,
pub stage: ParseTaskStage,
pub tree_shaking: bool,
pub known_target: options::Target,
pub module_type: options::ModuleType,
pub emit_decorator_metadata: bool,
pub experimental_decorators: bool,
pub ctx: Option<bun_ptr::ParentRef<BundleV2<'static>>>,
pub package_version: ast::StoreStr,
pub package_name: ast::StoreStr,
pub is_entry_point: bool,
}
pub enum ParseTaskStage {
NeedsSourceCode,
NeedsParse(CacheEntry),
}
pub struct Result {
pub task: EventLoop::Task,
pub ctx: bun_ptr::ParentRef<BundleV2<'static>>,
pub value: ResultValue,
pub watcher_data: WatcherData,
pub external: ExternalFreeFunction,
}
#[allow(clippy::large_enum_variant)]
pub enum ResultValue {
Success(Success),
Err(ResultError),
Empty { source_index: Index },
}
pub struct WatcherData {
pub fd: Fd,
pub dir_fd: Fd,
}
impl WatcherData {
pub(crate) const NONE: WatcherData = WatcherData {
fd: Fd::INVALID,
dir_fd: Fd::INVALID,
};
}
pub struct Success {
pub ast: JSAst<'static>,
pub source: Source,
pub log: Log,
pub use_directive: UseDirective,
pub side_effects: bun_ast::SideEffects,
pub unique_key_for_additional_file: ast::StoreStr,
pub content_hash_for_additional_file: u64,
pub loader: Loader,
pub package_name: ast::StoreStr,
}
pub struct ResultError {
pub err: AnyError,
pub step: Step,
pub log: Log,
pub target: options::Target,
pub source_index: Index,
}
#[derive(Copy, Clone, Eq, PartialEq)]
pub enum Step {
Pending,
ReadFile,
Parse,
Resolve,
}
impl ParseTask {
#[inline]
pub unsafe fn ctx<'r>(&self) -> &'r BundleV2<'static> {
unsafe { bun_ptr::detach_lifetime_ref(self.ctx.expect("ParseTask.ctx unset").get()) }
}
pub fn init(
resolve_result: &_resolver::Result,
source_index: Index,
ctx: *mut BundleV2<'_>,
) -> ParseTask {
let (package_name, package_version) = match resolve_result.package_json {
Some(pj) => unsafe {
let pj = &*pj;
(
ast::StoreStr::new(&pj.name[..]),
ast::StoreStr::new(&pj.version[..]),
)
},
None => (ast::StoreStr::EMPTY, ast::StoreStr::EMPTY),
};
let ctx_ref = unsafe { bun_ptr::ParentRef::from_raw_mut(ctx.cast::<BundleV2<'static>>()) };
let known_target = ctx_ref.get().transpiler().options.target;
ParseTask {
ctx: Some(ctx_ref),
path: resolve_result.path_pair.primary,
contents_or_fd: ContentsOrFd::Fd {
dir: resolve_result.dirname_fd,
file: resolve_result.file_fd,
},
side_effects: resolve_result.primary_side_effects_data,
jsx: resolve_result.jsx.clone(),
source_index,
module_type: resolve_result.module_type,
emit_decorator_metadata: resolve_result.flags.emit_decorator_metadata(),
experimental_decorators: resolve_result.flags.experimental_decorators(),
package_version,
package_name,
known_target,
secondary_path_for_commonjs_interop: None,
external_free_function: ExternalFreeFunction::NONE,
loader: None,
task: ThreadPoolLib::Task {
node: ThreadPoolLib::Node::default(),
callback: task_callback,
},
io_task: ThreadPoolLib::Task {
node: ThreadPoolLib::Node::default(),
callback: io_task_callback,
},
stage: ParseTaskStage::NeedsSourceCode,
tree_shaking: false,
is_entry_point: false,
}
}
#[inline]
pub fn get_runtime_source(target: options::Target) -> RuntimeSource {
parse_worker::get_runtime_source(target)
}
}
impl Default for ParseTask {
fn default() -> Self {
ParseTask {
ctx: None,
path: Fs::Path::init(b""),
secondary_path_for_commonjs_interop: None,
contents_or_fd: ContentsOrFd::Contents(b""),
external_free_function: ExternalFreeFunction::NONE,
side_effects: bun_ast::SideEffects::HasSideEffects,
loader: None,
jsx: Default::default(),
source_index: Index::INVALID,
task: ThreadPoolLib::Task {
node: ThreadPoolLib::Node::default(),
callback: task_callback,
},
io_task: ThreadPoolLib::Task {
node: ThreadPoolLib::Node::default(),
callback: io_task_callback,
},
stage: ParseTaskStage::NeedsSourceCode,
tree_shaking: false,
known_target: options::Target::default(),
module_type: options::ModuleType::Unknown,
emit_decorator_metadata: false,
experimental_decorators: false,
package_version: ast::StoreStr::EMPTY,
package_name: ast::StoreStr::EMPTY,
is_entry_point: false,
}
}
}
pub(crate) unsafe fn io_task_callback(task: *mut ThreadPoolLib::Task) {
let parse_task = unsafe { &mut *bun_core::from_field_ptr!(ParseTask, io_task, task) };
parse_worker::run_from_thread_pool(parse_task);
}
pub(crate) unsafe fn task_callback(task: *mut ThreadPoolLib::Task) {
let parse_task = unsafe { &mut *bun_core::from_field_ptr!(ParseTask, task, task) };
parse_worker::run_from_thread_pool(parse_task);
}
pub struct RuntimeSource {
pub parse_task: ParseTask,
pub source: Source,
}
const RUNTIME_REQUIRE_BUN: &str = "export var __require = import.meta.require;";
const RUNTIME_REQUIRE_NODE: &str = "\
import { createRequire } from \"node:module\";
export var __require = /* @__PURE__ */ createRequire(import.meta.url);
";
const RUNTIME_REQUIRE_OTHER: &str = "\
export var __require = /* @__PURE__ */ (x =>
typeof require !== 'undefined' ? require :
typeof Proxy !== 'undefined' ? new Proxy(x, {
get: (a, b) => (typeof require !== 'undefined' ? require : a)[b]
}) : x
)(function (x) {
if (typeof require !== 'undefined') return require.apply(this, arguments)
throw Error('Dynamic require of \"' + x + '\" is not supported')
});
";
const RUNTIME_USING_BUN: &str = "\
export var __using = (stack, value, async) => {
if (value != null) {
if (typeof value !== 'object' && typeof value !== 'function') throw TypeError('Object expected to be assigned to \"using\" declaration')
let dispose
if (async) dispose = value[Symbol.asyncDispose]
if (dispose === void 0) dispose = value[Symbol.dispose]
if (typeof dispose !== 'function') throw TypeError('Object not disposable')
stack.push([async, dispose, value])
} else if (async) {
stack.push([async])
}
return value
}
export var __callDispose = (stack, error, hasError) => {
let fail = e => error = hasError ? new SuppressedError(e, error, 'An error was suppressed during disposal') : (hasError = true, e)
, next = (it) => {
while (it = stack.pop()) {
try {
var result = it[1] && it[1].call(it[2])
if (it[0]) return Promise.resolve(result).then(next, (e) => (fail(e), next()))
} catch (e) {
fail(e)
}
}
if (hasError) throw error
}
return next()
}
";
const RUNTIME_USING_OTHER: &str = "\
var __dispose = Symbol.dispose || /* @__PURE__ */ Symbol.for('Symbol.dispose');
var __asyncDispose = Symbol.asyncDispose || /* @__PURE__ */ Symbol.for('Symbol.asyncDispose');
export var __using = (stack, value, async) => {
if (value != null) {
if (typeof value !== 'object' && typeof value !== 'function') throw TypeError('Object expected to be assigned to \"using\" declaration')
var dispose
if (async) dispose = value[__asyncDispose]
if (dispose === void 0) dispose = value[__dispose]
if (typeof dispose !== 'function') throw TypeError('Object not disposable')
stack.push([async, dispose, value])
} else if (async) {
stack.push([async])
}
return value
}
export var __callDispose = (stack, error, hasError) => {
var E = typeof SuppressedError === 'function' ? SuppressedError :
function (e, s, m, _) { return _ = Error(m), _.name = 'SuppressedError', _.error = e, _.suppressed = s, _ },
fail = e => error = hasError ? new E(e, error, 'An error was suppressed during disposal') : (hasError = true, e),
next = (it) => {
while (it = stack.pop()) {
try {
var result = it[1] && it[1].call(it[2])
if (it[0]) return Promise.resolve(result).then(next, (e) => (fail(e), next()))
} catch (e) {
fail(e)
}
}
if (hasError) throw error
}
return next()
}
";
pub mod parse_worker {
use super::*;
fn get_runtime_source_comptime(target: options::Target) -> RuntimeSource {
use const_format::concatcp;
let runtime_code: &'static str = match target {
options::Target::Bun => {
concatcp!(
include_str!("runtime.js"),
RUNTIME_REQUIRE_BUN,
RUNTIME_USING_BUN
)
}
options::Target::BunMacro => {
concatcp!(
include_str!("runtime.js"),
RUNTIME_REQUIRE_BUN,
RUNTIME_USING_OTHER
)
}
options::Target::Node => {
concatcp!(
include_str!("runtime.js"),
RUNTIME_REQUIRE_NODE,
RUNTIME_USING_OTHER
)
}
_ => {
concatcp!(
include_str!("runtime.js"),
RUNTIME_REQUIRE_OTHER,
RUNTIME_USING_OTHER
)
}
};
let parse_task = ParseTask {
ctx: None,
path: Fs::Path::init_with_namespace(b"runtime", b"bun:runtime"),
side_effects: bun_ast::SideEffects::NoSideEffectsPureData,
jsx: options::jsx::Pragma {
parse: false,
..Default::default()
},
contents_or_fd: ContentsOrFd::Contents(runtime_code.as_bytes()),
source_index: Index::RUNTIME,
loader: Some(Loader::Js),
known_target: target,
secondary_path_for_commonjs_interop: None,
external_free_function: ExternalFreeFunction::NONE,
task: ThreadPoolLib::Task {
node: ThreadPoolLib::Node::default(),
callback: task_callback,
},
io_task: ThreadPoolLib::Task {
node: ThreadPoolLib::Node::default(),
callback: io_task_callback,
},
stage: ParseTaskStage::NeedsSourceCode,
tree_shaking: false,
module_type: options::ModuleType::Unknown,
emit_decorator_metadata: false,
experimental_decorators: false,
package_version: ast::StoreStr::EMPTY,
package_name: ast::StoreStr::EMPTY,
is_entry_point: false,
};
let source = Source {
path: bun_paths::fs::Path {
text: b"runtime",
namespace: b"bun:runtime",
pretty: b"",
is_disabled: false,
is_symlink: false,
},
contents: std::borrow::Cow::Borrowed(runtime_code.as_bytes()),
index: bun_ast::Index(Index::RUNTIME.get()),
..Default::default()
};
RuntimeSource { parse_task, source }
}
pub fn get_runtime_source(target: options::Target) -> RuntimeSource {
get_runtime_source_comptime(target)
}
fn get_empty_css_ast(
log: &mut Log,
transpiler: *mut Transpiler,
opts: ParserOptions<'static>,
bump: &'static Bump,
source: &'static Source,
) -> core::result::Result<JSAst<'static>, AnyError> {
let root = Expr::init(E::Object::default(), Loc { start: 0 });
let define = unsafe { &mut (*transpiler).options.define };
let mut ast = JSAst::init(
js_parser::new_lazy_export_ast(bump, define, opts, log, root, source, b"")?.ok_or_else(|| err!("ParserError"))?,
);
ast.css = Some(crate::bundled_ast::CssAstRef::from_bump(
bump.alloc(bun_css::BundlerStyleSheet::empty()),
));
Ok(ast)
}
fn get_empty_ast<RootType: Default + bun_ast::expr::IntoExprData>(
log: &mut Log,
transpiler: *mut Transpiler,
opts: ParserOptions<'static>,
bump: &'static Bump,
source: &'static Source,
) -> core::result::Result<JSAst<'static>, AnyError> {
let root = Expr::init(RootType::default(), Loc::EMPTY);
let define = unsafe { &mut (*transpiler).options.define };
Ok(JSAst::init(
js_parser::new_lazy_export_ast(bump, define, opts, log, root, source, b"")?.ok_or_else(|| err!("ParserError"))?,
))
}
pub struct FileLoaderHash {
pub key: ast::StoreStr,
pub content_hash: u64,
}
fn css_symbols_to_parser_symbols(
src: &[bun_ast::Symbol],
bump: &'static Bump,
) -> bun_ast::symbol::List<'static> {
use bun_ast::symbol::{Kind as PKind, Symbol as PSym};
let mut out = bun_ast::symbol::List::with_capacity_in(src.len(), bump);
for s in src {
let kind: PKind = s.kind;
let import_item_status: bun_ast::ImportItemStatus = s.import_item_status;
let link: bun_ast::Ref = s.link.get();
out.push(PSym {
original_name: bun_ast::StoreStr::new(s.original_name.slice()),
namespace_alias: None,
link: std::cell::Cell::new(link),
use_count_estimate: s.use_count_estimate,
chunk_index: core::sync::atomic::AtomicU32::new(
s.chunk_index.load(core::sync::atomic::Ordering::Relaxed),
),
nested_scope_slot: s.nested_scope_slot,
did_keep_name: s.did_keep_name,
must_start_with_capital_letter_for_jsx: s.must_start_with_capital_letter_for_jsx,
kind,
must_not_be_renamed: s.must_not_be_renamed,
import_item_status,
private_symbol_must_be_lowered: s.private_symbol_must_be_lowered,
remove_overwritten_function_declaration: s.remove_overwritten_function_declaration,
has_been_assigned_to: s.has_been_assigned_to,
});
}
out
}
#[allow(clippy::too_many_arguments)]
fn get_ast(
log: &mut Log,
transpiler: *mut Transpiler,
opts: ParserOptions<'static>,
bump: &'static Bump,
resolver: *mut Resolver,
source: &'static Source,
loader: Loader,
unique_key_prefix: u64,
unique_key_for_additional_file: &mut FileLoaderHash,
has_any_css_locals: &AtomicU32,
) -> core::result::Result<JSAst<'static>, AnyError> {
use core::fmt::Write as _;
let topts = unsafe { &mut (*transpiler).options };
match loader {
Loader::Jsx | Loader::Tsx | Loader::Js | Loader::Ts => {
let _trace = perf::trace("Bundler.ParseJS");
let fallback_opts = opts.clone_for_lazy_export();
let module_type = opts.module_type;
return if let Some(res) = (crate::cache::JavaScript {}).parse(
bump, opts,
&topts.define,
log,
source,
)? {
match res {
bun_js_parser::Result::Ast(ast) => Ok(JSAst::init(*ast)),
bun_js_parser::Result::Cached
| bun_js_parser::Result::AlreadyBundled(_) => {
unreachable!("bundler parse never yields Cached/AlreadyBundled")
}
}
} else if module_type == options::ModuleType::Esm {
get_empty_ast::<E::Undefined>(log, transpiler, fallback_opts, bump, source)
} else {
get_empty_ast::<E::Object>(log, transpiler, fallback_opts, bump, source)
};
}
Loader::Json | Loader::Jsonc => {
let _trace = perf::trace("Bundler.ParseJSON");
let mode = if matches!(loader, Loader::Jsonc) {
bun_resolver::tsconfig_json::JsonMode::Jsonc
} else {
bun_resolver::tsconfig_json::JsonMode::Json
};
let root: Expr = unsafe { &mut (*resolver).caches.json }
.parse_json(log, source, mode, true)?
.unwrap_or_else(|| Expr::init(E::Object::default(), Loc::EMPTY));
return Ok(JSAst::init(
js_parser::new_lazy_export_ast(
bump,
&mut topts.define,
opts,
log,
root,
source,
b"",
)?
.ok_or_else(|| err!("ParserError"))?,
));
}
Loader::Toml => {
let _trace = perf::trace("Bundler.ParseTOML");
let mut temp_log = Log::init();
let result = (|| -> core::result::Result<JSAst<'static>, AnyError> {
let root: Expr =
bun_parsers::toml::TOML::parse(source, &mut temp_log, bump, false)?;
Ok(JSAst::init(
js_parser::new_lazy_export_ast(
bump,
&mut topts.define,
opts,
&mut temp_log,
root,
source,
b"",
)?
.ok_or_else(|| err!("ParserError"))?,
))
})();
let _ = temp_log.clone_to_with_recycled(log, true);
return result;
}
Loader::Yaml => {
let _trace = perf::trace("Bundler.ParseYAML");
let mut temp_log = Log::init();
let result = (|| -> core::result::Result<JSAst<'static>, AnyError> {
let root: Expr = bun_parsers::yaml::YAML::parse(source, &mut temp_log, bump)?;
Ok(JSAst::init(
js_parser::new_lazy_export_ast(
bump,
&mut topts.define,
opts,
&mut temp_log,
root,
source,
b"",
)?
.ok_or_else(|| err!("ParserError"))?,
))
})();
let _ = temp_log.clone_to_with_recycled(log, true);
return result;
}
Loader::Json5 => {
let _trace = perf::trace("Bundler.ParseJSON5");
let mut temp_log = Log::init();
let result = (|| -> core::result::Result<JSAst<'static>, AnyError> {
let root: Expr =
bun_parsers::json5::JSON5Parser::parse(source, &mut temp_log, bump)?;
Ok(JSAst::init(
js_parser::new_lazy_export_ast(
bump,
&mut topts.define,
opts,
&mut temp_log,
root,
source,
b"",
)?
.ok_or_else(|| err!("ParserError"))?,
))
})();
let _ = temp_log.clone_to_with_recycled(log, true);
return result;
}
Loader::Text => {
let root = Expr::init(
E::String {
data: source.contents().into(),
..Default::default()
},
Loc { start: 0 },
);
let mut ast = JSAst::init(
js_parser::new_lazy_export_ast(
bump,
&mut topts.define,
opts,
log,
root,
source,
b"",
)?
.ok_or_else(|| err!("ParserError"))?,
);
ast.add_url_for_css(
bump,
source,
Some(b"text/plain"),
None,
topts.compile_to_standalone_html,
);
return Ok(ast);
}
Loader::Md => {
let html = match bun_md::root::render_to_html(&source.contents) {
Ok(h) => h,
Err(_) => {
let _ = log.add_error(
Some(source),
Loc::EMPTY,
b"Failed to render markdown to HTML",
); return Err(err!("ParserError"));
}
};
let html: &[u8] = bump.alloc_slice_copy(&html);
let root = Expr::init(
E::String {
data: html.into(),
..Default::default()
},
Loc { start: 0 },
);
let mut ast = JSAst::init(
js_parser::new_lazy_export_ast(
bump,
&mut topts.define,
opts,
log,
root,
source,
b"",
)?
.ok_or_else(|| err!("ParserError"))?,
);
ast.add_url_for_css(
bump,
source,
Some(b"text/html"),
None,
topts.compile_to_standalone_html,
);
return Ok(ast);
}
Loader::SqliteEmbedded | Loader::Sqlite => {
if !topts.target.is_bun() {
let _ = log.add_error(
Some(source),
Loc::EMPTY,
b"To use the \"sqlite\" loader, set target to \"bun\"",
);
return Err(err!("ParserError"));
}
let path_to_use: &[u8] = 'brk: {
if loader == Loader::SqliteEmbedded {
let mut buf = bun_alloc::ArenaString::new_in(bump);
write!(
&mut buf,
"{}",
crate::chunk::UniqueKey {
prefix: unique_key_prefix,
kind: crate::chunk::QueryKind::Asset,
index: source.index.0,
},
)
.expect("unreachable");
let embedded_path = buf.into_bump_str().as_bytes();
*unique_key_for_additional_file = FileLoaderHash {
key: ast::StoreStr::new(embedded_path),
content_hash: ContentHasher::run(&source.contents),
};
break 'brk embedded_path;
}
break 'brk source.path.text;
};
let import_path = Expr::init(
E::String {
data: path_to_use.into(),
..Default::default()
},
Loc { start: 0 },
);
let import_meta = Expr::init(E::ImportMeta {}, Loc { start: 0 });
let require_property = Expr::init(
E::Dot {
target: import_meta,
name_loc: Loc::EMPTY,
name: b"require".into(),
..Default::default()
},
Loc { start: 0 },
);
let require_args = bump.alloc_slice_fill_default::<Expr>(2);
require_args[0] = import_path;
let object_properties = bump.alloc_slice_fill_default::<G::Property>(1);
object_properties[0] = G::Property {
key: Some(Expr::init(
E::String {
data: b"type".into(),
..Default::default()
},
Loc { start: 0 },
)),
value: Some(Expr::init(
E::String {
data: b"sqlite".into(),
..Default::default()
},
Loc { start: 0 },
)),
..Default::default()
};
require_args[1] = Expr::init(
E::Object {
properties: unsafe { G::PropertyList::from_bump_slice(object_properties) },
is_single_line: true,
..Default::default()
},
Loc { start: 0 },
);
let require_call = Expr::init(
E::Call {
target: require_property,
args: unsafe { bun_ast::ExprNodeList::from_bump_slice(require_args) },
..Default::default()
},
Loc { start: 0 },
);
let root = Expr::init(
E::Dot {
target: require_call,
name_loc: Loc::EMPTY,
name: b"db".into(),
..Default::default()
},
Loc { start: 0 },
);
return Ok(JSAst::init(
js_parser::new_lazy_export_ast(
bump,
&mut topts.define,
opts,
log,
root,
source,
b"",
)?
.ok_or_else(|| err!("ParserError"))?,
));
}
Loader::Napi => {
if topts.target == options::Target::Browser {
let _ = log.add_error(
Some(source),
Loc::EMPTY,
b"Loading .node files won't work in the browser. Make sure to set target to \"bun\" or \"node\"",
);
return Err(err!("ParserError"));
}
let mut buf = bun_alloc::ArenaString::new_in(bump);
write!(
&mut buf,
"{}",
crate::chunk::UniqueKey {
prefix: unique_key_prefix,
kind: crate::chunk::QueryKind::Asset,
index: source.index.0,
},
)
.expect("unreachable");
let unique_key = buf.into_bump_str().as_bytes();
let import_path = Expr::init(
E::String {
data: unique_key.into(),
..Default::default()
},
Loc { start: 0 },
);
let require_args = bump.alloc_slice_fill_default::<Expr>(1);
require_args[0] = import_path;
let root = Expr::init(
E::Call {
target: Expr {
data: ast::ExprData::ERequireCallTarget,
loc: Loc { start: 0 },
},
args: unsafe { bun_ast::ExprNodeList::from_bump_slice(require_args) },
..Default::default()
},
Loc { start: 0 },
);
*unique_key_for_additional_file = FileLoaderHash {
key: ast::StoreStr::new(unique_key),
content_hash: ContentHasher::run(&source.contents),
};
return Ok(JSAst::init(
js_parser::new_lazy_export_ast(
bump,
&mut topts.define,
opts,
log,
root,
source,
b"",
)?
.ok_or_else(|| err!("ParserError"))?,
));
}
Loader::Html => {
let import_records = {
let mut scanner = HTMLScanner::init(log, source);
scanner.scan(&source.contents)?;
scanner.import_records
};
let import_records_len = import_records.len();
let output_format = opts.output_format;
let mut ast = js_parser::new_lazy_export_ast(
bump,
&mut topts.define,
opts,
log,
Expr::init(E::Missing {}, Loc::EMPTY),
source,
b"",
)?
.ok_or_else(|| err!("ParserError"))?;
ast.import_records = bun_alloc::vec_from_iter_in(import_records, bump);
ast.has_lazy_export = false;
ast.parts.as_mut_slice()[1] = Part {
stmts: ast::StoreSlice::EMPTY,
import_record_indices: {
let mut import_record_indices = ast::PartImportRecordIndices::init_capacity(
import_records_len as usize,
);
import_record_indices
.extend(0..u32::try_from(import_records_len).expect("int cast"));
import_record_indices
},
..Default::default()
};
if output_format == js_parser::options::Format::Esm
|| output_format == js_parser::options::Format::Iife
{
ast.exports_kind = ast::ExportsKind::Esm;
}
return Ok(JSAst::init(ast));
}
Loader::Css => {
let mut import_records = Vec::<ImportRecord>::default();
let source_code = &source.contents;
let mut temp_log = Log::init();
const CSS_MODULE_SUFFIX: &[u8] = b".module.css";
let enable_css_modules = source.path.pretty.len() > CSS_MODULE_SUFFIX.len()
&& &source.path.pretty[source.path.pretty.len() - CSS_MODULE_SUFFIX.len()..]
== CSS_MODULE_SUFFIX;
let parser_options = {
let mut parseropts = bun_css::ParserOptions::default(None);
parseropts.logger = Some(core::ptr::NonNull::from(&mut temp_log));
if enable_css_modules {
parseropts.filename = bun_paths::basename(source.path.pretty);
parseropts.css_modules = Some(bun_css::CssModuleConfig::default());
}
parseropts
};
let (mut css_ast, extra) = match bun_css::BundlerStyleSheet::parse_bundler(
bump,
source_code,
parser_options,
&mut import_records,
bun_ast::Index::source(source.index.0),
) {
Ok(v) => v,
Err(e) => {
let _ = e.add_to_logger(&mut temp_log, source);
let _ = temp_log.append_to_maybe_recycled(log, source);
return Err(err!("SyntaxError"));
}
};
#[cfg(debug_assertions)]
if css_ast.local_scope.count() > 0 {
for entry in css_ast.local_scope.values() {
debug_assert!(entry.ref_.inner_index() < extra.symbols.len() as u32);
}
}
if let Err(e) = css_ast.minify(
bump,
&bun_css::MinifyOptions {
targets: bun_css::Targets::for_bundler_target(topts.target),
unused_symbols: Default::default(),
},
&extra,
) {
let _ = e.add_to_logger(&mut temp_log, source);
let _ = temp_log.append_to_maybe_recycled(log, source);
return Err(err!("MinifyError"));
}
if css_ast.local_scope.count() > 0 {
let _ = has_any_css_locals.fetch_add(1, Ordering::Relaxed);
}
let root = Expr::init(E::Object::default(), Loc { start: 0 });
let symbols = css_symbols_to_parser_symbols(&extra.symbols, bump);
let lazy = js_parser::new_lazy_export_ast_impl(
bump,
&mut topts.define,
opts,
&mut temp_log,
root,
source,
b"",
symbols,
);
let _ = temp_log.append_to_maybe_recycled(log, source);
let mut ast = JSAst::init(lazy?.ok_or_else(|| err!("ParserError"))?);
let css_ast_heap = crate::bundled_ast::CssAstRef::from_bump(bump.alloc(css_ast));
ast.css = Some(css_ast_heap);
ast.import_records = bun_alloc::vec_from_iter_in(import_records, bump);
return Ok(ast);
}
Loader::Dataurl | Loader::Base64 | Loader::Bunsh => {
return get_empty_ast::<E::String>(log, transpiler, opts, bump, source);
}
Loader::File | Loader::Wasm => {
debug_assert!(loader.should_copy_for_bundling());
let content_hash = ContentHasher::run(&source.contents);
let unique_key: &[u8] = if topts.has_dev_server() {
let mut buf = bun_alloc::ArenaString::new_in(bump);
write!(
&mut buf,
"{}/{}{}",
crate::bake_types::ASSET_PREFIX,
bun_core::fmt::bytes_to_hex_lower_string(&content_hash.to_ne_bytes()),
bstr::BStr::new(bun_paths::extension(source.path.text)),
)
.expect("unreachable");
buf.into_bump_str().as_bytes()
} else {
let mut buf = bun_alloc::ArenaString::new_in(bump);
write!(
&mut buf,
"{}",
crate::chunk::UniqueKey {
prefix: unique_key_prefix,
kind: crate::chunk::QueryKind::Asset,
index: source.index.0,
},
)
.expect("unreachable");
buf.into_bump_str().as_bytes()
};
let root = Expr::init(
E::String {
data: unique_key.into(),
..Default::default()
},
Loc { start: 0 },
);
*unique_key_for_additional_file = FileLoaderHash {
key: ast::StoreStr::new(unique_key),
content_hash,
};
let mut ast = JSAst::init(
js_parser::new_lazy_export_ast(
bump,
&mut topts.define,
opts,
log,
root,
source,
b"",
)?
.ok_or_else(|| err!("ParserError"))?,
);
ast.add_url_for_css(
bump,
source,
None,
Some(unique_key),
topts.compile_to_standalone_html,
);
return Ok(ast);
}
}
}
fn get_code_for_parse_task_without_plugins(
task: &mut ParseTask,
log: &mut Log,
transpiler: *mut Transpiler,
resolver: *mut Resolver,
bump: &Bump,
file_path: &mut Fs::Path,
_loader: Loader,
) -> core::result::Result<CacheEntry, AnyError> {
match &task.contents_or_fd {
ContentsOrFd::Fd { dir, file } => 'brk: {
let contents_dir = *dir;
let contents_file = *file;
let _trace = perf::trace("Bundler.readFile");
let ctx = unsafe { task.ctx() };
if let Some(file_map) = ctx.file_map {
if let Some(file_contents) = file_map.get(file_path.text) {
break 'brk Ok(CacheEntry {
contents: crate::cache::Contents::SharedBuffer {
ptr: file_contents.as_ptr(),
len: file_contents.len(),
},
fd: Fd::INVALID,
..Default::default()
});
}
}
if file_path.namespace == b"node" {
'lookup_builtin: {
if let Some(f) = &ctx.framework {
if let Some(file) = f.built_in_modules.get(file_path.text) {
match file {
crate::bake_types::BuiltInModule::Code(code) => {
break 'brk Ok(CacheEntry {
contents: crate::cache::Contents::SharedBuffer {
ptr: code.as_ptr(),
len: code.len(),
},
fd: Fd::INVALID,
..Default::default()
});
}
crate::bake_types::BuiltInModule::Import(path) => {
*file_path = Fs::Path::init(path);
break 'lookup_builtin;
}
}
}
}
let fallback =
NodeFallbackModules::contents_from_path(file_path.text).unwrap_or(b"");
break 'brk Ok(CacheEntry {
contents: crate::cache::Contents::SharedBuffer {
ptr: fallback.as_ptr(),
len: fallback.len(),
},
fd: Fd::INVALID,
..Default::default()
});
}
}
let read_arena: Option<&Bump> = Some(bump);
let fs_ref = unsafe { &mut *(*transpiler).fs };
break 'brk match unsafe { &mut (*resolver).caches.fs }.read_file_with_allocator(
fs_ref,
file_path.text,
contents_dir,
false,
contents_file.unwrap_valid(),
read_arena,
) {
Ok(e) => {
use bun_resolver::cache::Contents as RC;
let contents = match e.contents {
RC::Empty => crate::cache::Contents::Empty,
RC::Owned(v) => crate::cache::Contents::Owned(v),
RC::Arena { ptr, len } => crate::cache::Contents::Arena { ptr, len },
RC::SharedBuffer { ptr, len } => {
crate::cache::Contents::SharedBuffer { ptr, len }
}
RC::External { ptr, len } => {
crate::cache::Contents::External { ptr, len }
}
};
Ok(CacheEntry {
contents,
fd: e.fd,
..Default::default()
})
}
Err(e) => {
let source = Source::init_empty_file_interned(
file_path.text,
);
if e == err!("ENOENT") || e == err!("FileNotFound") {
let _ = log.add_error_fmt(
Some(&source),
Loc::EMPTY,
format_args!(
"File not found {}",
bun_core::fmt::quote(file_path.text)
),
);
return Err(err!("FileNotFound"));
} else {
let _ = log.add_error_fmt(
Some(&source),
Loc::EMPTY,
format_args!(
"{} reading file: {}",
e.name(),
bun_core::fmt::quote(file_path.text)
),
);
}
return Err(e);
}
};
}
ContentsOrFd::Contents(contents) => Ok(CacheEntry {
contents: crate::cache::Contents::SharedBuffer {
ptr: contents.as_ptr(),
len: contents.len(),
},
fd: Fd::INVALID,
..Default::default()
}),
}
}
#[allow(clippy::too_many_arguments)]
fn get_code_for_parse_task<'b>(
task: &mut ParseTask,
log: &mut Log,
transpiler: *mut Transpiler<'b>,
resolver: *mut Resolver<'b>,
bump: &Bump,
file_path: &mut Fs::Path<'b>,
loader: &mut Loader,
from_plugin: &mut bool,
) -> core::result::Result<CacheEntry, AnyError> {
let might_have_on_parse_plugins = 'brk: {
if task.source_index.is_runtime() {
break 'brk false;
}
let ctx = unsafe { task.ctx() };
let Some(plugin) = ctx.plugins_ref() else {
break 'brk false;
};
if !plugin.has_on_before_parse_plugins() {
break 'brk false;
}
if file_path.namespace == b"node" {
break 'brk false;
}
true
};
if !might_have_on_parse_plugins {
return get_code_for_parse_task_without_plugins(
task, log, transpiler, resolver, bump, file_path, *loader,
);
}
let should_continue_running = core::cell::Cell::new(1i32);
let mut ctx = OnBeforeParsePlugin {
task,
log,
transpiler,
resolver,
bump,
file_path,
loader,
deferred_error: None,
should_continue_running: &should_continue_running,
result: core::ptr::null_mut(),
original_contents: None,
};
let plugins = unsafe { ctx.task.ctx() }
.plugins_ref()
.expect("unreachable");
ctx.run(plugins, from_plugin)
}
pub struct OnBeforeParsePlugin<'a, 'b: 'a> {
task: &'a mut ParseTask,
log: &'a mut Log,
transpiler: *mut Transpiler<'b>,
resolver: *mut Resolver<'b>,
bump: &'a Bump,
file_path: &'a mut Fs::Path<'b>,
loader: &'a mut Loader,
deferred_error: Option<AnyError>,
should_continue_running: &'a core::cell::Cell<i32>,
result: *mut OnBeforeParseResult,
original_contents: Option<crate::cache::Contents>,
}
#[repr(C)]
pub struct OnBeforeParseArguments {
pub struct_size: usize,
pub context: *mut OnBeforeParsePlugin<'static, 'static>, pub path_ptr: *const u8,
pub path_len: usize,
pub namespace_ptr: *const u8,
pub namespace_len: usize,
pub default_loader: Loader,
pub external: *mut c_void, }
impl Default for OnBeforeParseArguments {
fn default() -> Self {
Self {
struct_size: core::mem::size_of::<OnBeforeParseArguments>(),
context: core::ptr::null_mut(),
path_ptr: b"".as_ptr(),
path_len: 0,
namespace_ptr: b"file".as_ptr(),
namespace_len: b"file".len(),
default_loader: Loader::File,
external: core::ptr::null_mut(),
}
}
}
#[repr(C)]
pub struct BunLogOptions {
pub struct_size: usize,
pub message_ptr: *const u8,
pub message_len: usize,
pub path_ptr: *const u8,
pub path_len: usize,
pub source_line_text_ptr: *const u8,
pub source_line_text_len: usize,
pub level: bun_ast::Level,
pub line: i32,
pub line_end: i32,
pub column: i32,
pub column_end: i32,
}
impl Default for BunLogOptions {
fn default() -> Self {
Self {
struct_size: core::mem::size_of::<BunLogOptions>(),
message_ptr: core::ptr::null(),
message_len: 0,
path_ptr: core::ptr::null(),
path_len: 0,
source_line_text_ptr: core::ptr::null(),
source_line_text_len: 0,
level: bun_ast::Level::Err,
line: 0,
line_end: 0,
column: 0,
column_end: 0,
}
}
}
bun_core::assert_ffi_layout!(
OnBeforeParseArguments, 64, 8;
struct_size @ 0, context @ 8, path_ptr @ 16, path_len @ 24,
namespace_ptr @ 32, namespace_len @ 40, default_loader @ 48, external @ 56,
);
bun_core::assert_ffi_layout!(
BunLogOptions, 80, 8;
struct_size @ 0, message_ptr @ 8, message_len @ 16, path_ptr @ 24,
path_len @ 32, source_line_text_ptr @ 40, source_line_text_len @ 48,
level @ 56, line @ 60, line_end @ 64, column @ 68, column_end @ 72,
);
bun_core::assert_ffi_layout!(
OnBeforeParseResult, 64, 8;
struct_size @ 0, source_ptr @ 8, source_len @ 16, loader @ 24,
fetch_source_code_fn @ 32, user_context @ 40, free_user_context @ 48, log @ 56,
);
impl BunLogOptions {
pub(crate) fn source_line_text(&self) -> &[u8] {
if !self.source_line_text_ptr.is_null() && self.source_line_text_len > 0 {
return unsafe {
core::slice::from_raw_parts(
self.source_line_text_ptr,
self.source_line_text_len,
)
};
}
b""
}
pub(crate) fn path(&self) -> &[u8] {
if !self.path_ptr.is_null() && self.path_len > 0 {
return unsafe { core::slice::from_raw_parts(self.path_ptr, self.path_len) };
}
b""
}
pub(crate) fn message(&self) -> &[u8] {
if !self.message_ptr.is_null() && self.message_len > 0 {
return unsafe { core::slice::from_raw_parts(self.message_ptr, self.message_len) };
}
b""
}
pub(crate) fn append(&self, log: &mut Log, namespace: &'static [u8]) {
let source_line_text = self.source_line_text();
let file = log.dupe(self.path());
let line_text = if !source_line_text.is_empty() {
Some(log.dupe(source_line_text))
} else {
None
};
let location = Location::init(
file,
namespace,
self.line.max(-1),
self.column.max(-1),
(self.column_end - self.column).max(0) as u32,
line_text,
);
let mut msg = Msg {
data: bun_ast::Data {
location: Some(location),
text: std::borrow::Cow::Owned(self.message().to_vec()),
..Default::default()
},
..Default::default()
};
match self.level {
bun_ast::Level::Err => msg.kind = bun_ast::Kind::Err,
bun_ast::Level::Warn => msg.kind = bun_ast::Kind::Warn,
bun_ast::Level::Verbose => msg.kind = bun_ast::Kind::Verbose,
bun_ast::Level::Debug => msg.kind = bun_ast::Kind::Debug,
_ => {}
}
if msg.kind == bun_ast::Kind::Err {
log.errors += 1;
} else if msg.kind == bun_ast::Kind::Warn {
log.warnings += 1;
}
let _ = log.add_msg(msg);
}
pub(crate) unsafe extern "C" fn log_fn(
args_: *mut OnBeforeParseArguments,
log_options_: *mut BunLogOptions,
) {
let Some(args) = (unsafe { args_.as_mut() }) else {
return;
};
let Some(log_options) = (unsafe { log_options_.as_ref() }) else {
return;
};
let ctx = unsafe { &mut *args.context };
log_options.append(ctx.log, ctx.file_path.namespace);
}
}
#[repr(C)]
pub(crate) struct OnBeforeParseResultWrapper {
pub original_source: *const u8,
pub original_source_len: usize,
pub original_source_fd: Fd,
pub loader: Loader,
#[cfg(debug_assertions)]
pub check: u32, pub result: OnBeforeParseResult,
}
#[repr(C)]
pub struct OnBeforeParseResult {
pub struct_size: usize,
pub source_ptr: *const u8,
pub source_len: usize,
pub loader: Loader,
pub fetch_source_code_fn:
unsafe extern "C" fn(*mut OnBeforeParseArguments, *mut OnBeforeParseResult) -> i32,
pub user_context: *mut c_void,
pub free_user_context: Option<extern "C" fn(*mut c_void)>,
pub log: unsafe extern "C" fn(*mut OnBeforeParseArguments, *mut BunLogOptions),
}
impl OnBeforeParseResult {
pub(crate) unsafe fn get_wrapper(
result: *mut OnBeforeParseResult,
) -> *mut OnBeforeParseResultWrapper {
let wrapper =
unsafe { bun_core::from_field_ptr!(OnBeforeParseResultWrapper, result, result) };
#[cfg(debug_assertions)]
debug_assert_eq!(unsafe { (*wrapper).check }, 42069);
wrapper
}
}
pub(crate) unsafe extern "C" fn fetch_source_code(
args: *mut OnBeforeParseArguments,
result_ptr: *mut OnBeforeParseResult,
) -> i32 {
scoped_log!(ParseTask, "fetchSourceCode");
let args = unsafe { &mut *args };
let this = unsafe { &mut *args.context };
if this.log.errors > 0
|| this.deferred_error.is_some()
|| this.should_continue_running.get() != 1
{
return 1;
}
{
let result = unsafe { &mut *result_ptr };
if !result.source_ptr.is_null() {
return 0;
}
let mut entry = match get_code_for_parse_task_without_plugins(
this.task,
this.log,
this.transpiler,
this.resolver,
this.bump,
this.file_path,
result.loader,
) {
Ok(e) => e,
Err(e) => {
this.deferred_error = Some(e);
this.should_continue_running.set(0);
return 1;
}
};
let fd = entry.fd;
this.original_contents = Some(core::mem::take(&mut entry.contents));
let contents_slice = this
.original_contents
.as_ref()
.expect("just set")
.as_slice();
let source_ptr = contents_slice.as_ptr();
let source_len = contents_slice.len();
result.source_ptr = source_ptr;
result.source_len = source_len;
result.free_user_context = None;
result.user_context = core::ptr::null_mut();
let wrapper = unsafe { OnBeforeParseResult::get_wrapper(result_ptr) };
unsafe {
(*wrapper).original_source = source_ptr;
(*wrapper).original_source_len = source_len;
(*wrapper).original_source_fd = fd;
}
}
0
}
#[unsafe(no_mangle)]
pub(crate) unsafe extern "C" fn OnBeforeParseResult__reset(this: *mut OnBeforeParseResult) {
let wrapper = unsafe { OnBeforeParseResult::get_wrapper(this) };
unsafe {
(*this).loader = (*wrapper).loader;
if !(*wrapper).original_source.is_null() {
(*this).source_ptr = (*wrapper).original_source;
(*this).source_len = (*wrapper).original_source_len;
} else {
(*this).source_ptr = core::ptr::null();
(*this).source_len = 0;
}
}
}
#[unsafe(no_mangle)]
pub(crate) unsafe extern "C" fn OnBeforeParsePlugin__isDone(
this: *mut OnBeforeParsePlugin<'_, '_>,
) -> i32 {
unsafe {
if (*this).should_continue_running.get() != 1 {
return 1;
}
let result = (*this).result;
if result.is_null() {
return 1;
}
let source_ptr = (*result).source_ptr;
if !source_ptr.is_null() {
let wrapper = OnBeforeParseResult::get_wrapper(result);
return (source_ptr != (*wrapper).original_source) as i32;
}
}
0
}
impl<'a, 'b: 'a> OnBeforeParsePlugin<'a, 'b> {
pub fn run(
&mut self,
plugin: &bundler::JSBundlerPlugin,
from_plugin: &mut bool,
) -> core::result::Result<CacheEntry, AnyError> {
let mut args = OnBeforeParseArguments {
path_ptr: self.file_path.text.as_ptr(),
path_len: self.file_path.text.len(),
default_loader: *self.loader,
..Default::default()
};
if !self.file_path.namespace.is_empty() {
args.namespace_ptr = self.file_path.namespace.as_ptr();
args.namespace_len = self.file_path.namespace.len();
}
let mut wrapper = OnBeforeParseResultWrapper {
original_source: core::ptr::null(),
original_source_len: 0,
original_source_fd: Fd::INVALID,
loader: *self.loader,
#[cfg(debug_assertions)]
check: 42069,
result: OnBeforeParseResult {
struct_size: core::mem::size_of::<OnBeforeParseResult>(),
source_ptr: core::ptr::null(),
source_len: 0,
loader: *self.loader,
fetch_source_code_fn: fetch_source_code,
user_context: core::ptr::null_mut(),
free_user_context: None,
log: BunLogOptions::log_fn,
},
};
let result_ptr = core::ptr::addr_of_mut!(wrapper.result);
let namespace_str;
let namespace = if self.file_path.namespace == b"file" {
&bun_core::String::EMPTY
} else {
namespace_str = bun_core::String::init(self.file_path.namespace);
&namespace_str
};
let path_str = bun_core::String::init(self.file_path.text);
let should_continue_running = self.should_continue_running;
self.result = result_ptr;
let self_ptr = std::ptr::from_mut(self).cast::<OnBeforeParsePlugin<'static, 'static>>();
args.context = self_ptr;
let count = plugin.call_on_before_parse_plugins(
self_ptr.cast(),
namespace,
&path_str,
&raw mut args,
result_ptr,
should_continue_running,
);
if cfg!(feature = "debug_logs") {
scoped_log!(
ParseTask,
"callOnBeforeParsePlugins({}:{}) = {}",
bstr::BStr::new(self.file_path.namespace),
bstr::BStr::new(self.file_path.text),
count
);
}
if count > 0 {
if let Some(e) = self.deferred_error {
if let Some(free_user_context) = wrapper.result.free_user_context {
free_user_context(wrapper.result.user_context);
}
return Err(e);
}
if wrapper.result.user_context.is_null()
&& wrapper.result.free_user_context.is_some()
{
let mut msg = Msg {
data: bun_ast::Data {
location: None,
text: std::borrow::Cow::Borrowed(
&b"Native plugin set the `free_plugin_source_code_context` field without setting the `plugin_source_code_context` field."[..],
),
..Default::default()
},
..Default::default()
};
msg.kind = bun_ast::Kind::Err;
self.log.errors += 1;
let _ = self.log.add_msg(msg); return Err(err!("InvalidNativePlugin"));
}
if self.log.errors > 0 {
if let Some(free_user_context) = wrapper.result.free_user_context {
free_user_context(wrapper.result.user_context);
}
return Err(err!("SyntaxError"));
}
if !wrapper.result.source_ptr.is_null() {
let ptr = wrapper.result.source_ptr;
let free_fn = wrapper
.result
.free_user_context
.map(|f| f as unsafe extern "C" fn(*mut c_void));
if free_fn.is_some() {
self.task.external_free_function = ExternalFreeFunction {
ctx: wrapper.result.user_context,
function: free_fn,
};
}
*from_plugin = true;
*self.loader = wrapper.result.loader;
let contents =
if !wrapper.original_source.is_null() && ptr == wrapper.original_source {
self.original_contents
.take()
.expect("original_contents set alongside original_source")
} else {
crate::cache::Contents::External {
ptr,
len: wrapper.result.source_len,
}
};
return Ok(CacheEntry {
contents,
external_free_function: ExternalFreeFunction {
ctx: wrapper.result.user_context,
function: free_fn,
},
fd: wrapper.original_source_fd,
});
}
}
get_code_for_parse_task_without_plugins(
self.task,
self.log,
self.transpiler,
self.resolver,
self.bump,
self.file_path,
*self.loader,
)
}
}
fn get_source_code(
task: &mut ParseTask,
this: &mut crate::Worker,
log: &mut Log,
) -> core::result::Result<CacheEntry, AnyError> {
let bump: &Bump = this.arena.get();
let data = this.data.as_mut().expect("Worker.data set in create()");
let transpiler: *mut Transpiler<'static> = &raw mut data.transpiler;
let resolver: *mut Resolver = unsafe { core::ptr::addr_of_mut!((*transpiler).resolver) };
let mut file_path = task.path;
let mut loader = task
.loader
.or_else(|| file_path.loader(unsafe { &(*transpiler).options.loaders }))
.unwrap_or(Loader::File);
let mut contents_came_from_plugin: bool = false;
let result = get_code_for_parse_task(
task,
log,
transpiler,
resolver,
bump,
&mut file_path,
&mut loader,
&mut contents_came_from_plugin,
);
if result.is_err() {
unsafe { (*transpiler).reset_store() };
}
result
}
fn run_with_source_code(
task: &mut ParseTask,
this: &mut crate::Worker,
step: &mut Step,
log: &mut Log,
entry: &mut CacheEntry,
) -> core::result::Result<Success, AnyError> {
let worker_raw: *mut crate::Worker = this;
let bump: &'static Bump = unsafe { bun_ptr::detach_lifetime_ref(&*(*worker_raw).arena) };
let mut transpiler: *mut Transpiler<'static> =
std::ptr::from_mut(unsafe { (*worker_raw).transpiler_for_target(task.known_target) });
let resolver: *mut Resolver = unsafe { core::ptr::addr_of_mut!((*transpiler).resolver) };
let file_path = &mut task.path;
let loader = task
.loader
.or_else(|| file_path.loader(unsafe { &(*transpiler).options.loaders }))
.unwrap_or(Loader::File);
#[cfg(debug_assertions)]
let debug_original_variant_check: ContentsOrFdTag = task.contents_or_fd.tag();
let worker_ctx = unsafe { (*worker_raw).ctx };
let will_close_file_descriptor = matches!(task.contents_or_fd, ContentsOrFd::Fd { .. })
&& entry.fd.is_valid()
&& entry.fd.stdio_tag().is_none()
&& worker_ctx.bun_watcher.is_none();
if will_close_file_descriptor {
let _ = entry.close_fd();
task.contents_or_fd = ContentsOrFd::Fd {
file: Fd::INVALID,
dir: Fd::INVALID,
};
} else if matches!(task.contents_or_fd, ContentsOrFd::Fd { .. }) {
task.contents_or_fd = ContentsOrFd::Fd {
file: entry.fd,
dir: Fd::INVALID,
};
}
*step = Step::Parse;
let entry_contents: &[u8] = entry.contents.as_slice();
let is_empty = strings::is_all_whitespace(entry_contents);
let topts = unsafe { &(*transpiler).options };
let use_directive: UseDirective = if !is_empty && topts.server_components {
UseDirective::parse(entry_contents).unwrap_or(UseDirective::None)
} else {
UseDirective::None
};
if (use_directive == UseDirective::Client
&& task.known_target != options::Target::BakeServerComponentsSsr
&& worker_ctx.framework.is_some()
&& worker_ctx
.framework
.as_ref()
.unwrap()
.server_components
.as_ref()
.unwrap()
.separate_ssr_graph)
||
((topts.server_components || topts.has_dev_server())
&& task.known_target == options::Target::Browser)
{
transpiler = std::ptr::from_mut(unsafe {
(*worker_raw).transpiler_for_target(options::Target::Browser)
});
}
let topts = unsafe { &(*transpiler).options };
let source: &'static Source = bump.alloc(Source {
path: bun_paths::fs::Path {
text: file_path.text,
namespace: file_path.namespace,
pretty: file_path.pretty,
is_disabled: file_path.is_disabled,
is_symlink: file_path.is_symlink,
},
index: bun_ast::Index(task.source_index.get()),
contents: std::borrow::Cow::Borrowed(ast::StoreStr::new(entry_contents).slice()),
contents_is_recycled: false,
..Default::default()
});
let target = (if task.source_index.get() == 1 {
target_from_hashbang(entry_contents)
} else {
None
})
.unwrap_or_else(|| {
if task.known_target == options::Target::BakeServerComponentsSsr
&& topts
.framework
.as_ref()
.unwrap()
.server_components
.as_ref()
.unwrap()
.separate_ssr_graph
{
options::Target::BakeServerComponentsSsr
} else {
topts.target
}
});
let output_format = topts.output_format;
let mut opts = ParserOptions::init(
crate::transpiler::to_parser_jsx_pragma(task.jsx.clone()),
loader,
);
opts.bundle = true;
opts.warn_about_unbundled_modules = false;
opts.allow_unresolved = unsafe { bun_collections::detach_ref(&topts.allow_unresolved) };
opts.macro_context = unsafe {
Some(&mut *std::ptr::from_mut(
(*transpiler).macro_context.as_mut().unwrap(),
))
};
opts.package_version = task.package_version.slice();
opts.features.allow_runtime = !task.source_index.is_runtime();
opts.features.unwrap_commonjs_to_esm =
output_format == options::Format::Esm && FeatureFlags::UNWRAP_COMMONJS_TO_ESM;
opts.features.top_level_await = output_format == options::Format::Esm
|| output_format == options::Format::InternalBakeDev;
opts.features.auto_import_jsx = task.jsx.parse && topts.auto_import_jsx;
opts.features.trim_unused_imports =
loader.is_typescript() || topts.trim_unused_imports.unwrap_or(false);
opts.features.inlining = topts.minify_syntax;
opts.output_format = match output_format {
options::Format::Esm => js_parser::options::Format::Esm,
options::Format::Cjs => js_parser::options::Format::Cjs,
options::Format::Iife => js_parser::options::Format::Iife,
options::Format::InternalBakeDev => js_parser::options::Format::InternalBakeDev,
};
opts.features.minify_syntax = topts.minify_syntax;
opts.features.minify_identifiers = topts.minify_identifiers;
opts.features.minify_keep_names = topts.keep_names;
opts.features.minify_whitespace = topts.minify_whitespace;
opts.features.emit_decorator_metadata = task.emit_decorator_metadata;
opts.features.standard_decorators = !loader.is_typescript()
|| !(task.experimental_decorators || task.emit_decorator_metadata);
opts.features.unwrap_commonjs_packages = topts.unwrap_commonjs_packages;
opts.features.bundler_feature_flags = topts
.bundler_feature_flags
.as_deref()
.map(|s| Box::new(bun_core::handle_oom(s.clone())));
opts.features.lower_using = !target.is_bun();
opts.features.hot_module_reloading =
output_format == options::Format::InternalBakeDev && !task.source_index.is_runtime();
opts.features.auto_polyfill_require =
output_format == options::Format::Esm && !opts.features.hot_module_reloading;
opts.features.react_fast_refresh =
topts.react_fast_refresh && loader.is_jsx() && !source.path.is_node_module();
opts.features.server_components = if topts.server_components {
use bun_ast::runtime::ServerComponentsMode as SC;
match target {
options::Target::Browser => SC::ClientSide,
_ => match use_directive {
UseDirective::None => SC::WrapAnonServerFunctions,
UseDirective::Client => {
if topts
.framework
.as_ref()
.unwrap()
.server_components
.as_ref()
.unwrap()
.separate_ssr_graph
{
SC::ClientSide
} else {
SC::WrapExportsForClientReference
}
}
UseDirective::Server => SC::WrapExportsForServerReference,
},
}
} else {
bun_ast::runtime::ServerComponentsMode::None
};
opts.framework = topts.framework.map(|f| {
let projected = js_parser::options::Framework {
is_built_in_react: f.is_built_in_react,
server_components: f.server_components.as_ref().map(|sc| {
js_parser::options::FrameworkServerComponents {
separate_ssr_graph: sc.separate_ssr_graph,
server_runtime_import: std::borrow::Cow::Borrowed(
bump.alloc_slice_copy(&sc.server_runtime_import),
),
server_register_client_reference: std::borrow::Cow::Borrowed(
bump.alloc_slice_copy(&sc.server_register_client_reference),
),
server_register_server_reference: std::borrow::Cow::Borrowed(
bump.alloc_slice_copy(&sc.server_register_server_reference),
),
client_register_server_reference: std::borrow::Cow::Borrowed(
bump.alloc_slice_copy(&sc.client_register_server_reference),
),
}
}),
react_fast_refresh: f.react_fast_refresh.as_ref().map(|rfr| {
js_parser::options::ReactFastRefresh {
import_source: std::borrow::Cow::Borrowed(
bump.alloc_slice_copy(&rfr.import_source),
),
}
}),
};
unsafe {
bun_collections::detach_ref::<js_parser::options::Framework>(bump.alloc(projected))
}
});
opts.ignore_dce_annotations =
topts.ignore_dce_annotations && !task.source_index.is_runtime();
if topts.inline_entrypoint_import_meta_main || !task.is_entry_point {
opts.import_meta_main_value = Some(task.is_entry_point && !topts.has_dev_server());
} else if target == options::Target::Node {
opts.lower_import_meta_main_for_node_js = true;
}
opts.tree_shaking = if task.source_index.is_runtime() {
true
} else {
topts.tree_shaking
};
opts.code_splitting = topts.code_splitting;
opts.module_type = task.module_type;
task.jsx.parse = loader.is_jsx();
let mut unique_key_for_additional_file = FileLoaderHash {
key: ast::StoreStr::EMPTY,
content_hash: 0,
};
let task_ctx = unsafe { task.ctx() };
let module_type = opts.module_type;
let _ = topts;
let ast_result: core::result::Result<JSAst, AnyError> =
if !is_empty || loader.handles_empty_file() {
get_ast(
log,
transpiler,
opts,
bump,
resolver,
source,
loader,
task_ctx.unique_key,
&mut unique_key_for_additional_file,
&task_ctx.linker.has_any_css_locals,
)
} else if loader.is_css() {
get_empty_css_ast(log, transpiler, opts, bump, source)
} else if module_type == options::ModuleType::Esm {
get_empty_ast::<E::Undefined>(log, transpiler, opts, bump, source)
} else {
get_empty_ast::<E::Object>(log, transpiler, opts, bump, source)
};
let mut ast = match ast_result {
Ok(a) => a,
Err(e) => {
#[cfg(debug_assertions)]
if task.contents_or_fd.tag() != debug_original_variant_check {
panic!(
"BUG: `task.contents_or_fd` changed in a way that will cause a double free or memory to leak!\n\n Original = {}\n New = {}\n",
<&'static str>::from(debug_original_variant_check),
<&'static str>::from(task.contents_or_fd.tag()),
);
}
unsafe { (*transpiler).reset_store() };
if matches!(task.contents_or_fd, ContentsOrFd::Fd { .. }) {
entry.deinit();
}
return Err(e);
}
};
ast.target = target;
if ast.parts.len() <= 1
&& ast.css.is_none()
&& (task.loader.is_none() || task.loader.unwrap() != Loader::Html)
{
task.side_effects = bun_ast::SideEffects::NoSideEffectsEmptyAst;
}
*step = Step::Resolve;
Ok(Success {
ast,
source: source.clone(),
log: core::mem::take(log),
use_directive,
unique_key_for_additional_file: unique_key_for_additional_file.key,
side_effects: task.side_effects,
loader,
package_name: task.package_name,
content_hash_for_additional_file: if loader.should_copy_for_bundling() {
unique_key_for_additional_file.content_hash
} else {
0
},
})
}
pub(crate) fn run_from_thread_pool(this: &mut ParseTask) {
run_from_thread_pool_impl(this);
}
fn run_from_thread_pool_impl(this: &mut ParseTask) {
let ctx = unsafe { this.ctx() };
let worker: &mut crate::Worker = crate::Worker::get(ctx);
scoped_log!(
ParseTask,
"ParseTask(0x{:x}, {}) callback",
std::ptr::from_mut(this) as usize,
bstr::BStr::new(this.path.text)
);
let mut step: Step = Step::Pending;
let mut log = Log::init();
debug_assert!(this.source_index.is_valid());
let value: ResultValue = 'value: {
if matches!(this.stage, ParseTaskStage::NeedsSourceCode) {
match get_source_code(this, worker, &mut log) {
Ok(entry) => this.stage = ParseTaskStage::NeedsParse(entry),
Err(e) => {
break 'value ResultValue::Err(ResultError {
err: e,
step,
log,
source_index: this.source_index,
target: this.known_target,
});
}
}
if log.has_errors() {
break 'value ResultValue::Err(ResultError {
err: err!("SyntaxError"),
step,
log,
source_index: this.source_index,
target: this.known_target,
});
}
if crate::ThreadPool::uses_io_pool() {
ctx.graph.pool().schedule_inside_thread_pool(this);
worker.unget();
return;
}
}
let mut entry =
match core::mem::replace(&mut this.stage, ParseTaskStage::NeedsSourceCode) {
ParseTaskStage::NeedsParse(e) => e,
ParseTaskStage::NeedsSourceCode => unreachable!(),
};
let parsed = run_with_source_code(this, worker, &mut step, &mut log, &mut entry);
this.stage = ParseTaskStage::NeedsParse(entry);
match parsed {
Ok(ast) => {
if ctx.transpiler().options.has_dev_server() && ast.log.has_errors() {
break 'value ResultValue::Err(ResultError {
err: err!("SyntaxError"),
step: Step::Parse,
log: ast.log,
source_index: this.source_index,
target: this.known_target,
});
}
break 'value ResultValue::Success(ast);
}
Err(e) => {
if e == err!("EmptyAST") {
drop(log);
break 'value ResultValue::Empty {
source_index: this.source_index,
};
}
break 'value ResultValue::Err(ResultError {
err: e,
step,
log,
source_index: this.source_index,
target: this.known_target,
});
}
}
};
let result = Box::new(Result {
ctx: this.ctx.expect("ParseTask.ctx unset"),
task: EventLoop::Task::default(),
value,
external: core::mem::take(&mut this.external_free_function),
watcher_data: match this.contents_or_fd {
ContentsOrFd::Fd { file, dir } => WatcherData {
fd: file,
dir_fd: dir,
},
ContentsOrFd::Contents(_) => WatcherData::NONE,
},
});
let result = bun_core::heap::into_raw(result);
drop(core::mem::take(&mut this.jsx));
match worker
.ctx
.linker
.any_loop_mut()
.expect("BundleV2.linker.loop must be set before scheduling ParseTask")
{
bun_event_loop::AnyEventLoop::Js { owner } => {
owner.enqueue_task_concurrent(
bun_event_loop::ConcurrentTask::ConcurrentTask::from_callback(result, |p| {
unsafe { on_complete(p) };
Ok(())
}),
);
}
bun_event_loop::AnyEventLoop::Mini(mini) => {
unsafe {
mini.enqueue_task_concurrent_with_extra_ctx::<Result, BundleV2<'static>>(
result,
on_complete_mini,
offset_of!(Result, task),
);
}
}
}
worker.unget();
}
fn drop_result_owned_fields(result: &mut Result) {
match &mut result.value {
ResultValue::Success(s) => drop(core::mem::take(&mut s.log)),
ResultValue::Err(e) => drop(core::mem::take(&mut e.log)),
ResultValue::Empty { .. } => {}
}
}
fn on_complete_mini(result: *mut Result, ctx: *mut BundleV2<'static>) {
BundleV2::on_parse_task_complete(unsafe { &mut *result }, unsafe { &mut *ctx });
drop_result_owned_fields(unsafe { &mut *result });
unsafe { std::alloc::dealloc(result.cast::<u8>(), std::alloc::Layout::new::<Result>()) };
}
pub unsafe fn on_complete(result: *mut Result) {
let r = unsafe { &mut *result };
let ctx = r.ctx;
BundleV2::on_parse_task_complete(r, unsafe { ctx.assume_mut() });
drop_result_owned_fields(r);
unsafe { std::alloc::dealloc(result.cast::<u8>(), std::alloc::Layout::new::<Result>()) };
}
}
pub use parse_worker::{FileLoaderHash, OnBeforeParsePlugin, get_runtime_source, on_complete};
pub use crate::DeferredBatchTask::DeferredBatchTask;