use std::{borrow::Cow, path::Path};
use arcstr::ArcStr;
use rolldown_common::{ModuleType, ResolvedExternal};
use rolldown_plugin::{
HookRenderChunkArgs, HookRenderChunkReturn, HookResolveIdArgs, HookResolveIdOutput,
HookResolveIdReturn, HookUsage, Plugin, PluginContext, PluginHookMeta, PluginOrder,
};
use rolldown_plugin_utils::{emit_asset, rewrite_emitted_asset_references};
use rolldown_utils::url::clean_url;
use rustc_hash::FxHashSet;
const PREFIX: &str = "__ROLLDOWN_COPY_MODULE__#";
#[derive(Debug)]
pub struct CopyModulePlugin {
copy_extensions: FxHashSet<String>,
}
impl CopyModulePlugin {
pub fn new(module_types: &rustc_hash::FxHashMap<Cow<'static, str>, ModuleType>) -> Self {
let mut copy_extensions = FxHashSet::default();
for (ext, module_type) in module_types {
if matches!(module_type, ModuleType::Copy) {
let ext = ext.strip_prefix('.').unwrap_or(ext);
copy_extensions.insert(ext.to_string());
}
}
Self { copy_extensions }
}
}
impl Plugin for CopyModulePlugin {
fn name(&self) -> Cow<'static, str> {
Cow::Borrowed("builtin:copy-module")
}
fn register_hook_usage(&self) -> HookUsage {
HookUsage::ResolveId | HookUsage::RenderChunk
}
fn resolve_id_meta(&self) -> Option<PluginHookMeta> {
Some(PluginHookMeta { order: Some(PluginOrder::Pre) })
}
async fn resolve_id(
&self,
ctx: &PluginContext,
args: &HookResolveIdArgs<'_>,
) -> HookResolveIdReturn {
if self.copy_extensions.is_empty() {
return Ok(None);
}
if args.specifier.starts_with(PREFIX) {
return Ok(None);
}
let resolved = ctx.resolve(args.specifier, args.importer, None).await?;
let resolved_id = match resolved {
Ok(id) => id,
Err(_) => return Ok(None),
};
if resolved_id.external.is_external() {
return Ok(None);
}
let clean_id = clean_url(resolved_id.id.as_str());
let resolved_path = Path::new(clean_id);
let ext = match resolved_path.extension().and_then(|e| e.to_str()) {
Some(e) => e,
None => return Ok(None),
};
if !self.copy_extensions.contains(ext) {
return Ok(None);
}
let reference_id = emit_asset(ctx, clean_id, |e| {
anyhow::anyhow!("Failed to read copy module {}: {e}", resolved_id.id)
})
.await?;
ctx.add_watch_file(clean_id);
let placeholder_id: ArcStr = format!("{PREFIX}{reference_id}").into();
Ok(Some(HookResolveIdOutput {
id: placeholder_id,
external: Some(ResolvedExternal::Bool(true)),
..Default::default()
}))
}
fn render_chunk_meta(&self) -> Option<PluginHookMeta> {
Some(PluginHookMeta { order: Some(PluginOrder::Pre) })
}
async fn render_chunk(
&self,
ctx: &PluginContext,
args: &HookRenderChunkArgs<'_>,
) -> HookRenderChunkReturn {
Ok(rewrite_emitted_asset_references(ctx, args, PREFIX))
}
}