use std::{borrow::Cow, path::Path};
use rolldown_common::{
ModuleIdx, ModuleType, NormalizedBundlerOptions, ResolvedId, SourcemapChainElement, StrOrBytes,
side_effects::HookSideEffects,
};
use rolldown_fs::FileSystem;
use rolldown_plugin::{HookLoadArgs, PluginDriver};
use rustc_hash::FxHashMap;
use sugar_path::SugarPath;
#[expect(clippy::too_many_arguments)]
pub async fn load_source<Fs: FileSystem + 'static>(
plugin_driver: &PluginDriver,
resolved_id: &ResolvedId,
fs: Fs,
sourcemap_chain: &mut Vec<SourcemapChainElement>,
side_effects: &mut Option<HookSideEffects>,
options: &NormalizedBundlerOptions,
asserted_module_type: Option<&ModuleType>,
is_read_from_disk: &mut bool,
module_idx: ModuleIdx,
) -> anyhow::Result<(StrOrBytes, ModuleType)> {
let (maybe_source, mut maybe_module_type) = if resolved_id.id.is_empty_module() {
(Some(String::new()), Some(ModuleType::Empty))
} else {
plugin_driver
.load(&HookLoadArgs { id: &resolved_id.id, module_idx, asserted_module_type })
.await?
.map(|load_hook_output| {
sourcemap_chain.extend(load_hook_output.map.map(SourcemapChainElement::Load));
if let Some(v) = load_hook_output.side_effects {
*side_effects = Some(v);
}
(Some(load_hook_output.code.to_string()), load_hook_output.module_type)
})
.unwrap_or_default()
};
if let Some(asserted) = asserted_module_type {
if maybe_module_type.is_none() {
maybe_module_type = Some(asserted.clone());
}
}
if maybe_source.is_some() {
*is_read_from_disk = false;
}
match (maybe_source, maybe_module_type) {
(Some(source), Some(module_type)) => Ok((source.into(), module_type)),
(source, None) => {
let guessed = get_module_loader_from_file_extension(&resolved_id.id, &options.module_types);
match (source, guessed) {
(None, None) => {
Ok((
StrOrBytes::Str({
#[cfg(not(target_family = "wasm"))]
{
let id = resolved_id.id.clone();
tokio::runtime::Handle::current()
.spawn_blocking(move || fs.read_to_string(id.as_path()))
.await??
}
#[cfg(target_family = "wasm")]
{
fs.read_to_string(resolved_id.id.as_path())?
}
}),
ModuleType::Js,
))
}
(source, Some(guessed)) => match &guessed {
ModuleType::Base64 | ModuleType::Binary | ModuleType::Dataurl => Ok((
StrOrBytes::Bytes({
match source {
Some(s) => s.into_bytes(),
None => {
if cfg!(target_family = "wasm") {
fs.read(resolved_id.id.as_path())?
} else {
let id = resolved_id.id.clone();
tokio::runtime::Handle::current()
.spawn_blocking(move || fs.read(id.as_path()))
.await??
}
}
}
}),
guessed,
)),
ModuleType::Copy => Err(anyhow::format_err!(
"Encountered a module with type `copy`, but no plugin handled it. \
If you configured this file's extension as `copy` in `moduleTypes`, \
ensure the builtin copy-module plugin is enabled."
))?,
ModuleType::Asset => Err(anyhow::format_err!(
"Encountered a module with type `asset`, but no plugin handled it. \
If you configured this file's extension as `asset` in `moduleTypes`, \
ensure the builtin asset-module plugin is enabled."
))?,
ModuleType::Js
| ModuleType::Jsx
| ModuleType::Ts
| ModuleType::Tsx
| ModuleType::Json
| ModuleType::Text
| ModuleType::Empty
| ModuleType::Css
| ModuleType::Custom(_) => Ok((
StrOrBytes::Str({
if let Some(s) = source {
s
} else {
#[cfg(not(target_family = "wasm"))]
{
let id = resolved_id.id.clone();
tokio::runtime::Handle::current()
.spawn_blocking(move || fs.read_to_string(id.as_path()))
.await??
}
#[cfg(target_family = "wasm")]
{
fs.read_to_string(resolved_id.id.as_path())?
}
}
}),
guessed,
)),
},
(Some(source), None) => Ok((StrOrBytes::Str(source), ModuleType::Js)),
}
}
(None, Some(ty)) => {
assert!(asserted_module_type.is_some(), "Invalid state");
Ok((read_file_by_module_type(resolved_id.id.as_path(), &ty, fs).await?, ty))
}
}
}
fn get_module_loader_from_file_extension<S: AsRef<str>>(
id: S,
module_types: &FxHashMap<Cow<'static, str>, ModuleType>,
) -> Option<ModuleType> {
let id = id.as_ref();
for i in memchr::memchr_iter(b'.', id.as_bytes()) {
if let Some(ty) = module_types.get(&id[i + 1..]) {
return Some(ty.clone());
}
}
None
}
async fn read_file_by_module_type<Fs: FileSystem + 'static>(
path: impl AsRef<Path>,
ty: &ModuleType,
fs: Fs,
) -> anyhow::Result<StrOrBytes> {
let path = path.as_ref().to_path_buf();
match ty {
ModuleType::Js
| ModuleType::Jsx
| ModuleType::Ts
| ModuleType::Tsx
| ModuleType::Json
| ModuleType::Css
| ModuleType::Empty
| ModuleType::Copy
| ModuleType::Custom(_)
| ModuleType::Text => Ok(StrOrBytes::Str({
if cfg!(target_family = "wasm") {
fs.read_to_string(&path)?
} else {
tokio::runtime::Handle::current().spawn_blocking(move || fs.read_to_string(&path)).await??
}
})),
ModuleType::Asset => Err(anyhow::format_err!(
"Encountered a module with type `asset` in read_file_by_module_type. \
Asset modules should be handled by the builtin asset-module plugin."
))?,
ModuleType::Base64 | ModuleType::Binary | ModuleType::Dataurl => Ok(StrOrBytes::Bytes({
if cfg!(target_family = "wasm") {
fs.read(&path)?
} else {
tokio::runtime::Handle::current().spawn_blocking(move || fs.read(&path)).await??
}
})),
}
}