use std::{ptr::NonNull, sync::Arc};
use napi::bindgen_prelude::*;
use napi_derive::napi;
use rspack_core::{LoaderContext, Module, RunnerContext};
use rspack_error::ToStringResultToRspackResultExt;
use rspack_loader_runner::State as LoaderState;
use rspack_napi::threadsafe_js_value_ref::ThreadsafeJsValueRef;
use rustc_hash::FxHashMap as HashMap;
use crate::{error::RspackError, module::ModuleObject};
#[napi(object)]
#[derive(Hash)]
pub struct JsLoaderItem {
pub loader: String,
pub r#type: String,
pub data: serde_json::Value,
pub normal_executed: bool,
pub pitch_executed: bool,
pub no_pitch: bool,
}
impl From<&rspack_loader_runner::LoaderItem<RunnerContext>> for JsLoaderItem {
fn from(value: &rspack_loader_runner::LoaderItem<RunnerContext>) -> Self {
JsLoaderItem {
loader: value.request().to_string(),
r#type: value.r#type().to_string(),
data: value.data().clone(),
normal_executed: value.normal_executed(),
pitch_executed: value.pitch_executed(),
no_pitch: false,
}
}
}
impl<C> From<&Arc<dyn rspack_core::Loader<C>>> for JsLoaderItem
where
C: Send,
{
fn from(loader: &Arc<dyn rspack_core::Loader<C>>) -> Self {
let identifier = loader.identifier();
if let Some((r#type, ident)) = identifier.split_once('|') {
return Self {
loader: ident.to_string(),
data: serde_json::Value::Null,
r#type: r#type.to_string(),
pitch_executed: false,
normal_executed: false,
no_pitch: false,
};
}
Self {
loader: identifier.to_string(),
data: serde_json::Value::Null,
r#type: String::default(),
pitch_executed: false,
normal_executed: false,
no_pitch: false,
}
}
}
#[napi(string_enum)]
pub enum JsLoaderState {
Pitching,
Normal,
}
impl From<LoaderState> for JsLoaderState {
fn from(value: LoaderState) -> Self {
match value {
LoaderState::Init | LoaderState::ProcessResource | LoaderState::Finished => {
panic!("Unexpected loader runner state: {value:?}")
}
LoaderState::Pitching => JsLoaderState::Pitching,
LoaderState::Normal => JsLoaderState::Normal,
}
}
}
#[napi(object)]
pub struct JsLoaderContext {
pub resource: String,
#[napi(js_name = "_module", ts_type = "Module")]
pub module: ModuleObject,
#[napi(ts_type = "Readonly<boolean>")]
pub hot: bool,
pub content: Either<Null, Buffer>,
#[napi(ts_type = "any")]
pub additional_data: Option<ThreadsafeJsValueRef<Unknown<'static>>>,
#[napi(js_name = "__internal__parseMeta")]
pub parse_meta: HashMap<String, String>,
pub source_map: Option<Buffer>,
pub cacheable: bool,
pub file_dependencies: Vec<String>,
pub context_dependencies: Vec<String>,
pub missing_dependencies: Vec<String>,
pub build_dependencies: Vec<String>,
pub loader_items: Vec<JsLoaderItem>,
pub loader_index: i32,
#[napi(ts_type = "Readonly<JsLoaderState>")]
pub loader_state: JsLoaderState,
#[napi(js_name = "__internal__error")]
pub error: Option<RspackError>,
#[napi(js_name = "__internal__utf8Hint")]
pub utf8_hint: Option<bool>,
}
impl TryFrom<&mut LoaderContext<RunnerContext>> for JsLoaderContext {
type Error = rspack_error::Error;
fn try_from(
cx: &mut rspack_core::LoaderContext<RunnerContext>,
) -> std::result::Result<Self, Self::Error> {
let module = &cx.context.module;
#[allow(clippy::unwrap_used)]
Ok(JsLoaderContext {
resource: cx.resource_data.resource().to_owned(),
module: ModuleObject::with_ptr(
NonNull::new(module as *const dyn Module as *mut dyn Module).unwrap(),
cx.context.compiler_id,
),
hot: cx.hot,
content: match cx.content() {
Some(c) => Either::B(c.to_owned().into_bytes().into()),
None => Either::A(Null),
},
parse_meta: Default::default(),
additional_data: cx
.additional_data()
.and_then(|data| data.get::<ThreadsafeJsValueRef<Unknown>>())
.cloned(),
source_map: cx
.source_map()
.cloned()
.map(|v| v.to_json())
.transpose()
.to_rspack_result()?
.map(|v| v.into_bytes().into()),
cacheable: cx.cacheable,
file_dependencies: cx
.file_dependencies
.iter()
.map(|i| i.to_string_lossy().to_string())
.collect(),
context_dependencies: cx
.context_dependencies
.iter()
.map(|i| i.to_string_lossy().to_string())
.collect(),
missing_dependencies: cx
.missing_dependencies
.iter()
.map(|i| i.to_string_lossy().to_string())
.collect(),
build_dependencies: cx
.build_dependencies
.iter()
.map(|i| i.to_string_lossy().to_string())
.collect(),
loader_items: cx.loader_items.iter().map(Into::into).collect(),
loader_index: cx.loader_index,
loader_state: cx.state().into(),
error: None,
utf8_hint: None,
})
}
}