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