use std::{
fmt::Formatter,
ptr::NonNull,
sync::{Arc, LazyLock},
};
use derive_more::Debug;
use futures::future::BoxFuture;
use napi::{
Either,
bindgen_prelude::{Buffer, Either3, FnArgs},
};
use napi_derive::napi;
use regex::Regex;
use rspack_core::{
AssetGeneratorDataUrl, AssetGeneratorDataUrlFnCtx, AssetGeneratorDataUrlOptions,
AssetGeneratorOptions, AssetInlineGeneratorOptions, AssetParserDataUrl,
AssetParserDataUrlOptions, AssetParserOptions, AssetResourceGeneratorOptions,
CssAutoGeneratorOptions, CssAutoParserOptions, CssGeneratorOptions, CssModuleGeneratorOptions,
CssModuleParserOptions, CssParserOptions, DescriptionData, DynamicImportFetchPriority,
DynamicImportMode, ExportPresenceMode, FuncUseCtx, GeneratorOptions, GeneratorOptionsMap,
JavascriptParserCommonjsExportsOption, JavascriptParserCommonjsOptions, JavascriptParserOptions,
JavascriptParserOrder, JavascriptParserUrl, JsonGeneratorOptions, JsonParserOptions,
ModuleNoParseRule, ModuleNoParseRules, ModuleNoParseTestFn, ModuleOptions, ModuleRule,
ModuleRuleEffect, ModuleRuleEnforce, ModuleRuleUse, ModuleRuleUseLoader, OverrideStrict,
ParseOption, ParserOptions, ParserOptionsMap, TypeReexportPresenceMode, UnsafeCachePredicate,
};
use rspack_error::error;
use rspack_napi::threadsafe_function::ThreadsafeFunction;
use rspack_regex::RspackRegex;
use rustc_hash::FxHashMap as HashMap;
use crate::{filename::JsFilename, module::ModuleObject, options::raw_resolve::RawResolveOptions};
#[napi(object)]
#[derive(Debug)]
pub struct RawModuleRuleUse {
pub loader: String,
pub options: Option<String>,
}
#[rspack_napi_macros::tagged_union]
pub enum RawRuleSetCondition {
string(String),
#[napi(ts_type = "RegExp")]
regexp(RspackRegex),
logical(Vec<RawRuleSetLogicalConditions>),
array(Vec<RawRuleSetCondition>),
#[napi(ts_type = r#"(value: string) => boolean"#)]
func(ThreadsafeFunction<serde_json::Value, bool>),
}
impl std::fmt::Debug for RawRuleSetCondition {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
RawRuleSetCondition::string(s) => write!(f, "RawRuleSetCondition::string({s:?})"),
RawRuleSetCondition::regexp(r) => write!(f, "RawRuleSetCondition::regexp({r:?})"),
RawRuleSetCondition::logical(l) => write!(f, "RawRuleSetCondition::logical({l:?})"),
RawRuleSetCondition::array(a) => write!(f, "RawRuleSetCondition::array({a:?})"),
RawRuleSetCondition::func(_) => write!(f, "RawRuleSetCondition::func(...)"),
}
}
}
#[derive(Debug, Default)]
#[napi(object, object_to_js = false)]
pub struct RawRuleSetLogicalConditions {
pub and: Option<Vec<RawRuleSetCondition>>,
pub or: Option<Vec<RawRuleSetCondition>>,
pub not: Option<RawRuleSetCondition>,
}
impl TryFrom<RawRuleSetLogicalConditions> for rspack_core::RuleSetLogicalConditions {
type Error = rspack_error::Error;
fn try_from(value: RawRuleSetLogicalConditions) -> rspack_error::Result<Self> {
Ok(Self {
and: value
.and
.map(|i| {
i.into_iter()
.map(TryFrom::try_from)
.collect::<rspack_error::Result<Vec<_>>>()
})
.transpose()?,
or: value
.or
.map(|i| {
i.into_iter()
.map(TryFrom::try_from)
.collect::<rspack_error::Result<Vec<_>>>()
})
.transpose()?,
not: value.not.map(TryFrom::try_from).transpose()?,
})
}
}
impl TryFrom<RawRuleSetCondition> for rspack_core::RuleSetCondition {
type Error = rspack_error::Error;
fn try_from(x: RawRuleSetCondition) -> rspack_error::Result<Self> {
let result = match x {
RawRuleSetCondition::string(s) => Self::String(s),
RawRuleSetCondition::regexp(r) => Self::Regexp(r),
RawRuleSetCondition::logical(mut l) => {
let l = l.get_mut(0).ok_or_else(|| {
error!("TODO: use Box after https://github.com/napi-rs/napi-rs/issues/1500 landed")
})?;
let l = std::mem::take(l);
Self::Logical(Box::new(rspack_core::RuleSetLogicalConditions::try_from(
l,
)?))
}
RawRuleSetCondition::array(a) => Self::Array(
a.into_iter()
.map(|i| i.try_into())
.collect::<rspack_error::Result<Vec<_>>>()?,
),
RawRuleSetCondition::func(f) => Self::Func(Box::new(move |data| {
let data = data.to_value();
let f = f.clone();
Box::pin(async move { f.call_with_sync(data).await })
})),
};
Ok(result)
}
}
impl TryFrom<RawRuleSetCondition> for rspack_core::RuleSetConditionWithEmpty {
type Error = rspack_error::Error;
fn try_from(x: RawRuleSetCondition) -> rspack_error::Result<Self> {
Ok(Self::new(x.try_into()?))
}
}
type ThreadsafeUse = ThreadsafeFunction<RawFuncUseCtx, Vec<RawModuleRuleUse>>;
#[derive(Debug, Default)]
#[napi(object, object_to_js = false)]
pub struct RawModuleRule {
pub rspack_resource: Option<RawRuleSetCondition>,
pub test: Option<RawRuleSetCondition>,
pub include: Option<RawRuleSetCondition>,
pub exclude: Option<RawRuleSetCondition>,
pub resource: Option<RawRuleSetCondition>,
pub resource_query: Option<RawRuleSetCondition>,
pub resource_fragment: Option<RawRuleSetCondition>,
pub description_data: Option<HashMap<String, RawRuleSetCondition>>,
pub with: Option<HashMap<String, RawRuleSetCondition>>,
pub side_effects: Option<bool>,
#[napi(ts_type = "RawModuleRuleUse[] | ((arg: RawFuncUseCtx) => RawModuleRuleUse[])")]
pub r#use: Option<Either<Vec<RawModuleRuleUse>, ThreadsafeUse>>,
pub r#type: Option<String>,
pub layer: Option<String>,
pub parser: Option<RawParserOptions>,
pub generator: Option<RawGeneratorOptions>,
pub resolve: Option<RawResolveOptions>,
pub issuer: Option<RawRuleSetCondition>,
pub issuer_layer: Option<RawRuleSetCondition>,
pub dependency: Option<RawRuleSetCondition>,
pub scheme: Option<RawRuleSetCondition>,
pub mimetype: Option<RawRuleSetCondition>,
pub one_of: Option<Vec<RawModuleRule>>,
pub rules: Option<Vec<RawModuleRule>>,
#[napi(ts_type = "'pre' | 'post'")]
pub enforce: Option<String>,
pub extract_source_map: Option<bool>,
}
#[derive(Debug, Default)]
#[napi(object, object_to_js = false)]
pub struct RawParserOptions {
#[napi(
ts_type = r#""asset" | "css" | "css/auto" | "css/module" | "javascript" | "javascript/auto" | "javascript/dynamic" | "javascript/esm" | "json""#
)]
pub r#type: String,
pub asset: Option<RawAssetParserOptions>,
pub css: Option<RawCssParserOptions>,
pub css_auto: Option<RawCssAutoParserOptions>,
pub css_module: Option<RawCssModuleParserOptions>,
pub javascript: Option<RawJavascriptParserOptions>,
pub json: Option<RawJsonParserOptions>,
}
impl From<RawParserOptions> for ParserOptions {
fn from(value: RawParserOptions) -> Self {
match value.r#type.as_str() {
"asset" => Self::Asset(
value
.asset
.expect("should have an \"asset\" when RawParserOptions.type is \"asset\"")
.into(),
),
"javascript" => Self::Javascript(
value
.javascript
.expect("should have an \"javascript\" when RawParserOptions.type is \"javascript\"")
.into(),
),
"javascript/auto" => Self::JavascriptAuto(
value
.javascript
.expect("should have an \"javascript\" when RawParserOptions.type is \"javascript/auto\"")
.into(),
),
"javascript/dynamic" => Self::JavascriptDynamic(
value
.javascript
.expect(
"should have an \"javascript\" when RawParserOptions.type is \"javascript/dynamic\"",
)
.into(),
),
"javascript/esm" => Self::JavascriptEsm(
value
.javascript
.expect("should have an \"javascript\" when RawParserOptions.type is \"javascript/esm\"")
.into(),
),
"css" => Self::Css(
value
.css
.expect("should have an \"css\" when RawParserOptions.type is \"css\"")
.into(),
),
"css/auto" => Self::CssAuto(
value
.css_auto
.expect("should have an \"css_auto\" when RawParserOptions.type is \"css/auto\"")
.into(),
),
"css/module" => Self::CssModule(
value
.css_module
.expect("should have an \"css_module\" when RawParserOptions.type is \"css/module\"")
.into(),
),
"json" => Self::Json(
value
.json
.expect("should have an \"json\" when RawParserOptions.type is \"json\"")
.into(),
),
_ => panic!(
"Failed to resolve the RawParserOptions.type {}.",
value.r#type
),
}
}
}
#[derive(Debug, Default)]
#[napi(object)]
pub struct RawJavascriptParserOptions {
pub dynamic_import_mode: Option<String>,
pub dynamic_import_preload: Option<String>,
pub dynamic_import_prefetch: Option<String>,
pub dynamic_import_fetch_priority: Option<String>,
pub url: Option<String>,
pub expr_context_critical: Option<bool>,
pub unknown_context_critical: Option<bool>,
pub wrapped_context_critical: Option<bool>,
#[napi(ts_type = "RegExp")]
pub wrapped_context_reg_exp: Option<RspackRegex>,
pub exports_presence: Option<String>,
pub import_exports_presence: Option<String>,
pub reexport_exports_presence: Option<String>,
pub strict_export_presence: Option<bool>,
pub worker: Option<Vec<String>>,
pub override_strict: Option<String>,
pub import_meta: Option<bool>,
pub require_alias: Option<bool>,
pub require_as_expression: Option<bool>,
pub require_dynamic: Option<bool>,
pub require_resolve: Option<bool>,
#[napi(ts_type = "boolean | { exports?: boolean | 'skipInEsm' }")]
pub commonjs: Option<Either<bool, RawJavascriptParserCommonjsOptions>>,
pub import_dynamic: Option<bool>,
pub commonjs_magic_comments: Option<bool>,
pub type_reexports_presence: Option<String>,
pub jsx: Option<bool>,
pub defer_import: Option<bool>,
}
#[napi(object)]
#[derive(Debug)]
pub struct RawJavascriptParserCommonjsOptions {
#[napi(ts_type = "boolean | 'skipInEsm'")]
pub exports: Option<Either<bool, RawJavascriptParserCommonjsExports>>,
}
#[napi(string_enum)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum RawJavascriptParserCommonjsExports {
#[napi(value = "skipInEsm")]
SkipInEsm,
}
impl From<RawJavascriptParserOptions> for JavascriptParserOptions {
fn from(value: RawJavascriptParserOptions) -> Self {
Self {
dynamic_import_mode: value
.dynamic_import_mode
.map(|v| DynamicImportMode::from(v.as_str())),
dynamic_import_preload: value
.dynamic_import_preload
.map(|v| JavascriptParserOrder::from(v.as_str())),
dynamic_import_prefetch: value
.dynamic_import_prefetch
.map(|v| JavascriptParserOrder::from(v.as_str())),
dynamic_import_fetch_priority: value
.dynamic_import_fetch_priority
.map(|x| DynamicImportFetchPriority::from(x.as_str())),
url: value.url.map(|v| JavascriptParserUrl::from(v.as_str())),
expr_context_critical: value.expr_context_critical,
unknown_context_critical: value.unknown_context_critical,
wrapped_context_reg_exp: value.wrapped_context_reg_exp,
wrapped_context_critical: value.wrapped_context_critical,
exports_presence: value
.exports_presence
.map(|e| ExportPresenceMode::from(e.as_str())),
import_exports_presence: value
.import_exports_presence
.map(|e| ExportPresenceMode::from(e.as_str())),
reexport_exports_presence: value
.reexport_exports_presence
.map(|e| ExportPresenceMode::from(e.as_str())),
strict_export_presence: value.strict_export_presence,
type_reexports_presence: value
.type_reexports_presence
.map(|e| TypeReexportPresenceMode::from(e.as_str())),
worker: value.worker,
override_strict: value
.override_strict
.map(|e| OverrideStrict::from(e.as_str())),
import_meta: value.import_meta,
require_alias: value.require_alias,
require_as_expression: value.require_as_expression,
require_dynamic: value.require_dynamic,
require_resolve: value.require_resolve,
commonjs: value.commonjs.map(|commonjs| match commonjs {
Either::A(flag) => JavascriptParserCommonjsOptions {
exports: JavascriptParserCommonjsExportsOption::from(flag),
},
Either::B(options) => {
let exports = options
.exports
.map(|exports| match exports {
Either::A(flag) => JavascriptParserCommonjsExportsOption::from(flag),
Either::B(RawJavascriptParserCommonjsExports::SkipInEsm) => {
JavascriptParserCommonjsExportsOption::SkipInEsm
}
})
.unwrap_or(JavascriptParserCommonjsExportsOption::Enable);
JavascriptParserCommonjsOptions { exports }
}
}),
import_dynamic: value.import_dynamic,
commonjs_magic_comments: value.commonjs_magic_comments,
jsx: value.jsx,
defer_import: value.defer_import,
}
}
}
#[derive(Debug, Default)]
#[napi(object)]
pub struct RawJsonGeneratorOptions {
#[napi(js_name = "JSONParse")]
pub json_parse: Option<bool>,
}
impl From<RawJsonGeneratorOptions> for JsonGeneratorOptions {
fn from(value: RawJsonGeneratorOptions) -> Self {
Self {
json_parse: value.json_parse,
}
}
}
#[derive(Debug, Default)]
#[napi(object)]
pub struct RawAssetParserOptions {
pub data_url_condition: Option<RawAssetParserDataUrl>,
}
impl From<RawAssetParserOptions> for AssetParserOptions {
fn from(value: RawAssetParserOptions) -> Self {
Self {
data_url_condition: value.data_url_condition.map(|i| i.into()),
}
}
}
#[derive(Debug, Default)]
#[napi(object)]
pub struct RawAssetParserDataUrl {
#[napi(ts_type = r#""options""#)]
pub r#type: String,
pub options: Option<RawAssetParserDataUrlOptions>,
}
impl From<RawAssetParserDataUrl> for AssetParserDataUrl {
fn from(value: RawAssetParserDataUrl) -> Self {
match value.r#type.as_str() {
"options" => Self::Options(
value
.options
.expect("should have an \"options\" when RawAssetParserDataUrl.type is \"options\"")
.into(),
),
_ => panic!(
"Failed to resolve the RawAssetParserDataUrl.type {}. Expected type is `options`.",
value.r#type
),
}
}
}
#[derive(Debug, Clone, Default)]
#[napi(object)]
pub struct RawAssetParserDataUrlOptions {
pub max_size: Option<f64>,
}
impl From<RawAssetParserDataUrlOptions> for AssetParserDataUrlOptions {
fn from(value: RawAssetParserDataUrlOptions) -> Self {
Self {
max_size: value.max_size,
}
}
}
#[derive(Debug, Default)]
#[napi(object)]
pub struct RawCssParserOptions {
pub named_exports: Option<bool>,
pub url: Option<bool>,
}
impl From<RawCssParserOptions> for CssParserOptions {
fn from(value: RawCssParserOptions) -> Self {
Self {
named_exports: value.named_exports,
url: value.url,
}
}
}
#[derive(Debug, Default)]
#[napi(object)]
pub struct RawCssAutoParserOptions {
pub named_exports: Option<bool>,
pub url: Option<bool>,
}
impl From<RawCssAutoParserOptions> for CssAutoParserOptions {
fn from(value: RawCssAutoParserOptions) -> Self {
Self {
named_exports: value.named_exports,
url: value.url,
}
}
}
#[derive(Debug, Default)]
#[napi(object)]
pub struct RawCssModuleParserOptions {
pub named_exports: Option<bool>,
pub url: Option<bool>,
}
impl From<RawCssModuleParserOptions> for CssModuleParserOptions {
fn from(value: RawCssModuleParserOptions) -> Self {
Self {
named_exports: value.named_exports,
url: value.url,
}
}
}
#[derive(Debug, Default)]
#[napi(object, object_to_js = false)]
pub struct RawJsonParserOptions {
pub exports_depth: Option<u32>,
#[napi(ts_type = "(source: string) => string")]
pub parse: Option<ThreadsafeFunction<String, String>>,
}
impl From<RawJsonParserOptions> for JsonParserOptions {
fn from(value: RawJsonParserOptions) -> Self {
let parse = match value.parse {
Some(f) => ParseOption::Func(Arc::new(move |s: String| {
let f = f.clone();
Box::pin(async move { f.call_with_sync(s).await })
})),
_ => ParseOption::None,
};
Self {
exports_depth: value.exports_depth,
parse,
}
}
}
#[derive(Debug, Default)]
#[napi(object, object_to_js = false)]
pub struct RawGeneratorOptions {
#[napi(
ts_type = r#""asset" | "asset/inline" | "asset/resource" | "css" | "css/auto" | "css/module" | "json""#
)]
pub r#type: String,
pub asset: Option<RawAssetGeneratorOptions>,
pub asset_inline: Option<RawAssetInlineGeneratorOptions>,
pub asset_resource: Option<RawAssetResourceGeneratorOptions>,
pub css: Option<RawCssGeneratorOptions>,
pub css_auto: Option<RawCssAutoGeneratorOptions>,
pub css_module: Option<RawCssModuleGeneratorOptions>,
pub json: Option<RawJsonGeneratorOptions>,
}
impl From<RawGeneratorOptions> for GeneratorOptions {
fn from(value: RawGeneratorOptions) -> Self {
match value.r#type.as_str() {
"asset" => Self::Asset(
value
.asset
.expect("should have an \"asset\" when RawGeneratorOptions.type is \"asset\"")
.into(),
),
"asset/inline" => Self::AssetInline(
value
.asset_inline
.expect(
"should have an \"asset_inline\" when RawGeneratorOptions.type is \"asset/inline\"",
)
.into(),
),
"asset/resource" => Self::AssetResource(
value
.asset_resource
.expect(
"should have an \"asset_resource\" when RawGeneratorOptions.type is \"asset/resource\"",
)
.into(),
),
"css" => Self::Css(
value
.css
.expect("should have an \"css\" when RawGeneratorOptions.type is \"css\"")
.into(),
),
"css/auto" => Self::CssAuto(
value
.css_auto
.expect("should have an \"css_auto\" when RawGeneratorOptions.type is \"css/auto\"")
.into(),
),
"css/module" => Self::CssModule(
value
.css_module
.expect("should have an \"css_module\" when RawGeneratorOptions.type is \"css/module\"")
.into(),
),
"json" => Self::Json(
value
.json
.expect("should have an \"json\" when RawGeneratorOptions.type is \"json\"")
.into(),
),
_ => panic!(
r#"Failed to resolve the RawGeneratorOptions.type {}."#,
value.r#type
),
}
}
}
#[derive(Default, Debug)]
#[napi(object, object_to_js = false)]
pub struct RawAssetGeneratorOptions {
pub emit: Option<bool>,
pub filename: Option<JsFilename>,
pub output_path: Option<JsFilename>,
#[napi(ts_type = "\"auto\" | JsFilename")]
pub public_path: Option<JsFilename>,
#[debug(skip)]
#[napi(
ts_type = "RawAssetGeneratorDataUrlOptions | ((source: Buffer, context: RawAssetGeneratorDataUrlFnCtx) => string)"
)]
pub data_url: Option<RawAssetGeneratorDataUrl>,
#[napi(ts_type = r#""url" | "preserve""#)]
pub import_mode: Option<String>,
pub binary: Option<bool>,
}
impl From<RawAssetGeneratorOptions> for AssetGeneratorOptions {
fn from(value: RawAssetGeneratorOptions) -> Self {
Self {
emit: value.emit,
filename: value.filename.map(|i| i.into()),
output_path: value.output_path.map(|i| i.into()),
public_path: value.public_path.map(|i| i.into()),
data_url: value
.data_url
.map(|i| RawAssetGeneratorDataUrlWrapper(i).into()),
import_mode: value.import_mode.map(|n| n.into()),
binary: value.binary,
}
}
}
#[derive(Default, Debug)]
#[napi(object, object_to_js = false)]
pub struct RawAssetInlineGeneratorOptions {
#[debug(skip)]
#[napi(
ts_type = "RawAssetGeneratorDataUrlOptions | ((source: Buffer, context: RawAssetGeneratorDataUrlFnCtx) => string)"
)]
pub data_url: Option<RawAssetGeneratorDataUrl>,
pub binary: Option<bool>,
}
impl From<RawAssetInlineGeneratorOptions> for AssetInlineGeneratorOptions {
fn from(value: RawAssetInlineGeneratorOptions) -> Self {
Self {
data_url: value
.data_url
.map(|i| RawAssetGeneratorDataUrlWrapper(i).into()),
binary: value.binary,
}
}
}
#[derive(Debug, Default)]
#[napi(object, object_to_js = false)]
pub struct RawAssetResourceGeneratorOptions {
pub emit: Option<bool>,
pub filename: Option<JsFilename>,
pub output_path: Option<JsFilename>,
#[napi(ts_type = "\"auto\" | JsFilename")]
pub public_path: Option<JsFilename>,
#[napi(ts_type = r#""url" | "preserve""#)]
pub import_mode: Option<String>,
pub binary: Option<bool>,
}
impl From<RawAssetResourceGeneratorOptions> for AssetResourceGeneratorOptions {
fn from(value: RawAssetResourceGeneratorOptions) -> Self {
Self {
emit: value.emit,
filename: value.filename.map(|i| i.into()),
output_path: value.output_path.map(|i| i.into()),
public_path: value.public_path.map(|i| i.into()),
import_mode: value.import_mode.map(|i| i.into()),
binary: value.binary,
}
}
}
type RawAssetGeneratorDataUrl = Either<
RawAssetGeneratorDataUrlOptions,
ThreadsafeFunction<FnArgs<(Buffer, RawAssetGeneratorDataUrlFnCtx)>, String>,
>;
struct RawAssetGeneratorDataUrlWrapper(RawAssetGeneratorDataUrl);
#[napi(object)]
pub struct RawAssetGeneratorDataUrlFnCtx {
pub filename: String,
#[napi(ts_type = "Module")]
pub module: ModuleObject,
}
impl From<AssetGeneratorDataUrlFnCtx<'_>> for RawAssetGeneratorDataUrlFnCtx {
fn from(value: AssetGeneratorDataUrlFnCtx) -> Self {
#[allow(clippy::unwrap_used)]
Self {
filename: value.filename,
module: ModuleObject::with_ptr(
NonNull::new(
value.module as *const dyn rspack_core::Module as *mut dyn rspack_core::Module,
)
.unwrap(),
value.compilation.compiler_id(),
),
}
}
}
impl From<RawAssetGeneratorDataUrlWrapper> for AssetGeneratorDataUrl {
fn from(value: RawAssetGeneratorDataUrlWrapper) -> Self {
match value.0 {
Either::A(a) => Self::Options(a.into()),
Either::B(b) => Self::Func(Arc::new(move |source, ctx| {
let b = b.clone();
let source = source.into();
let ctx = ctx.into();
Box::pin(async move { b.call_with_sync((source, ctx).into()).await })
})),
}
}
}
#[derive(Debug, Default)]
#[napi(object)]
pub struct RawAssetGeneratorDataUrlOptions {
#[napi(ts_type = r#""base64" | "false" | undefined"#)]
pub encoding: Option<String>,
pub mimetype: Option<String>,
}
impl From<RawAssetGeneratorDataUrlOptions> for AssetGeneratorDataUrlOptions {
fn from(value: RawAssetGeneratorDataUrlOptions) -> Self {
Self {
encoding: value.encoding.map(|i| i.into()),
mimetype: value.mimetype,
}
}
}
#[derive(Debug, Default)]
#[napi(object)]
pub struct RawCssGeneratorOptions {
pub exports_only: Option<bool>,
pub es_module: Option<bool>,
}
impl From<RawCssGeneratorOptions> for CssGeneratorOptions {
fn from(value: RawCssGeneratorOptions) -> Self {
Self {
exports_only: value.exports_only,
es_module: value.es_module,
}
}
}
#[derive(Debug, Default)]
#[napi(object)]
pub struct RawCssAutoGeneratorOptions {
#[napi(ts_type = r#""as-is" | "camel-case" | "camel-case-only" | "dashes" | "dashes-only""#)]
pub exports_convention: Option<String>,
pub exports_only: Option<bool>,
pub local_ident_name: Option<String>,
pub es_module: Option<bool>,
}
impl From<RawCssAutoGeneratorOptions> for CssAutoGeneratorOptions {
fn from(value: RawCssAutoGeneratorOptions) -> Self {
Self {
exports_convention: value.exports_convention.map(|n| n.into()),
exports_only: value.exports_only,
local_ident_name: value.local_ident_name.map(|n| n.into()),
es_module: value.es_module,
}
}
}
#[derive(Debug, Default)]
#[napi(object)]
pub struct RawCssModuleGeneratorOptions {
#[napi(ts_type = r#""as-is" | "camel-case" | "camel-case-only" | "dashes" | "dashes-only""#)]
pub exports_convention: Option<String>,
pub exports_only: Option<bool>,
pub local_ident_name: Option<String>,
pub es_module: Option<bool>,
}
impl From<RawCssModuleGeneratorOptions> for CssModuleGeneratorOptions {
fn from(value: RawCssModuleGeneratorOptions) -> Self {
Self {
exports_convention: value.exports_convention.map(|n| n.into()),
exports_only: value.exports_only,
local_ident_name: value.local_ident_name.map(|n| n.into()),
es_module: value.es_module,
}
}
}
#[napi(object, object_to_js = false)]
#[derive(Debug)]
pub struct RawModuleOptions {
pub rules: Vec<RawModuleRule>,
pub parser: Option<HashMap<String, RawParserOptions>>,
pub generator: Option<HashMap<String, RawGeneratorOptions>>,
#[napi(
ts_type = "string | RegExp | ((request: string) => boolean) | (string | RegExp | ((request: string) => boolean))[]"
)]
#[debug(skip)]
pub no_parse: Option<RawModuleNoParseRules>,
#[napi(ts_type = "boolean | RegExp")]
pub unsafe_cache: Option<Either<bool, RspackRegex>>,
}
#[derive(Debug, Clone)]
#[napi(object)]
pub struct RawFuncUseCtx {
pub resource: Option<String>,
pub real_resource: Option<String>,
pub resource_query: String,
pub resource_fragment: String,
pub issuer: String,
pub issuer_layer: String,
}
impl From<FuncUseCtx> for RawFuncUseCtx {
fn from(value: FuncUseCtx) -> Self {
Self {
resource: value.resource,
real_resource: value.real_resource,
resource_query: value.resource_query.unwrap_or_default(),
resource_fragment: value.resource_fragment.unwrap_or_default(),
issuer: value.issuer.map(|s| s.to_string()).unwrap_or_default(),
issuer_layer: value.issuer_layer.unwrap_or_default(),
}
}
}
impl TryFrom<RawModuleRule> for ModuleRule {
type Error = rspack_error::Error;
fn try_from(value: RawModuleRule) -> rspack_error::Result<Self> {
let uses = value.r#use.map(|raw| match raw {
Either::A(array) => {
let uses = array
.into_iter()
.map(|rule_use| ModuleRuleUseLoader {
loader: rule_use.loader,
options: rule_use.options,
})
.collect::<Vec<_>>();
Ok::<ModuleRuleUse, rspack_error::Error>(ModuleRuleUse::Array(uses))
}
Either::B(tsfn) => Ok::<ModuleRuleUse, rspack_error::Error>(ModuleRuleUse::Func(Box::new(
move |ctx: FuncUseCtx| {
let tsfn = tsfn.clone();
Box::pin(async move {
tsfn.call_with_sync(ctx.into()).await.map(|uses| {
uses
.into_iter()
.map(|rule_use| ModuleRuleUseLoader {
loader: rule_use.loader,
options: rule_use.options,
})
.collect::<Vec<_>>()
})
})
},
))),
});
let module_type = value.r#type.map(|t| (&*t).into());
let one_of = value
.one_of
.map(|one_of| {
one_of
.into_iter()
.map(|raw| raw.try_into())
.collect::<rspack_error::Result<Vec<_>>>()
})
.transpose()?;
let rules = value
.rules
.map(|rule| {
rule
.into_iter()
.map(|raw| raw.try_into())
.collect::<rspack_error::Result<Vec<_>>>()
})
.transpose()?;
let description_data = value
.description_data
.map(|data| {
data
.into_iter()
.map(|(k, v)| Ok((k, v.try_into()?)))
.collect::<rspack_error::Result<DescriptionData>>()
})
.transpose()?;
let with = value
.with
.map(|data| {
data
.into_iter()
.map(|(k, v)| Ok((k, v.try_into()?)))
.collect::<rspack_error::Result<DescriptionData>>()
})
.transpose()?;
let enforce = value
.enforce
.map(|enforce| match &*enforce {
"pre" => Ok(ModuleRuleEnforce::Pre),
"post" => Ok(ModuleRuleEnforce::Post),
_ => Err(error!(
"Unsupported Rule.enforce type, supported: 'pre' | 'post' | undefined"
)),
})
.transpose()?
.unwrap_or_default();
Ok(ModuleRule {
rspack_resource: value
.rspack_resource
.map(|raw| raw.try_into())
.transpose()?,
test: value.test.map(|raw| raw.try_into()).transpose()?,
include: value.include.map(|raw| raw.try_into()).transpose()?,
exclude: value.exclude.map(|raw| raw.try_into()).transpose()?,
resource_query: value.resource_query.map(|raw| raw.try_into()).transpose()?,
resource_fragment: value
.resource_fragment
.map(|raw| raw.try_into())
.transpose()?,
resource: value.resource.map(|raw| raw.try_into()).transpose()?,
description_data,
with,
issuer: value.issuer.map(|raw| raw.try_into()).transpose()?,
issuer_layer: value.issuer_layer.map(|raw| raw.try_into()).transpose()?,
dependency: value.dependency.map(|raw| raw.try_into()).transpose()?,
scheme: value.scheme.map(|raw| raw.try_into()).transpose()?,
mimetype: value.mimetype.map(|raw| raw.try_into()).transpose()?,
one_of,
rules,
effect: ModuleRuleEffect {
r#use: uses.transpose()?.unwrap_or_default(),
r#type: module_type,
layer: value.layer,
parser: value.parser.map(|raw| raw.into()),
generator: value.generator.map(|raw| raw.into()),
resolve: value.resolve.map(|raw| raw.try_into()).transpose()?,
side_effects: value.side_effects,
enforce,
extract_source_map: value.extract_source_map,
},
extract_source_map: value.extract_source_map,
})
}
}
impl TryFrom<RawModuleOptions> for ModuleOptions {
type Error = rspack_error::Error;
fn try_from(value: RawModuleOptions) -> rspack_error::Result<Self> {
let rules = value
.rules
.into_iter()
.map(|rule| rule.try_into())
.collect::<rspack_error::Result<Vec<ModuleRule>>>()?;
let unsafe_cache: Option<UnsafeCachePredicate> =
value.unsafe_cache.and_then(|either| match either {
Either::A(true) => Some(Box::new(|module: &dyn rspack_core::Module| {
let name = module.name_for_condition();
Box::pin(async move { Ok(true) }) as BoxFuture<'static, rspack_error::Result<bool>>
}) as UnsafeCachePredicate),
Either::A(false) => None,
Either::B(regex) => {
let regex = Arc::from(regex);
Some(Box::new(move |module: &dyn rspack_core::Module| {
let name = module.name_for_condition();
let regex = regex.clone();
Box::pin(async move { Ok(name.is_some_and(|name| regex.test(name.as_ref()))) })
as BoxFuture<'static, rspack_error::Result<bool>>
}) as UnsafeCachePredicate)
}
});
Ok(ModuleOptions {
rules,
parser: value
.parser
.map(|x| {
x.into_iter()
.map(|(k, v)| Ok((k, v.into())))
.collect::<std::result::Result<ParserOptionsMap, rspack_error::Error>>()
})
.transpose()?,
generator: value
.generator
.map(|x| {
x.into_iter()
.map(|(k, v)| Ok((k, v.into())))
.collect::<std::result::Result<GeneratorOptionsMap, rspack_error::Error>>()
})
.transpose()?,
no_parse: value
.no_parse
.map(|x| RawModuleNoParseRulesWrapper(x).into()),
unsafe_cache,
})
}
}
type RawModuleNoParseRule = Either3<String, RspackRegex, ThreadsafeFunction<String, Option<bool>>>;
type RawModuleNoParseRules = Either<RawModuleNoParseRule, Vec<RawModuleNoParseRule>>;
struct RawModuleNoParseRuleWrapper(RawModuleNoParseRule);
struct RawModuleNoParseRulesWrapper(RawModuleNoParseRules);
fn js_func_to_no_parse_test_func(
v: ThreadsafeFunction<String, Option<bool>>,
) -> ModuleNoParseTestFn {
Box::new(move |s| {
let v = v.clone();
Box::pin(async move { v.call_with_sync(s).await.map(|v| v.unwrap_or_default()) })
})
}
impl From<RawModuleNoParseRuleWrapper> for ModuleNoParseRule {
fn from(x: RawModuleNoParseRuleWrapper) -> Self {
match x.0 {
Either3::A(v) => Self::AbsPathPrefix(v),
Either3::B(v) => Self::Regexp(v),
Either3::C(v) => Self::TestFn(js_func_to_no_parse_test_func(v)),
}
}
}
impl From<RawModuleNoParseRulesWrapper> for ModuleNoParseRules {
fn from(x: RawModuleNoParseRulesWrapper) -> Self {
match x.0 {
Either::A(v) => Self::Rule(RawModuleNoParseRuleWrapper(v).into()),
Either::B(v) => Self::Rules(
v.into_iter()
.map(|r| RawModuleNoParseRuleWrapper(r).into())
.collect(),
),
}
}
}