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