rspack_core/options/
module.rs

1use std::{
2  fmt,
3  ops::{Deref, DerefMut},
4  sync::Arc,
5};
6
7use async_recursion::async_recursion;
8use bitflags::bitflags;
9use derive_more::Debug;
10use futures::future::BoxFuture;
11use rspack_cacheable::{cacheable, with::Unsupported};
12use rspack_error::Result;
13use rspack_macros::MergeFrom;
14use rspack_regex::RspackRegex;
15use rspack_util::{MergeFrom, try_all, try_any};
16use rustc_hash::FxHashMap as HashMap;
17use tokio::sync::OnceCell;
18
19use crate::{Compilation, Filename, Module, ModuleType, PublicPath, Resolve};
20
21#[derive(Debug, Default)]
22pub struct ParserOptionsMap(HashMap<String, ParserOptions>);
23
24impl Deref for ParserOptionsMap {
25  type Target = HashMap<String, ParserOptions>;
26
27  fn deref(&self) -> &Self::Target {
28    &self.0
29  }
30}
31
32impl DerefMut for ParserOptionsMap {
33  fn deref_mut(&mut self) -> &mut Self::Target {
34    &mut self.0
35  }
36}
37
38impl FromIterator<(String, ParserOptions)> for ParserOptionsMap {
39  fn from_iter<I: IntoIterator<Item = (String, ParserOptions)>>(i: I) -> Self {
40    Self(HashMap::from_iter(i))
41  }
42}
43
44impl ParserOptionsMap {
45  pub fn get<'a>(&'a self, key: &'a str) -> Option<&'a ParserOptions> {
46    self.0.get(key)
47  }
48}
49
50#[cacheable]
51#[derive(Debug, Clone, MergeFrom)]
52pub enum ParserOptions {
53  Asset(AssetParserOptions),
54  Css(CssParserOptions),
55  CssAuto(CssAutoParserOptions),
56  CssModule(CssModuleParserOptions),
57  Javascript(JavascriptParserOptions),
58  JavascriptAuto(JavascriptParserOptions),
59  JavascriptEsm(JavascriptParserOptions),
60  JavascriptDynamic(JavascriptParserOptions),
61  Json(JsonParserOptions),
62  Unknown,
63}
64
65macro_rules! get_variant {
66  ($fn_name:ident, $variant:ident, $ret_ty:ident) => {
67    pub fn $fn_name(&self) -> Option<&$ret_ty> {
68      match self {
69        Self::$variant(value) => Some(value),
70        _ => None,
71      }
72    }
73  };
74}
75
76impl ParserOptions {
77  get_variant!(get_asset, Asset, AssetParserOptions);
78  get_variant!(get_css, Css, CssParserOptions);
79  get_variant!(get_css_auto, CssAuto, CssAutoParserOptions);
80  get_variant!(get_css_module, CssModule, CssModuleParserOptions);
81  get_variant!(get_javascript, Javascript, JavascriptParserOptions);
82  get_variant!(get_javascript_auto, JavascriptAuto, JavascriptParserOptions);
83  get_variant!(get_javascript_esm, JavascriptEsm, JavascriptParserOptions);
84  get_variant!(
85    get_javascript_dynamic,
86    JavascriptDynamic,
87    JavascriptParserOptions
88  );
89  get_variant!(get_json, Json, JsonParserOptions);
90}
91
92#[cacheable]
93#[derive(Debug, Clone, Copy, MergeFrom)]
94pub enum DynamicImportMode {
95  Lazy,
96  Weak,
97  Eager,
98  LazyOnce,
99}
100
101impl From<&str> for DynamicImportMode {
102  fn from(value: &str) -> Self {
103    match value {
104      "weak" => DynamicImportMode::Weak,
105      "eager" => DynamicImportMode::Eager,
106      "lazy" => DynamicImportMode::Lazy,
107      "lazy-once" => DynamicImportMode::LazyOnce,
108      _ => {
109        // TODO: warning
110        DynamicImportMode::Lazy
111      }
112    }
113  }
114}
115
116#[cacheable]
117#[derive(Debug, Clone, Copy, MergeFrom, PartialEq, Eq, Hash, PartialOrd, Ord)]
118pub enum DynamicImportFetchPriority {
119  Low,
120  High,
121  Auto,
122}
123
124impl From<&str> for DynamicImportFetchPriority {
125  fn from(value: &str) -> Self {
126    match value {
127      "low" => DynamicImportFetchPriority::Low,
128      "high" => DynamicImportFetchPriority::High,
129      "auto" => DynamicImportFetchPriority::Auto,
130      _ => DynamicImportFetchPriority::Auto,
131    }
132  }
133}
134
135impl fmt::Display for DynamicImportFetchPriority {
136  fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
137    match self {
138      DynamicImportFetchPriority::Low => write!(f, "low"),
139      DynamicImportFetchPriority::High => write!(f, "high"),
140      DynamicImportFetchPriority::Auto => write!(f, "auto"),
141    }
142  }
143}
144
145#[cacheable]
146#[derive(Debug, Clone, Copy, MergeFrom)]
147pub enum JavascriptParserUrl {
148  Enable,
149  Disable,
150  NewUrlRelative,
151  Relative,
152}
153
154impl From<&str> for JavascriptParserUrl {
155  fn from(value: &str) -> Self {
156    match value {
157      "false" => Self::Disable,
158      "relative" => Self::Relative,
159      "new-url-relative" => Self::NewUrlRelative,
160      _ => Self::Enable,
161    }
162  }
163}
164
165#[cacheable]
166#[derive(Debug, Clone, Copy, MergeFrom)]
167pub enum JavascriptParserOrder {
168  Disable,
169  Order(i32),
170}
171
172impl JavascriptParserOrder {
173  pub fn get_order(&self) -> Option<i32> {
174    match self {
175      Self::Disable => None,
176      Self::Order(o) => Some(*o),
177    }
178  }
179}
180
181impl From<&str> for JavascriptParserOrder {
182  fn from(value: &str) -> Self {
183    match value {
184      "false" => Self::Disable,
185      "true" => Self::Order(0),
186      _ => {
187        if let Ok(order) = value.parse::<i32>() {
188          Self::Order(order)
189        } else {
190          Self::Order(0)
191        }
192      }
193    }
194  }
195}
196
197#[cacheable]
198#[derive(Debug, Clone, Copy, MergeFrom, PartialEq, Eq)]
199pub enum JavascriptParserCommonjsExportsOption {
200  Enable,
201  Disable,
202  SkipInEsm,
203}
204
205impl From<bool> for JavascriptParserCommonjsExportsOption {
206  fn from(value: bool) -> Self {
207    if value { Self::Enable } else { Self::Disable }
208  }
209}
210
211#[cacheable]
212#[derive(Debug, Clone, MergeFrom)]
213pub struct JavascriptParserCommonjsOptions {
214  pub exports: JavascriptParserCommonjsExportsOption,
215}
216
217#[cacheable]
218#[derive(Debug, Clone, Copy, MergeFrom)]
219pub enum ExportPresenceMode {
220  None,
221  Warn,
222  Auto,
223  Error,
224}
225
226impl From<&str> for ExportPresenceMode {
227  fn from(value: &str) -> Self {
228    match value {
229      "false" => Self::None,
230      "warn" => Self::Warn,
231      "error" => Self::Error,
232      _ => Self::Auto,
233    }
234  }
235}
236
237impl ExportPresenceMode {
238  pub fn get_effective_export_presence(&self, module: &dyn Module) -> Option<bool> {
239    match self {
240      ExportPresenceMode::None => None,
241      ExportPresenceMode::Warn => Some(false),
242      ExportPresenceMode::Error => Some(true),
243      ExportPresenceMode::Auto => Some(module.build_meta().strict_esm_module),
244    }
245  }
246}
247
248#[cacheable]
249#[derive(Debug, Default, Clone, Copy, MergeFrom)]
250pub enum TypeReexportPresenceMode {
251  #[default]
252  NoTolerant,
253  Tolerant,
254  TolerantNoCheck,
255}
256
257impl From<&str> for TypeReexportPresenceMode {
258  fn from(value: &str) -> Self {
259    match value {
260      "tolerant" => Self::Tolerant,
261      "tolerant-no-check" => Self::TolerantNoCheck,
262      _ => Self::NoTolerant,
263    }
264  }
265}
266
267#[cacheable]
268#[derive(Debug, Clone, Copy, MergeFrom)]
269pub enum OverrideStrict {
270  Strict,
271  NoneStrict,
272}
273
274impl From<&str> for OverrideStrict {
275  fn from(value: &str) -> Self {
276    match value {
277      "strict" => Self::Strict,
278      "non-strict" => Self::NoneStrict,
279      _ => unreachable!("parser.overrideStrict should be 'strict' or 'non-strict'"),
280    }
281  }
282}
283
284#[cacheable]
285#[derive(Debug, Clone, MergeFrom, Default)]
286pub struct JavascriptParserOptions {
287  pub dynamic_import_mode: Option<DynamicImportMode>,
288  pub dynamic_import_preload: Option<JavascriptParserOrder>,
289  pub dynamic_import_prefetch: Option<JavascriptParserOrder>,
290  pub dynamic_import_fetch_priority: Option<DynamicImportFetchPriority>,
291  pub url: Option<JavascriptParserUrl>,
292  pub unknown_context_critical: Option<bool>,
293  pub expr_context_critical: Option<bool>,
294  pub wrapped_context_critical: Option<bool>,
295  pub wrapped_context_reg_exp: Option<RspackRegex>,
296  pub exports_presence: Option<ExportPresenceMode>,
297  pub import_exports_presence: Option<ExportPresenceMode>,
298  pub reexport_exports_presence: Option<ExportPresenceMode>,
299  pub strict_export_presence: Option<bool>,
300  pub type_reexports_presence: Option<TypeReexportPresenceMode>,
301  pub worker: Option<Vec<String>>,
302  pub override_strict: Option<OverrideStrict>,
303  pub import_meta: Option<bool>,
304  pub require_as_expression: Option<bool>,
305  pub require_dynamic: Option<bool>,
306  pub require_resolve: Option<bool>,
307  pub commonjs: Option<JavascriptParserCommonjsOptions>,
308  pub import_dynamic: Option<bool>,
309  pub commonjs_magic_comments: Option<bool>,
310  pub inline_const: Option<bool>,
311  pub jsx: Option<bool>,
312  pub defer_import: Option<bool>,
313}
314
315#[cacheable]
316#[derive(Debug, Clone, MergeFrom)]
317pub struct AssetParserOptions {
318  pub data_url_condition: Option<AssetParserDataUrl>,
319}
320
321#[cacheable]
322#[derive(Debug, Clone, MergeFrom)]
323pub enum AssetParserDataUrl {
324  Options(AssetParserDataUrlOptions),
325  // TODO: Function
326}
327
328#[cacheable]
329#[derive(Debug, Clone, MergeFrom)]
330pub struct AssetParserDataUrlOptions {
331  pub max_size: Option<f64>,
332}
333
334#[cacheable]
335#[derive(Debug, Clone, MergeFrom)]
336pub struct CssParserOptions {
337  pub named_exports: Option<bool>,
338  pub url: Option<bool>,
339}
340
341#[cacheable]
342#[derive(Debug, Clone, MergeFrom)]
343pub struct CssAutoParserOptions {
344  pub named_exports: Option<bool>,
345  pub url: Option<bool>,
346}
347
348impl From<CssParserOptions> for CssAutoParserOptions {
349  fn from(value: CssParserOptions) -> Self {
350    Self {
351      named_exports: value.named_exports,
352      url: value.url,
353    }
354  }
355}
356
357#[cacheable]
358#[derive(Debug, Clone, MergeFrom)]
359pub struct CssModuleParserOptions {
360  pub named_exports: Option<bool>,
361  pub url: Option<bool>,
362}
363
364impl From<CssParserOptions> for CssModuleParserOptions {
365  fn from(value: CssParserOptions) -> Self {
366    Self {
367      named_exports: value.named_exports,
368      url: value.url,
369    }
370  }
371}
372
373pub type JsonParseFn = Arc<dyn Fn(String) -> BoxFuture<'static, Result<String>> + Sync + Send>;
374
375#[cacheable]
376pub enum ParseOption {
377  Func(#[cacheable(with=Unsupported)] JsonParseFn),
378  None,
379}
380
381impl fmt::Debug for ParseOption {
382  fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
383    match self {
384      Self::Func(_) => write!(f, "ParseOption::Func(...)"),
385      _ => write!(f, "ParseOption::None"),
386    }
387  }
388}
389
390impl Clone for ParseOption {
391  fn clone(&self) -> Self {
392    match self {
393      Self::Func(f) => Self::Func(f.clone()),
394      Self::None => Self::None,
395    }
396  }
397}
398
399impl MergeFrom for ParseOption {
400  fn merge_from(self, other: &Self) -> Self {
401    other.clone()
402  }
403}
404
405#[cacheable]
406#[derive(Debug, Clone, MergeFrom)]
407pub struct JsonParserOptions {
408  pub exports_depth: Option<u32>,
409  pub parse: ParseOption,
410}
411
412#[derive(Debug, Default)]
413pub struct GeneratorOptionsMap(HashMap<String, GeneratorOptions>);
414
415impl Deref for GeneratorOptionsMap {
416  type Target = HashMap<String, GeneratorOptions>;
417
418  fn deref(&self) -> &Self::Target {
419    &self.0
420  }
421}
422
423impl DerefMut for GeneratorOptionsMap {
424  fn deref_mut(&mut self) -> &mut Self::Target {
425    &mut self.0
426  }
427}
428
429impl FromIterator<(String, GeneratorOptions)> for GeneratorOptionsMap {
430  fn from_iter<I: IntoIterator<Item = (String, GeneratorOptions)>>(i: I) -> Self {
431    Self(HashMap::from_iter(i))
432  }
433}
434
435impl GeneratorOptionsMap {
436  pub fn get(&self, key: &str) -> Option<&GeneratorOptions> {
437    self.0.get(key)
438  }
439}
440
441#[cacheable]
442#[derive(Debug, Clone, MergeFrom)]
443pub enum GeneratorOptions {
444  Asset(AssetGeneratorOptions),
445  AssetInline(AssetInlineGeneratorOptions),
446  AssetResource(AssetResourceGeneratorOptions),
447  Css(CssGeneratorOptions),
448  CssAuto(CssAutoGeneratorOptions),
449  CssModule(CssModuleGeneratorOptions),
450  Json(JsonGeneratorOptions),
451  Unknown,
452}
453
454impl GeneratorOptions {
455  get_variant!(get_asset, Asset, AssetGeneratorOptions);
456  get_variant!(get_asset_inline, AssetInline, AssetInlineGeneratorOptions);
457  get_variant!(
458    get_asset_resource,
459    AssetResource,
460    AssetResourceGeneratorOptions
461  );
462  get_variant!(get_css, Css, CssGeneratorOptions);
463  get_variant!(get_css_auto, CssAuto, CssAutoGeneratorOptions);
464  get_variant!(get_css_module, CssModule, CssModuleGeneratorOptions);
465  get_variant!(get_json, Json, JsonGeneratorOptions);
466
467  pub fn asset_filename(&self) -> Option<&Filename> {
468    self
469      .get_asset()
470      .and_then(|x| x.filename.as_ref())
471      .or_else(|| self.get_asset_resource().and_then(|x| x.filename.as_ref()))
472  }
473
474  pub fn asset_output_path(&self) -> Option<&Filename> {
475    self
476      .get_asset()
477      .and_then(|x| x.output_path.as_ref())
478      .or_else(|| {
479        self
480          .get_asset_resource()
481          .and_then(|x| x.output_path.as_ref())
482      })
483  }
484
485  pub fn asset_public_path(&self) -> Option<&PublicPath> {
486    self
487      .get_asset()
488      .and_then(|x| x.public_path.as_ref())
489      .or_else(|| {
490        self
491          .get_asset_resource()
492          .and_then(|x| x.public_path.as_ref())
493      })
494  }
495
496  pub fn asset_data_url(&self) -> Option<&AssetGeneratorDataUrl> {
497    self
498      .get_asset()
499      .and_then(|x| x.data_url.as_ref())
500      .or_else(|| self.get_asset_inline().and_then(|x| x.data_url.as_ref()))
501  }
502
503  pub fn asset_emit(&self) -> Option<bool> {
504    self
505      .get_asset()
506      .and_then(|x| x.emit)
507      .or_else(|| self.get_asset_resource().and_then(|x| x.emit))
508  }
509}
510
511#[cacheable]
512#[derive(Debug, Clone, MergeFrom)]
513pub struct AssetInlineGeneratorOptions {
514  pub data_url: Option<AssetGeneratorDataUrl>,
515  pub binary: Option<bool>,
516}
517
518impl From<AssetGeneratorOptions> for AssetInlineGeneratorOptions {
519  fn from(value: AssetGeneratorOptions) -> Self {
520    Self {
521      data_url: value.data_url,
522      binary: value.binary,
523    }
524  }
525}
526
527#[cacheable]
528#[derive(Debug, Clone, Copy, MergeFrom)]
529struct AssetGeneratorImportModeFlags(u8);
530bitflags! {
531  impl AssetGeneratorImportModeFlags: u8 {
532    const URL = 1 << 0;
533    const PRESERVE = 1 << 1;
534  }
535}
536
537#[cacheable]
538#[derive(Debug, Clone, Copy, MergeFrom)]
539pub struct AssetGeneratorImportMode(AssetGeneratorImportModeFlags);
540
541impl AssetGeneratorImportMode {
542  pub fn is_url(&self) -> bool {
543    self.0.contains(AssetGeneratorImportModeFlags::URL)
544  }
545  pub fn is_preserve(&self) -> bool {
546    self.0.contains(AssetGeneratorImportModeFlags::PRESERVE)
547  }
548}
549
550impl From<String> for AssetGeneratorImportMode {
551  fn from(s: String) -> Self {
552    match s.as_str() {
553      "url" => Self(AssetGeneratorImportModeFlags::URL),
554      "preserve" => Self(AssetGeneratorImportModeFlags::PRESERVE),
555      _ => unreachable!("AssetGeneratorImportMode error"),
556    }
557  }
558}
559
560impl Default for AssetGeneratorImportMode {
561  fn default() -> Self {
562    Self(AssetGeneratorImportModeFlags::URL)
563  }
564}
565
566#[cacheable]
567#[derive(Debug, Clone, MergeFrom)]
568pub struct AssetResourceGeneratorOptions {
569  pub emit: Option<bool>,
570  pub filename: Option<Filename>,
571  pub output_path: Option<Filename>,
572  pub public_path: Option<PublicPath>,
573  pub import_mode: Option<AssetGeneratorImportMode>,
574  pub binary: Option<bool>,
575}
576
577impl From<AssetGeneratorOptions> for AssetResourceGeneratorOptions {
578  fn from(value: AssetGeneratorOptions) -> Self {
579    Self {
580      emit: value.emit,
581      filename: value.filename,
582      output_path: value.output_path,
583      public_path: value.public_path,
584      import_mode: value.import_mode,
585      binary: value.binary,
586    }
587  }
588}
589
590#[cacheable]
591#[derive(Debug, Clone, MergeFrom)]
592pub struct AssetGeneratorOptions {
593  pub emit: Option<bool>,
594  pub filename: Option<Filename>,
595  pub output_path: Option<Filename>,
596  pub public_path: Option<PublicPath>,
597  pub data_url: Option<AssetGeneratorDataUrl>,
598  pub import_mode: Option<AssetGeneratorImportMode>,
599  pub binary: Option<bool>,
600}
601
602pub struct AssetGeneratorDataUrlFnCtx<'a> {
603  pub filename: String,
604  pub module: &'a dyn Module,
605  pub compilation: &'a Compilation,
606}
607
608pub type AssetGeneratorDataUrlFn = Arc<
609  dyn Fn(Vec<u8>, AssetGeneratorDataUrlFnCtx) -> BoxFuture<'static, Result<String>> + Sync + Send,
610>;
611
612#[cacheable]
613pub enum AssetGeneratorDataUrl {
614  Options(AssetGeneratorDataUrlOptions),
615  Func(#[cacheable(with=Unsupported)] AssetGeneratorDataUrlFn),
616}
617
618impl fmt::Debug for AssetGeneratorDataUrl {
619  fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
620    match self {
621      Self::Options(i) => i.fmt(f),
622      Self::Func(_) => "Func(...)".fmt(f),
623    }
624  }
625}
626
627impl Clone for AssetGeneratorDataUrl {
628  fn clone(&self) -> Self {
629    match self {
630      Self::Options(i) => Self::Options(i.clone()),
631      Self::Func(i) => Self::Func(i.clone()),
632    }
633  }
634}
635
636impl MergeFrom for AssetGeneratorDataUrl {
637  fn merge_from(self, other: &Self) -> Self {
638    other.clone()
639  }
640}
641
642#[cacheable]
643#[derive(Debug, Clone, MergeFrom, Hash)]
644pub struct AssetGeneratorDataUrlOptions {
645  pub encoding: Option<DataUrlEncoding>,
646  pub mimetype: Option<String>,
647}
648
649#[cacheable]
650#[derive(Debug, Clone, MergeFrom, Hash)]
651pub enum DataUrlEncoding {
652  None,
653  Base64,
654}
655
656impl fmt::Display for DataUrlEncoding {
657  fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
658    match self {
659      DataUrlEncoding::None => write!(f, ""),
660      DataUrlEncoding::Base64 => write!(f, "base64"),
661    }
662  }
663}
664
665impl From<String> for DataUrlEncoding {
666  fn from(value: String) -> Self {
667    match value.as_str() {
668      "base64" => Self::Base64,
669      "false" => Self::None,
670      _ => unreachable!("DataUrlEncoding should be base64 or false"),
671    }
672  }
673}
674
675#[cacheable]
676#[derive(Debug, Clone, MergeFrom)]
677pub struct CssGeneratorOptions {
678  pub exports_only: Option<bool>,
679  pub es_module: Option<bool>,
680}
681
682#[cacheable]
683#[derive(Default, Debug, Clone, MergeFrom)]
684pub struct CssAutoGeneratorOptions {
685  pub exports_convention: Option<CssExportsConvention>,
686  pub exports_only: Option<bool>,
687  pub local_ident_name: Option<LocalIdentName>,
688  pub es_module: Option<bool>,
689}
690
691impl From<CssGeneratorOptions> for CssAutoGeneratorOptions {
692  fn from(value: CssGeneratorOptions) -> Self {
693    Self {
694      exports_only: value.exports_only,
695      es_module: value.es_module,
696      ..Default::default()
697    }
698  }
699}
700
701#[cacheable]
702#[derive(Default, Debug, Clone, MergeFrom)]
703pub struct CssModuleGeneratorOptions {
704  pub exports_convention: Option<CssExportsConvention>,
705  pub exports_only: Option<bool>,
706  pub local_ident_name: Option<LocalIdentName>,
707  pub es_module: Option<bool>,
708}
709
710impl From<CssGeneratorOptions> for CssModuleGeneratorOptions {
711  fn from(value: CssGeneratorOptions) -> Self {
712    Self {
713      exports_only: value.exports_only,
714      es_module: value.es_module,
715      ..Default::default()
716    }
717  }
718}
719
720#[cacheable]
721#[derive(Default, Debug, Clone, MergeFrom)]
722pub struct JsonGeneratorOptions {
723  pub json_parse: Option<bool>,
724}
725
726#[cacheable]
727#[derive(Debug, Clone, MergeFrom)]
728pub struct LocalIdentName {
729  pub template: Filename,
730}
731
732impl From<String> for LocalIdentName {
733  fn from(value: String) -> Self {
734    Self {
735      template: value.into(),
736    }
737  }
738}
739
740impl From<&str> for LocalIdentName {
741  fn from(value: &str) -> Self {
742    Self {
743      template: crate::Filename::from(value),
744    }
745  }
746}
747
748#[cacheable]
749#[derive(Debug, Clone, Copy)]
750struct ExportsConventionFlags(u8);
751bitflags! {
752  impl ExportsConventionFlags: u8 {
753    const ASIS = 1 << 0;
754    const CAMELCASE = 1 << 1;
755    const DASHES = 1 << 2;
756  }
757}
758
759impl MergeFrom for ExportsConventionFlags {
760  fn merge_from(self, other: &Self) -> Self {
761    *other
762  }
763}
764
765#[cacheable]
766#[derive(Debug, Clone, Copy, MergeFrom)]
767pub struct CssExportsConvention(ExportsConventionFlags);
768
769impl CssExportsConvention {
770  pub fn as_is(&self) -> bool {
771    self.0.contains(ExportsConventionFlags::ASIS)
772  }
773
774  pub fn camel_case(&self) -> bool {
775    self.0.contains(ExportsConventionFlags::CAMELCASE)
776  }
777
778  pub fn dashes(&self) -> bool {
779    self.0.contains(ExportsConventionFlags::DASHES)
780  }
781}
782
783impl From<String> for CssExportsConvention {
784  fn from(s: String) -> Self {
785    match s.as_str() {
786      "as-is" => Self(ExportsConventionFlags::ASIS),
787      "camel-case" => Self(ExportsConventionFlags::ASIS | ExportsConventionFlags::CAMELCASE),
788      "camel-case-only" => Self(ExportsConventionFlags::CAMELCASE),
789      "dashes" => Self(ExportsConventionFlags::ASIS | ExportsConventionFlags::DASHES),
790      "dashes-only" => Self(ExportsConventionFlags::DASHES),
791      _ => unreachable!("css exportsConventions error"),
792    }
793  }
794}
795
796impl Default for CssExportsConvention {
797  fn default() -> Self {
798    Self(ExportsConventionFlags::ASIS)
799  }
800}
801
802pub type DescriptionData = HashMap<String, RuleSetConditionWithEmpty>;
803pub type With = HashMap<String, RuleSetConditionWithEmpty>;
804
805pub type RuleSetConditionFnMatcher =
806  Box<dyn Fn(DataRef) -> BoxFuture<'static, Result<bool>> + Sync + Send>;
807
808pub enum RuleSetCondition {
809  String(String),
810  Regexp(RspackRegex),
811  Logical(Box<RuleSetLogicalConditions>),
812  Array(Vec<RuleSetCondition>),
813  Func(RuleSetConditionFnMatcher),
814}
815
816impl fmt::Debug for RuleSetCondition {
817  fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
818    match self {
819      Self::String(i) => i.fmt(f),
820      Self::Regexp(i) => i.fmt(f),
821      Self::Logical(i) => i.fmt(f),
822      Self::Array(i) => i.fmt(f),
823      Self::Func(_) => "Func(...)".fmt(f),
824    }
825  }
826}
827
828#[derive(Copy, Clone)]
829pub enum DataRef<'a> {
830  Str(&'a str),
831  Value(&'a serde_json::Value),
832}
833
834impl<'s> From<&'s str> for DataRef<'s> {
835  fn from(value: &'s str) -> Self {
836    Self::Str(value)
837  }
838}
839
840impl<'s> From<&'s serde_json::Value> for DataRef<'s> {
841  fn from(value: &'s serde_json::Value) -> Self {
842    Self::Value(value)
843  }
844}
845
846impl DataRef<'_> {
847  pub fn as_str(&self) -> Option<&str> {
848    match self {
849      Self::Str(s) => Some(s),
850      Self::Value(v) => v.as_str(),
851    }
852  }
853
854  pub fn to_value(&self) -> serde_json::Value {
855    match self {
856      Self::Str(s) => serde_json::Value::String((*s).to_owned()),
857      Self::Value(v) => (*v).to_owned(),
858    }
859  }
860}
861
862impl RuleSetCondition {
863  #[async_recursion]
864  pub async fn try_match(&self, data: DataRef<'async_recursion>) -> Result<bool> {
865    match self {
866      Self::String(s) => Ok(
867        data
868          .as_str()
869          .map(|data| data.starts_with(s))
870          .unwrap_or_default(),
871      ),
872      Self::Regexp(r) => Ok(data.as_str().map(|data| r.test(data)).unwrap_or_default()),
873      Self::Logical(g) => g.try_match(data).await,
874      Self::Array(l) => try_any(l, |i| async { i.try_match(data).await }).await,
875      Self::Func(f) => f(data).await,
876    }
877  }
878
879  #[async_recursion]
880  async fn match_when_empty(&self) -> Result<bool> {
881    let res = match self {
882      RuleSetCondition::String(s) => s.is_empty(),
883      RuleSetCondition::Regexp(rspack_regex) => rspack_regex.test(""),
884      RuleSetCondition::Logical(logical) => logical.match_when_empty().await?,
885      RuleSetCondition::Array(arr) => {
886        arr.is_empty() && try_any(arr, |c| async move { c.match_when_empty().await }).await?
887      }
888      RuleSetCondition::Func(func) => func("".into()).await?,
889    };
890    Ok(res)
891  }
892}
893
894#[derive(Debug)]
895pub struct RuleSetConditionWithEmpty {
896  condition: RuleSetCondition,
897  match_when_empty: OnceCell<bool>,
898}
899
900impl RuleSetConditionWithEmpty {
901  pub fn new(condition: RuleSetCondition) -> Self {
902    Self {
903      condition,
904      match_when_empty: OnceCell::new(),
905    }
906  }
907
908  pub async fn try_match(&self, data: DataRef<'_>) -> Result<bool> {
909    self.condition.try_match(data).await
910  }
911
912  pub async fn match_when_empty(&self) -> Result<bool> {
913    self
914      .match_when_empty
915      .get_or_try_init(|| async { self.condition.match_when_empty().await })
916      .await
917      .copied()
918  }
919}
920
921impl From<RuleSetCondition> for RuleSetConditionWithEmpty {
922  fn from(condition: RuleSetCondition) -> Self {
923    Self::new(condition)
924  }
925}
926
927#[derive(Debug, Default)]
928pub struct RuleSetLogicalConditions {
929  pub and: Option<Vec<RuleSetCondition>>,
930  pub or: Option<Vec<RuleSetCondition>>,
931  pub not: Option<RuleSetCondition>,
932}
933
934impl RuleSetLogicalConditions {
935  #[async_recursion]
936  pub async fn try_match(&self, data: DataRef<'async_recursion>) -> Result<bool> {
937    if let Some(and) = &self.and
938      && try_any(and, |i| async { i.try_match(data).await.map(|i| !i) }).await?
939    {
940      return Ok(false);
941    }
942    if let Some(or) = &self.or
943      && try_all(or, |i| async { i.try_match(data).await.map(|i| !i) }).await?
944    {
945      return Ok(false);
946    }
947    if let Some(not) = &self.not
948      && not.try_match(data).await?
949    {
950      return Ok(false);
951    }
952    Ok(true)
953  }
954
955  pub async fn match_when_empty(&self) -> Result<bool> {
956    let mut has_condition = false;
957    let mut match_when_empty = true;
958    if let Some(and) = &self.and {
959      has_condition = true;
960      match_when_empty &= try_all(and, |i| async { i.match_when_empty().await }).await?;
961    }
962    if let Some(or) = &self.or {
963      has_condition = true;
964      match_when_empty &= try_any(or, |i| async { i.match_when_empty().await }).await?;
965    }
966    if let Some(not) = &self.not {
967      has_condition = true;
968      match_when_empty &= !not.match_when_empty().await?;
969    }
970    Ok(has_condition && match_when_empty)
971  }
972}
973
974pub struct FuncUseCtx {
975  pub resource: Option<String>,
976  pub real_resource: Option<String>,
977  pub resource_query: Option<String>,
978  pub resource_fragment: Option<String>,
979  pub issuer: Option<Box<str>>,
980  pub issuer_layer: Option<String>,
981}
982
983#[derive(Debug, Clone)]
984pub struct ModuleRuleUseLoader {
985  /// Loader identifier with query and fragments
986  /// Loader ident or query will be appended if it exists.
987  pub loader: String,
988  /// Loader options
989  /// This only exists if the loader is a built-in loader.
990  pub options: Option<String>,
991}
992
993pub type FnUse =
994  Box<dyn Fn(FuncUseCtx) -> BoxFuture<'static, Result<Vec<ModuleRuleUseLoader>>> + Sync + Send>;
995
996#[derive(Debug, Default)]
997pub struct ModuleRule {
998  /// A conditional match matching an absolute path + query + fragment.
999  /// Note:
1000  ///   This is a custom matching rule not initially designed by webpack.
1001  ///   Only for single-threaded environment interoperation purpose.
1002  pub rspack_resource: Option<RuleSetCondition>,
1003  /// A condition matcher matching an absolute path.
1004  pub test: Option<RuleSetCondition>,
1005  pub include: Option<RuleSetCondition>,
1006  pub exclude: Option<RuleSetCondition>,
1007  /// A condition matcher matching an absolute path.
1008  pub resource: Option<RuleSetCondition>,
1009  /// A condition matcher against the resource query.
1010  pub resource_query: Option<RuleSetConditionWithEmpty>,
1011  pub resource_fragment: Option<RuleSetConditionWithEmpty>,
1012  pub dependency: Option<RuleSetCondition>,
1013  pub issuer: Option<RuleSetConditionWithEmpty>,
1014  pub issuer_layer: Option<RuleSetConditionWithEmpty>,
1015  pub scheme: Option<RuleSetConditionWithEmpty>,
1016  pub mimetype: Option<RuleSetConditionWithEmpty>,
1017  pub description_data: Option<DescriptionData>,
1018  pub with: Option<With>,
1019  pub one_of: Option<Vec<ModuleRule>>,
1020  pub rules: Option<Vec<ModuleRule>>,
1021  pub effect: ModuleRuleEffect,
1022  pub extract_source_map: Option<bool>,
1023}
1024
1025#[derive(Debug, Default)]
1026pub struct ModuleRuleEffect {
1027  pub side_effects: Option<bool>,
1028  /// The `ModuleType` to use for the matched resource.
1029  pub r#type: Option<ModuleType>,
1030  pub layer: Option<String>,
1031  pub r#use: ModuleRuleUse,
1032  pub parser: Option<ParserOptions>,
1033  pub generator: Option<GeneratorOptions>,
1034  pub resolve: Option<Resolve>,
1035  pub enforce: ModuleRuleEnforce,
1036  pub extract_source_map: Option<bool>,
1037}
1038
1039pub enum ModuleRuleUse {
1040  Array(Vec<ModuleRuleUseLoader>),
1041  Func(FnUse),
1042}
1043
1044impl Default for ModuleRuleUse {
1045  fn default() -> Self {
1046    Self::Array(vec![])
1047  }
1048}
1049
1050impl fmt::Debug for ModuleRuleUse {
1051  fn fmt(&self, f: &mut std::fmt::Formatter) -> std::result::Result<(), std::fmt::Error> {
1052    match self {
1053      ModuleRuleUse::Array(array_use) => write!(
1054        f,
1055        "{}",
1056        array_use
1057          .iter()
1058          .map(|l| &*l.loader)
1059          .collect::<Vec<_>>()
1060          .join("!")
1061      ),
1062      ModuleRuleUse::Func(_) => write!(f, "Fn(...)"),
1063    }
1064  }
1065}
1066
1067pub type ModuleNoParseTestFn =
1068  Box<dyn Fn(String) -> BoxFuture<'static, Result<bool>> + Sync + Send>;
1069
1070pub enum ModuleNoParseRule {
1071  AbsPathPrefix(String),
1072  Regexp(RspackRegex),
1073  TestFn(ModuleNoParseTestFn),
1074}
1075
1076impl fmt::Debug for ModuleNoParseRule {
1077  fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1078    match self {
1079      Self::TestFn(_) => "Fn(...)".fmt(f),
1080      _ => self.fmt(f),
1081    }
1082  }
1083}
1084
1085impl ModuleNoParseRule {
1086  pub async fn try_match(&self, request: &str) -> Result<bool> {
1087    match self {
1088      Self::AbsPathPrefix(s) => Ok(request.starts_with(s)),
1089      Self::Regexp(reg) => Ok(reg.test(request)),
1090      Self::TestFn(func) => func(request.to_string()).await,
1091    }
1092  }
1093}
1094
1095#[derive(Debug)]
1096pub enum ModuleNoParseRules {
1097  Rule(ModuleNoParseRule),
1098  Rules(Vec<ModuleNoParseRule>),
1099}
1100
1101impl ModuleNoParseRules {
1102  #[async_recursion]
1103  pub async fn try_match(&self, request: &str) -> Result<bool> {
1104    match self {
1105      Self::Rule(r) => r.try_match(request).await,
1106      Self::Rules(list) => try_any(list, |r| r.try_match(request)).await,
1107    }
1108  }
1109}
1110
1111#[derive(Debug, Default)]
1112pub enum ModuleRuleEnforce {
1113  Post,
1114  #[default]
1115  Normal,
1116  Pre,
1117}
1118
1119pub type UnsafeCachePredicate =
1120  Box<dyn Fn(&dyn Module) -> BoxFuture<'static, Result<bool>> + Sync + Send>;
1121
1122// BE CAREFUL:
1123// Add more fields to this struct should result in adding new fields to options builder.
1124// `impl From<ModuleOptions> for ModuleOptionsBuilder` should be updated.
1125#[derive(Debug, Default)]
1126pub struct ModuleOptions {
1127  pub rules: Vec<ModuleRule>,
1128  pub parser: Option<ParserOptionsMap>,
1129  pub generator: Option<GeneratorOptionsMap>,
1130  pub no_parse: Option<ModuleNoParseRules>,
1131  #[debug(skip)]
1132  pub unsafe_cache: Option<UnsafeCachePredicate>,
1133}