swc_css_modules/
lib.rs

1use rustc_hash::FxHashMap;
2use swc_atoms::Atom;
3use swc_common::{util::take::Take, Span};
4use swc_css_ast::{
5    ComplexSelector, ComplexSelectorChildren, ComponentValue, Declaration, DeclarationName,
6    Delimiter, DelimiterValue, FunctionName, Ident, KeyframesName, PseudoClassSelectorChildren,
7    QualifiedRule, QualifiedRulePrelude, Stylesheet, SubclassSelector,
8};
9use swc_css_visit::{VisitMut, VisitMutWith};
10
11pub mod imports;
12
13/// Various configurations for the css modules.
14///
15/// # Note
16///
17/// This is a trait rather than a struct because api like `fn() -> String` is
18/// too restricted and `Box<Fn() -> String` is (needlessly) slow.
19pub trait TransformConfig {
20    /// Creates a class name for the given `local_name`.
21    fn new_name_for(&self, local: &Atom) -> Atom;
22
23    // /// Used for `@value` imports.
24    // fn get_value(&self, import_source: &str, value_name: &Atom) ->
25    // ComponentValue;
26}
27
28#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
29pub enum CssClassName {
30    Local {
31        /// Tranformed css class name
32        name: Ident,
33    },
34    Global {
35        name: Ident,
36    },
37    Import {
38        /// The exported class name. This is the value specified by the user.
39        name: Ident,
40        /// The module specifier.
41        from: Atom,
42    },
43}
44
45#[derive(Debug, Clone)]
46pub struct TransformResult {
47    /// A map of js class name to css class names.
48    pub renamed: FxHashMap<Atom, Vec<CssClassName>>,
49}
50
51/// Returns a map from local name to exported name.
52pub fn compile<'a>(ss: &mut Stylesheet, config: impl 'a + TransformConfig) -> TransformResult {
53    let mut compiler = Compiler {
54        config,
55        data: Default::default(),
56        result: TransformResult {
57            renamed: Default::default(),
58        },
59    };
60
61    ss.visit_mut_with(&mut compiler);
62
63    fn add(result: &mut TransformResult, data: &Data, key: &Atom, composes: &[CssClassName]) {
64        let mut extra_classes = Vec::new();
65        {
66            let class_names = result.renamed.entry(key.clone()).or_default();
67
68            class_names.extend(composes.iter().cloned());
69        }
70
71        for composed_class_name in composes.iter() {
72            if let CssClassName::Local { name } = composed_class_name {
73                if let Some(original_class_name) = data.renamed_to_orig.get(&name.value) {
74                    extra_classes.extend(
75                        result
76                            .renamed
77                            .entry(original_class_name.clone())
78                            .or_default()
79                            .split_at(1)
80                            .1
81                            .to_vec(),
82                    );
83                }
84            }
85        }
86
87        {
88            let class_names = result.renamed.entry(key.clone()).or_default();
89
90            class_names.extend(extra_classes);
91        }
92    }
93
94    let composes = compiler.data.composes_inherit.take();
95
96    for (key, composes) in &composes {
97        add(&mut compiler.result, &compiler.data, key, composes);
98    }
99    for (key, composes) in &composes {
100        add(&mut compiler.result, &compiler.data, key, composes);
101    }
102    compiler.result.renamed.iter_mut().for_each(|(_, v)| {
103        v.sort();
104        v.dedup();
105    });
106
107    compiler.result
108}
109
110struct Compiler<C>
111where
112    C: TransformConfig,
113{
114    config: C,
115    data: Data,
116    result: TransformResult,
117}
118
119#[derive(Default)]
120struct Data {
121    /// Context for `composes`
122    composes_for_current: Option<Vec<CssClassName>>,
123    composes_inherit: Vec<(Atom, Vec<CssClassName>)>,
124
125    renamed_to_orig: FxHashMap<Atom, Atom>,
126    orig_to_renamed: FxHashMap<Atom, Atom>,
127
128    is_global_mode: bool,
129    is_in_local_pseudo_class: bool,
130}
131
132impl<C> VisitMut for Compiler<C>
133where
134    C: TransformConfig,
135{
136    // TODO handle `@counter-style`, CSS modules doesn't support it, but we should
137    // to fix it
138    fn visit_mut_keyframes_name(&mut self, n: &mut KeyframesName) {
139        match n {
140            KeyframesName::CustomIdent(n) if !self.data.is_global_mode => {
141                n.raw = None;
142
143                rename(
144                    n.span,
145                    &mut self.config,
146                    &mut self.result,
147                    &mut self.data.orig_to_renamed,
148                    &mut self.data.renamed_to_orig,
149                    &mut n.value,
150                );
151            }
152            KeyframesName::Str(n) if !self.data.is_global_mode => {
153                n.raw = None;
154
155                rename(
156                    n.span,
157                    &mut self.config,
158                    &mut self.result,
159                    &mut self.data.orig_to_renamed,
160                    &mut self.data.renamed_to_orig,
161                    &mut n.value,
162                );
163            }
164            KeyframesName::PseudoFunction(pseudo_function)
165                if pseudo_function.pseudo.value == "local" =>
166            {
167                match &pseudo_function.name {
168                    KeyframesName::CustomIdent(custom_ident) => {
169                        *n = KeyframesName::CustomIdent(custom_ident.clone());
170                    }
171                    KeyframesName::Str(string) => {
172                        *n = KeyframesName::Str(string.clone());
173                    }
174                    _ => {
175                        unreachable!();
176                    }
177                }
178
179                n.visit_mut_with(self);
180
181                return;
182            }
183            KeyframesName::PseudoPrefix(pseudo_prefix) if pseudo_prefix.pseudo.value == "local" => {
184                match &pseudo_prefix.name {
185                    KeyframesName::CustomIdent(custom_ident) => {
186                        *n = KeyframesName::CustomIdent(custom_ident.clone());
187                    }
188                    KeyframesName::Str(string) => {
189                        *n = KeyframesName::Str(string.clone());
190                    }
191                    _ => {
192                        unreachable!();
193                    }
194                }
195
196                n.visit_mut_with(self);
197
198                return;
199            }
200            KeyframesName::PseudoFunction(pseudo_function)
201                if pseudo_function.pseudo.value == "global" =>
202            {
203                match &pseudo_function.name {
204                    KeyframesName::CustomIdent(custom_ident) => {
205                        *n = KeyframesName::CustomIdent(custom_ident.clone());
206                    }
207                    KeyframesName::Str(string) => {
208                        *n = KeyframesName::Str(string.clone());
209                    }
210                    _ => {
211                        unreachable!();
212                    }
213                }
214
215                return;
216            }
217            KeyframesName::PseudoPrefix(pseudo_prefix)
218                if pseudo_prefix.pseudo.value == "global" =>
219            {
220                match &pseudo_prefix.name {
221                    KeyframesName::CustomIdent(custom_ident) => {
222                        *n = KeyframesName::CustomIdent(custom_ident.clone());
223                    }
224                    KeyframesName::Str(string) => {
225                        *n = KeyframesName::Str(string.clone());
226                    }
227                    _ => {
228                        unreachable!();
229                    }
230                }
231
232                return;
233            }
234            _ => {}
235        }
236
237        n.visit_mut_children_with(self);
238    }
239
240    fn visit_mut_qualified_rule(&mut self, n: &mut QualifiedRule) {
241        let old_compose_stack = self.data.composes_for_current.take();
242
243        self.data.composes_for_current = Some(Default::default());
244
245        n.visit_mut_children_with(self);
246
247        if let QualifiedRulePrelude::SelectorList(sel) = &n.prelude {
248            let composes = self.data.composes_for_current.take();
249
250            for child in &sel.children {
251                if let ComplexSelectorChildren::CompoundSelector(sel) = &child.children[0] {
252                    for subclass_sel in &sel.subclass_selectors {
253                        if let SubclassSelector::Class(class_sel) = &subclass_sel {
254                            if let Some(composes) = &composes {
255                                let key = self
256                                    .data
257                                    .renamed_to_orig
258                                    .get(&class_sel.text.value)
259                                    .cloned();
260
261                                if let Some(key) = key {
262                                    self.data.composes_inherit.push((key, composes.clone()));
263                                }
264                            }
265                        }
266                    }
267                }
268            }
269        }
270
271        self.data.composes_for_current = old_compose_stack;
272    }
273
274    fn visit_mut_component_values(&mut self, n: &mut Vec<ComponentValue>) {
275        n.visit_mut_children_with(self);
276
277        n.retain(|v| match v {
278            ComponentValue::Declaration(d) => {
279                if let DeclarationName::Ident(ident) = &d.name {
280                    if &*ident.value == "composes" {
281                        return false;
282                    }
283                }
284
285                true
286            }
287            _ => true,
288        });
289    }
290
291    /// Handles `composes`
292    fn visit_mut_declaration(&mut self, n: &mut Declaration) {
293        n.visit_mut_children_with(self);
294
295        if let Some(composes_for_current) = &mut self.data.composes_for_current {
296            if let DeclarationName::Ident(name) = &n.name {
297                if &*name.value == "composes" {
298                    // composes: name from 'foo.css'
299                    if n.value.len() >= 3 {
300                        match (&n.value[n.value.len() - 2], &n.value[n.value.len() - 1]) {
301                            (ComponentValue::Ident(ident), ComponentValue::Str(import_source))
302                                if ident.value == "from" =>
303                            {
304                                for class_name in n.value.iter().take(n.value.len() - 2) {
305                                    if let ComponentValue::Ident(value) = class_name {
306                                        composes_for_current.push(CssClassName::Import {
307                                            name: *value.clone(),
308                                            from: import_source.value.clone(),
309                                        });
310                                    }
311                                }
312
313                                return;
314                            }
315                            (ComponentValue::Ident(from), ComponentValue::Ident(global))
316                                if from.value == "from" && global.value == "global" =>
317                            {
318                                for class_name in n.value.iter().take(n.value.len() - 2) {
319                                    if let ComponentValue::Ident(value) = class_name {
320                                        composes_for_current.push(CssClassName::Global {
321                                            name: *value.clone(),
322                                        });
323                                    }
324                                }
325                                return;
326                            }
327                            _ => (),
328                        }
329                    }
330
331                    for class_name in n.value.iter_mut() {
332                        if let ComponentValue::Ident(ident) = class_name {
333                            let Ident { span, value, .. } = &mut **ident;
334                            let orig = value.clone();
335                            rename(
336                                *span,
337                                &mut self.config,
338                                &mut self.result,
339                                &mut self.data.orig_to_renamed,
340                                &mut self.data.renamed_to_orig,
341                                value,
342                            );
343
344                            if let Some(new_name) = self.data.orig_to_renamed.get(&orig) {
345                                composes_for_current.push(CssClassName::Local {
346                                    name: Ident {
347                                        span: *span,
348                                        value: new_name.clone(),
349                                        raw: None,
350                                    },
351                                });
352                            }
353                        }
354                    }
355                }
356            }
357        }
358
359        if let DeclarationName::Ident(name) = &n.name {
360            match &*name.value {
361                "animation" => {
362                    let mut can_change = true;
363
364                    let mut iteration_count_visited = false;
365                    let mut fill_mode_visited = false;
366                    let mut direction_visited = false;
367                    let mut easing_function_visited = false;
368                    let mut play_state_visited = false;
369
370                    for v in &mut n.value {
371                        match v {
372                            ComponentValue::Ident(ident) => {
373                                if !can_change {
374                                    continue;
375                                }
376
377                                let Ident {
378                                    span, value, raw, ..
379                                } = &mut **ident;
380
381                                match &**value {
382                                    // iteration-count
383                                    "infinite" => {
384                                        if !iteration_count_visited {
385                                            iteration_count_visited = true;
386                                            continue;
387                                        }
388                                    }
389                                    // fill-mode
390                                    // NOTE: `animation: none:` will be trapped here
391                                    "none" | "forwards" | "backwards" | "both" => {
392                                        if !fill_mode_visited {
393                                            fill_mode_visited = true;
394                                            continue;
395                                        }
396                                    }
397                                    // direction
398                                    "normal" | "reverse" | "alternate" | "alternate-reverse" => {
399                                        if !direction_visited {
400                                            direction_visited = true;
401                                            continue;
402                                        }
403                                    }
404                                    // easing-function
405                                    "linear" | "ease" | "ease-in" | "ease-out" | "ease-in-out"
406                                    | "step-start" | "step-end" => {
407                                        if !easing_function_visited {
408                                            easing_function_visited = true;
409                                            continue;
410                                        }
411                                    }
412                                    // play-state
413                                    "running" | "paused" => {
414                                        if !play_state_visited {
415                                            play_state_visited = true;
416                                            continue;
417                                        }
418                                    }
419                                    _ => {}
420                                }
421
422                                *raw = None;
423
424                                rename(
425                                    *span,
426                                    &mut self.config,
427                                    &mut self.result,
428                                    &mut self.data.orig_to_renamed,
429                                    &mut self.data.renamed_to_orig,
430                                    value,
431                                );
432                                can_change = false;
433                            }
434                            ComponentValue::Integer(_) => {
435                                iteration_count_visited = true;
436                            }
437                            ComponentValue::Function(f) => {
438                                if let FunctionName::Ident(ident) = &f.name {
439                                    match &*ident.value {
440                                        // easing-function
441                                        "steps" | "cubic-bezier" | "linear" => {
442                                            easing_function_visited = true;
443                                        }
444                                        _ => {
445                                            // should be syntax error
446                                        }
447                                    }
448                                }
449                            }
450                            ComponentValue::Delimiter(delimiter) => {
451                                if matches!(
452                                    &**delimiter,
453                                    Delimiter {
454                                        value: DelimiterValue::Comma,
455                                        ..
456                                    }
457                                ) {
458                                    can_change = true;
459
460                                    // reset all flags
461                                    iteration_count_visited = false;
462                                    fill_mode_visited = false;
463                                    direction_visited = false;
464                                    easing_function_visited = false;
465                                    play_state_visited = false;
466                                }
467                            }
468                            _ => (),
469                        }
470                    }
471                }
472                "animation-name" => {
473                    for v in &mut n.value {
474                        if let ComponentValue::Ident(ident) = v {
475                            let Ident {
476                                span, value, raw, ..
477                            } = &mut **ident;
478                            *raw = None;
479
480                            rename(
481                                *span,
482                                &mut self.config,
483                                &mut self.result,
484                                &mut self.data.orig_to_renamed,
485                                &mut self.data.renamed_to_orig,
486                                value,
487                            );
488                        }
489                    }
490                }
491                _ => {}
492            }
493        }
494    }
495
496    fn visit_mut_complex_selector(&mut self, n: &mut ComplexSelector) {
497        let mut new_children = Vec::with_capacity(n.children.len());
498
499        let old_is_global_mode = self.data.is_global_mode;
500
501        'complex: for mut n in n.children.take() {
502            if let ComplexSelectorChildren::CompoundSelector(selector) = &mut n {
503                for sel in selector.subclass_selectors.iter_mut() {
504                    match sel {
505                        SubclassSelector::Class(..) | SubclassSelector::Id(..) => {
506                            if !self.data.is_global_mode {
507                                process_local(
508                                    &mut self.config,
509                                    &mut self.result,
510                                    &mut self.data.orig_to_renamed,
511                                    &mut self.data.renamed_to_orig,
512                                    sel,
513                                );
514                            }
515                        }
516
517                        _ => {}
518                    }
519                }
520
521                for (sel_index, sel) in selector.subclass_selectors.iter_mut().enumerate() {
522                    if let SubclassSelector::PseudoClass(class_sel) = sel {
523                        match &*class_sel.name.value {
524                            "local" => {
525                                if let Some(children) = &mut class_sel.children {
526                                    if let Some(PseudoClassSelectorChildren::ComplexSelector(
527                                        complex_selector,
528                                    )) = children.get_mut(0)
529                                    {
530                                        let old_is_global_mode = self.data.is_global_mode;
531                                        let old_inside = self.data.is_global_mode;
532
533                                        self.data.is_global_mode = false;
534                                        self.data.is_in_local_pseudo_class = true;
535
536                                        complex_selector.visit_mut_with(self);
537
538                                        let mut complex_selector_children =
539                                            complex_selector.children.clone();
540                                        prepend_left_subclass_selectors(
541                                            &mut complex_selector_children,
542                                            &mut selector.subclass_selectors,
543                                            sel_index,
544                                        );
545                                        new_children.extend(complex_selector_children);
546
547                                        self.data.is_global_mode = old_is_global_mode;
548                                        self.data.is_in_local_pseudo_class = old_inside;
549                                    }
550                                } else {
551                                    if sel_index > 0 {
552                                        if let Some(n) = n.as_mut_compound_selector() {
553                                            n.subclass_selectors.remove(sel_index);
554                                        }
555                                        new_children.push(n);
556                                    }
557                                    self.data.is_global_mode = false;
558                                }
559
560                                continue 'complex;
561                            }
562                            "global" => {
563                                if let Some(children) = &mut class_sel.children {
564                                    if let Some(PseudoClassSelectorChildren::ComplexSelector(
565                                        complex_selector,
566                                    )) = children.get_mut(0)
567                                    {
568                                        let mut complex_selector_children =
569                                            complex_selector.children.clone();
570                                        prepend_left_subclass_selectors(
571                                            &mut complex_selector_children,
572                                            &mut selector.subclass_selectors,
573                                            sel_index,
574                                        );
575                                        new_children.extend(complex_selector_children);
576                                    }
577                                } else {
578                                    if sel_index > 0 {
579                                        if let Some(n) = n.as_mut_compound_selector() {
580                                            n.subclass_selectors.remove(sel_index);
581                                        }
582                                        new_children.push(n);
583                                    }
584                                    self.data.is_global_mode = true;
585                                }
586
587                                continue 'complex;
588                            }
589                            _ => {}
590                        }
591                    }
592                }
593            }
594
595            new_children.push(n);
596        }
597
598        n.children = new_children;
599
600        self.data.is_global_mode = old_is_global_mode;
601
602        if self.data.is_in_local_pseudo_class {
603            return;
604        }
605
606        n.visit_mut_children_with(self);
607    }
608
609    fn visit_mut_complex_selectors(&mut self, n: &mut Vec<ComplexSelector>) {
610        n.visit_mut_children_with(self);
611
612        n.retain_mut(|s| !s.children.is_empty());
613    }
614}
615
616fn rename<C>(
617    span: Span,
618    config: &mut C,
619    result: &mut TransformResult,
620    orig_to_renamed: &mut FxHashMap<Atom, Atom>,
621    renamed_to_orig: &mut FxHashMap<Atom, Atom>,
622    name: &mut Atom,
623) where
624    C: TransformConfig,
625{
626    if let Some(renamed) = orig_to_renamed.get(name) {
627        *name = renamed.clone();
628        return;
629    }
630
631    let new = config.new_name_for(name);
632
633    orig_to_renamed.insert(name.clone(), new.clone());
634    renamed_to_orig.insert(new.clone(), name.clone());
635
636    {
637        let e = result.renamed.entry(name.clone()).or_default();
638
639        let v = CssClassName::Local {
640            name: Ident {
641                span,
642                value: new.clone(),
643                raw: None,
644            },
645        };
646        if !e.contains(&v) {
647            e.push(v);
648        }
649    }
650
651    *name = new;
652}
653
654fn process_local<C>(
655    config: &mut C,
656    result: &mut TransformResult,
657    orig_to_renamed: &mut FxHashMap<Atom, Atom>,
658    renamed_to_orig: &mut FxHashMap<Atom, Atom>,
659    sel: &mut SubclassSelector,
660) where
661    C: TransformConfig,
662{
663    match sel {
664        SubclassSelector::Id(sel) => {
665            sel.text.raw = None;
666
667            rename(
668                sel.span,
669                config,
670                result,
671                orig_to_renamed,
672                renamed_to_orig,
673                &mut sel.text.value,
674            );
675        }
676        SubclassSelector::Class(sel) => {
677            sel.text.raw = None;
678
679            rename(
680                sel.span,
681                config,
682                result,
683                orig_to_renamed,
684                renamed_to_orig,
685                &mut sel.text.value,
686            );
687        }
688        SubclassSelector::Attribute(_) => {}
689        SubclassSelector::PseudoClass(_) => {}
690        SubclassSelector::PseudoElement(_) => {}
691    }
692}
693
694fn prepend_left_subclass_selectors(
695    complex_selector_children: &mut [ComplexSelectorChildren],
696    sels: &mut Vec<SubclassSelector>,
697    mut sel_index: usize,
698) {
699    sels.remove(sel_index);
700
701    for c in complex_selector_children
702        .iter_mut()
703        .filter_map(|c| c.as_mut_compound_selector())
704    {
705        c.subclass_selectors.splice(0..0, sels.drain(..sel_index));
706
707        if !sels.is_empty() {
708            c.subclass_selectors.extend(sels[..].iter().cloned());
709        }
710
711        sel_index = 0;
712    }
713}