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