use crate::{
HookResolveIdArgs, PluginDriver,
types::{custom_field::CustomField, hook_resolve_id_skipped::HookResolveIdSkipped},
};
use rolldown_common::{
ImportKind, ModuleDefFormat, PackageJson, ResolvedId, is_existing_node_builtin_modules,
};
use rolldown_resolver::{ResolveError, Resolver};
use std::{path::Path, sync::Arc};
use sugar_path::SugarPath;
fn is_http_url(s: &str) -> bool {
s.starts_with("http://") || s.starts_with("https://") || s.starts_with("//")
}
pub fn is_data_url(s: &str) -> bool {
s.trim_start().starts_with("data:")
}
pub fn infer_module_def_format(
path: &str,
package_json: Option<&Arc<PackageJson>>,
) -> ModuleDefFormat {
let fmt = ModuleDefFormat::from_path(path);
if !matches!(fmt, ModuleDefFormat::Unknown) {
return fmt;
}
let is_js_like_extension = Path::new(path)
.extension()
.is_some_and(|ext| matches!(ext.to_str(), Some("js" | "jsx" | "ts" | "tsx")));
if is_js_like_extension {
if let Some(pkg) = package_json {
if let Some(type_field) = pkg.r#type() {
return match type_field {
"module" => ModuleDefFormat::EsmPackageJson,
"commonjs" => ModuleDefFormat::CjsPackageJson,
_ => ModuleDefFormat::Unknown,
};
}
}
}
ModuleDefFormat::Unknown
}
#[expect(clippy::too_many_arguments)]
pub async fn resolve_id_with_plugins(
resolver: &Resolver,
plugin_driver: &PluginDriver,
specifier: &str,
importer: Option<&str>,
is_entry: bool,
import_kind: ImportKind,
skipped_resolve_calls: Option<Vec<Arc<HookResolveIdSkipped>>>,
custom: Arc<CustomField>,
is_user_defined_entry: bool,
) -> anyhow::Result<Result<ResolvedId, ResolveError>> {
if matches!(import_kind, ImportKind::DynamicImport) {
if let Some(r) = plugin_driver
.resolve_dynamic_import(
&HookResolveIdArgs {
importer: importer.map(std::convert::AsRef::as_ref),
specifier,
is_entry,
kind: import_kind,
custom: Arc::clone(&custom),
},
skipped_resolve_calls.as_ref(),
)
.await?
{
let package_json = r
.package_json_path
.as_ref()
.map(|p| resolver.try_get_package_json_or_create(p.as_path()))
.transpose()?;
return Ok(Ok(ResolvedId {
module_def_format: infer_module_def_format(r.id.as_str(), package_json.as_ref()),
id: r.id,
external: r.external.unwrap_or_default(),
normalize_external_id: r.normalize_external_id,
side_effects: r.side_effects,
package_json,
..Default::default()
}));
}
}
if let Some(r) = plugin_driver
.resolve_id(
&HookResolveIdArgs {
specifier,
importer,
is_entry,
kind: import_kind,
custom: Arc::clone(&custom),
},
skipped_resolve_calls.as_ref(),
)
.await?
{
let package_json = r
.package_json_path
.as_ref()
.map(|p| resolver.try_get_package_json_or_create(p.as_path()))
.transpose()?;
return Ok(Ok(ResolvedId {
module_def_format: infer_module_def_format(r.id.as_str(), package_json.as_ref()),
id: r.id,
external: r.external.unwrap_or_default(),
normalize_external_id: r.normalize_external_id,
side_effects: r.side_effects,
package_json,
..Default::default()
}));
}
if is_http_url(specifier) || is_data_url(specifier) {
return Ok(Ok(ResolvedId {
id: specifier.into(),
external: true.into(),
..Default::default()
}));
}
Ok(resolve_id(resolver, specifier, importer, import_kind, is_user_defined_entry))
}
fn resolve_id(
resolver: &Resolver,
specifier: &str,
importer: Option<&str>,
import_kind: ImportKind,
is_user_defined_entry: bool,
) -> Result<ResolvedId, ResolveError> {
let resolved =
resolver.resolve(importer.map(Path::new), specifier, import_kind, is_user_defined_entry);
match resolved {
Ok(resolved) => Ok(ResolvedId::from(resolved)),
Err(err) => match err {
ResolveError::Builtin { resolved, is_runtime_module } => Ok(ResolvedId {
is_external_without_side_effects: is_existing_node_builtin_modules(&resolved),
id: if resolved.starts_with("node:") && !is_runtime_module {
resolved[5..].into()
} else {
resolved.into()
},
external: true.into(),
..Default::default()
}),
ResolveError::Ignored(p) => Ok(ResolvedId {
id: p.to_str().expect("Should be valid utf8").into(),
ignored: true,
..Default::default()
}),
_ => Err(err),
},
}
}