use std::{hash::Hash, sync::Arc};
use napi_derive::napi;
use rspack_core::rspack_sources::{
BoxSource, CachedSource, ConcatSource, MapOptions, OriginalSource, RawSource, ReplaceSource,
Source, SourceExt, SourceMap, SourceMapSource, WithoutOriginalOptions,
};
use rspack_napi::napi::bindgen_prelude::*;
#[napi(object)]
pub struct JsCompatSource<'s> {
pub source: Either<String, BufferSlice<'s>>,
pub map: Option<String>,
}
impl<'s> From<JsCompatSource<'s>> for BoxSource {
fn from(value: JsCompatSource<'s>) -> Self {
match value.source {
Either::A(string) => {
if let Some(map) = value.map {
match SourceMap::from_slice(map.as_ref()).ok() {
Some(source_map) => SourceMapSource::new(WithoutOriginalOptions {
value: string,
name: "inmemory://from js",
source_map,
})
.boxed(),
None => RawSource::from(string).boxed(),
}
} else {
RawSource::from(string).boxed()
}
}
Either::B(buffer) => RawSource::from(buffer.to_vec()).boxed(),
}
}
}
#[napi(object)]
#[derive(Clone)]
pub struct JsCompatSourceOwned {
pub source: Either<String, Buffer>,
pub map: Option<String>,
}
impl From<JsCompatSourceOwned> for BoxSource {
fn from(value: JsCompatSourceOwned) -> Self {
match value.source {
Either::A(string) => {
if let Some(map) = value.map {
match SourceMap::from_slice(map.as_ref()).ok() {
Some(source_map) => SourceMapSource::new(WithoutOriginalOptions {
value: string,
name: "inmemory://from js",
source_map,
})
.boxed(),
None => RawSource::from(string).boxed(),
}
} else {
RawSource::from(string).boxed()
}
}
Either::B(buffer) => RawSource::from(Vec::<u8>::from(buffer)).boxed(),
}
}
}
pub trait ToJsCompatSource {
fn to_js_compat_source(&self, env: &Env) -> Result<JsCompatSource>;
}
impl ToJsCompatSource for RawSource {
fn to_js_compat_source(&self, env: &Env) -> Result<JsCompatSource> {
Ok(JsCompatSource {
source: if self.is_buffer() {
Either::B(BufferSlice::from_data(env, self.buffer())?)
} else {
Either::A(self.source().to_string())
},
map: to_webpack_map(self)?,
})
}
}
impl<T: Source + Hash + PartialEq + Eq + 'static> ToJsCompatSource for ReplaceSource<T> {
fn to_js_compat_source(&self, env: &Env) -> Result<JsCompatSource> {
Ok(JsCompatSource {
source: Either::B(BufferSlice::from_data(env, self.source().as_bytes())?),
map: to_webpack_map(self)?,
})
}
}
impl<T: ToJsCompatSource> ToJsCompatSource for CachedSource<T> {
fn to_js_compat_source(&self, env: &Env) -> Result<JsCompatSource> {
self.original().to_js_compat_source(env)
}
}
impl ToJsCompatSource for Arc<dyn Source> {
fn to_js_compat_source(&self, env: &Env) -> Result<JsCompatSource> {
(**self).to_js_compat_source(env)
}
}
impl ToJsCompatSource for Box<dyn Source> {
fn to_js_compat_source(&self, env: &Env) -> Result<JsCompatSource> {
(**self).to_js_compat_source(env)
}
}
macro_rules! impl_default_to_compat_source {
($ident:ident) => {
impl ToJsCompatSource for $ident {
fn to_js_compat_source(&self, _env: &Env) -> Result<JsCompatSource> {
Ok(JsCompatSource {
source: Either::A(self.source().to_string()),
map: to_webpack_map(self)?,
})
}
}
};
}
impl_default_to_compat_source!(SourceMapSource);
impl_default_to_compat_source!(ConcatSource);
impl_default_to_compat_source!(OriginalSource);
impl ToJsCompatSource for dyn Source + '_ {
fn to_js_compat_source(&self, env: &Env) -> Result<JsCompatSource> {
if let Some(raw_source) = self.as_any().downcast_ref::<RawSource>() {
raw_source.to_js_compat_source(env)
} else if let Some(cached_source) = self.as_any().downcast_ref::<CachedSource<RawSource>>() {
cached_source.to_js_compat_source(env)
} else if let Some(cached_source) = self
.as_any()
.downcast_ref::<CachedSource<Box<dyn Source>>>()
{
cached_source.to_js_compat_source(env)
} else if let Some(cached_source) = self
.as_any()
.downcast_ref::<CachedSource<Arc<dyn Source>>>()
{
cached_source.to_js_compat_source(env)
} else if let Some(source) = self.as_any().downcast_ref::<Box<dyn Source>>() {
source.to_js_compat_source(env)
} else if let Some(source) = self.as_any().downcast_ref::<Arc<dyn Source>>() {
source.to_js_compat_source(env)
} else {
Ok(JsCompatSource {
source: Either::A(self.source().to_string()),
map: to_webpack_map(self)?,
})
}
}
}
pub trait ToJsCompatSourceOwned {
fn to_js_compat_source_owned(&self) -> Result<JsCompatSourceOwned>;
}
impl ToJsCompatSourceOwned for RawSource {
fn to_js_compat_source_owned(&self) -> Result<JsCompatSourceOwned> {
Ok(JsCompatSourceOwned {
source: if self.is_buffer() {
Either::B(self.buffer().to_vec().into())
} else {
Either::A(self.source().to_string())
},
map: to_webpack_map(self)?,
})
}
}
impl<T: Source + Hash + PartialEq + Eq + 'static> ToJsCompatSourceOwned for ReplaceSource<T> {
fn to_js_compat_source_owned(&self) -> Result<JsCompatSourceOwned> {
Ok(JsCompatSourceOwned {
source: Either::A(self.source().to_string()),
map: to_webpack_map(self)?,
})
}
}
impl<T: ToJsCompatSourceOwned> ToJsCompatSourceOwned for CachedSource<T> {
fn to_js_compat_source_owned(&self) -> Result<JsCompatSourceOwned> {
self.original().to_js_compat_source_owned()
}
}
impl ToJsCompatSourceOwned for Arc<dyn Source> {
fn to_js_compat_source_owned(&self) -> Result<JsCompatSourceOwned> {
(**self).to_js_compat_source_owned()
}
}
impl ToJsCompatSourceOwned for Box<dyn Source> {
fn to_js_compat_source_owned(&self) -> Result<JsCompatSourceOwned> {
(**self).to_js_compat_source_owned()
}
}
macro_rules! impl_default_to_compat_source {
($ident:ident) => {
impl ToJsCompatSourceOwned for $ident {
fn to_js_compat_source_owned(&self) -> Result<JsCompatSourceOwned> {
Ok(JsCompatSourceOwned {
source: Either::A(self.source().to_string()),
map: to_webpack_map(self)?,
})
}
}
};
}
impl_default_to_compat_source!(SourceMapSource);
impl_default_to_compat_source!(ConcatSource);
impl_default_to_compat_source!(OriginalSource);
impl ToJsCompatSourceOwned for dyn Source + '_ {
fn to_js_compat_source_owned(&self) -> Result<JsCompatSourceOwned> {
if let Some(raw_source) = self.as_any().downcast_ref::<RawSource>() {
raw_source.to_js_compat_source_owned()
} else if let Some(cached_source) = self.as_any().downcast_ref::<CachedSource<RawSource>>() {
cached_source.to_js_compat_source_owned()
} else if let Some(cached_source) = self
.as_any()
.downcast_ref::<CachedSource<Box<dyn Source>>>()
{
cached_source.to_js_compat_source_owned()
} else if let Some(cached_source) = self
.as_any()
.downcast_ref::<CachedSource<Arc<dyn Source>>>()
{
cached_source.to_js_compat_source_owned()
} else if let Some(source) = self.as_any().downcast_ref::<Box<dyn Source>>() {
source.to_js_compat_source_owned()
} else if let Some(source) = self.as_any().downcast_ref::<Arc<dyn Source>>() {
source.to_js_compat_source_owned()
} else {
Ok(JsCompatSourceOwned {
source: Either::A(self.source().to_string()),
map: to_webpack_map(self)?,
})
}
}
}
fn to_webpack_map(source: &dyn Source) -> Result<Option<String>> {
let map = source.map(&MapOptions::default());
map
.map(|m| m.to_json())
.transpose()
.map_err(|err| napi::Error::from_reason(err.to_string()))
}