Skip to main content

turbopack_ecmascript_plugins/transform/
emotion.rs

1#![allow(unused)]
2use std::{
3    hash::{Hash, Hasher},
4    path::Path,
5};
6
7use anyhow::Result;
8use async_trait::async_trait;
9use bincode::{Decode, Encode};
10use indexmap::IndexMap;
11use rustc_hash::{FxBuildHasher, FxHasher};
12use serde::{Deserialize, Serialize};
13use swc_core::{
14    atoms::Atom,
15    common::util::take::Take,
16    ecma::{
17        ast::{Module, Program},
18        visit::FoldWith,
19    },
20};
21use swc_emotion::ImportMap;
22use turbo_rcstr::RcStr;
23use turbo_tasks::{NonLocalValue, OperationValue, ValueDefault, Vc};
24use turbopack_ecmascript::{CustomTransformer, TransformContext};
25
26#[derive(
27    Clone, PartialEq, Eq, Debug, Deserialize, NonLocalValue, OperationValue, Encode, Decode,
28)]
29#[serde(rename_all = "kebab-case")]
30pub enum EmotionLabelKind {
31    DevOnly,
32    Always,
33    Never,
34}
35
36#[derive(Clone, PartialEq, Eq, Debug, Serialize, Deserialize, NonLocalValue, OperationValue)]
37#[serde(rename_all = "camelCase")]
38pub struct EmotionImportItemConfig {
39    pub canonical_import: EmotionItemSpecifier,
40    pub styled_base_import: Option<EmotionItemSpecifier>,
41}
42
43impl From<&EmotionImportItemConfig> for swc_emotion::ImportItemConfig {
44    fn from(value: &EmotionImportItemConfig) -> Self {
45        swc_emotion::ImportItemConfig {
46            canonical_import: From::from(&value.canonical_import),
47            styled_base_import: value.styled_base_import.as_ref().map(From::from),
48        }
49    }
50}
51
52#[derive(Clone, PartialEq, Eq, Debug, Serialize, Deserialize, NonLocalValue, OperationValue)]
53pub struct EmotionItemSpecifier(pub RcStr, pub RcStr);
54
55impl From<&EmotionItemSpecifier> for swc_emotion::ItemSpecifier {
56    fn from(value: &EmotionItemSpecifier) -> Self {
57        swc_emotion::ItemSpecifier(value.0.as_str().into(), value.1.as_str().into())
58    }
59}
60
61pub type EmotionImportMapValue = IndexMap<RcStr, EmotionImportItemConfig, FxBuildHasher>;
62
63#[turbo_tasks::value(shared, operation)]
64#[derive(Default, Clone, Debug, Deserialize)]
65#[serde(rename_all = "camelCase")]
66pub struct EmotionTransformConfig {
67    pub sourcemap: Option<bool>,
68    pub label_format: Option<String>,
69    pub auto_label: Option<EmotionLabelKind>,
70    #[bincode(with_serde)]
71    pub import_map: Option<IndexMap<RcStr, EmotionImportMapValue, FxBuildHasher>>,
72}
73
74#[derive(Debug)]
75pub struct EmotionTransformer {
76    #[cfg(feature = "transform_emotion")]
77    config: swc_emotion::EmotionOptions,
78}
79
80#[cfg(feature = "transform_emotion")]
81impl EmotionTransformer {
82    pub fn new(config: &EmotionTransformConfig) -> Option<Self> {
83        let config = swc_emotion::EmotionOptions {
84            // When you create a transformer structure, it is assumed that you are performing an
85            // emotion transform.
86            enabled: Some(true),
87            sourcemap: config.sourcemap,
88            label_format: config.label_format.as_deref().map(From::from),
89            auto_label: match config.auto_label.as_ref() {
90                Some(EmotionLabelKind::Always) => Some(true),
91                Some(EmotionLabelKind::Never) => Some(false),
92                // [TODO]: this is not correct (doesn't take current mode into account)
93                Some(EmotionLabelKind::DevOnly) => None,
94                None => None,
95            },
96            import_map: config.import_map.as_ref().map(|map| {
97                map.iter()
98                    .map(|(k, v)| {
99                        (
100                            k.as_str().into(),
101                            swc_emotion::ImportMapValue::from_iter(v.iter().map(|(k, v)| {
102                                (k.as_str().into(), swc_emotion::ImportItemConfig::from(v))
103                            })),
104                        )
105                    })
106                    .collect()
107            }),
108        };
109
110        Some(EmotionTransformer { config })
111    }
112}
113
114#[cfg(not(feature = "transform_emotion"))]
115impl EmotionTransformer {
116    pub fn new(_config: &EmotionTransformConfig) -> Option<Self> {
117        None
118    }
119}
120
121#[async_trait]
122impl CustomTransformer for EmotionTransformer {
123    #[tracing::instrument(level = tracing::Level::TRACE, name = "emotion", skip_all)]
124    async fn transform(&self, program: &mut Program, ctx: &TransformContext<'_>) -> Result<()> {
125        #[cfg(feature = "transform_emotion")]
126        {
127            let hash = {
128                let mut hasher = FxHasher::default();
129                program.hash(&mut hasher);
130                hasher.finish()
131            };
132            program.mutate(swc_emotion::emotion(
133                &self.config,
134                Path::new(ctx.file_name_str),
135                hash as u32,
136                ctx.source_map.clone(),
137                ctx.comments.clone(),
138            ));
139        }
140
141        Ok(())
142    }
143}