turbopack_ecmascript_plugins/transform/
emotion.rs1#![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 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 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}