Repository: vercel/next.js
Pull request: fix(turbopack): Apply `import_map` option of `swc_emotion` correctly

### What?

Preserve `import_map` config specified by the user

### Why?

The default mode (webpack mode) supports `import_map` config, so turbopack should also support it.

### How?

Closes https://github.com/vercel/next.js/issues/71408


Closes PACK-4287

Changed files:
filename: test/e2e/app-dir/emotion-js/app/page.js
patch: @@ -1,5 +1,15 @@
 'use client'
+import { styledCss } from 'import-map-test'
+
+const red = styledCss`
+  color: red;
+`
 
 export default function Page() {
-  return <h1 css={{ color: 'blue' }}>Blue</h1>
+  return (
+    <>
+      <h1 css={{ color: 'blue' }}>Blue</h1>
+      <p css={red}>Red</p>
+    </>
+  )
 }

filename: test/e2e/app-dir/emotion-js/index.test.ts
patch: @@ -26,5 +26,15 @@ describe('app dir - emotion-js', () => {
         ),
       'rgb(0, 0, 255)'
     )
+
+    const el2 = browser.elementByCss('p')
+    expect(await el2.text()).toBe('Red')
+    await check(
+      async () =>
+        await browser.eval(
+          `window.getComputedStyle(document.querySelector('p')).color`
+        ),
+      'rgb(255, 0, 0)'
+    )
   })
 })

filename: test/e2e/app-dir/emotion-js/next.config.js
patch: @@ -2,6 +2,14 @@
 module.exports = {
   reactStrictMode: true,
   compiler: {
-    emotion: true,
+    emotion: {
+      importMap: {
+        'import-map-test': {
+          styledCss: {
+            canonicalImport: ['@emotion/react', 'css'],
+          },
+        },
+      },
+    },
   },
 }

filename: test/e2e/app-dir/emotion-js/node_modules/import-map-test/index.js
patch: @@ -0,0 +1 @@
+export { css as styledCss } from '@emotion/react'

filename: turbopack/crates/turbopack-ecmascript-plugins/src/transform/emotion.rs
patch: @@ -6,15 +6,19 @@ use std::{
 
 use anyhow::Result;
 use async_trait::async_trait;
-use rustc_hash::FxHasher;
+use indexmap::IndexMap;
+use rustc_hash::{FxBuildHasher, FxHasher};
 use serde::{Deserialize, Serialize};
 use swc_core::{
+    atoms::Atom,
     common::util::take::Take,
     ecma::{
         ast::{Module, Program},
         visit::FoldWith,
     },
 };
+use swc_emotion::ImportMap;
+use turbo_rcstr::RcStr;
 use turbo_tasks::{trace::TraceRawVcs, NonLocalValue, OperationValue, ValueDefault, Vc};
 use turbopack_ecmascript::{CustomTransformer, TransformContext};
 
@@ -28,15 +32,45 @@ pub enum EmotionLabelKind {
     Never,
 }
 
-//[TODO]: need to support importmap, there are type mismatch between
-//next.config.js to swc's emotion options
+#[derive(
+    Clone, PartialEq, Eq, Debug, TraceRawVcs, Serialize, Deserialize, NonLocalValue, OperationValue,
+)]
+#[serde(rename_all = "camelCase")]
+pub struct EmotionImportItemConfig {
+    pub canonical_import: EmotionItemSpecifier,
+    pub styled_base_import: Option<EmotionItemSpecifier>,
+}
+
+impl From<&EmotionImportItemConfig> for swc_emotion::ImportItemConfig {
+    fn from(value: &EmotionImportItemConfig) -> Self {
+        swc_emotion::ImportItemConfig {
+            canonical_import: From::from(&value.canonical_import),
+            styled_base_import: value.styled_base_import.as_ref().map(From::from),
+        }
+    }
+}
+
+#[derive(
+    Clone, PartialEq, Eq, Debug, TraceRawVcs, Serialize, Deserialize, NonLocalValue, OperationValue,
+)]
+pub struct EmotionItemSpecifier(pub RcStr, pub RcStr);
+
+impl From<&EmotionItemSpecifier> for swc_emotion::ItemSpecifier {
+    fn from(value: &EmotionItemSpecifier) -> Self {
+        swc_emotion::ItemSpecifier(value.0.as_str().into(), value.1.as_str().into())
+    }
+}
+
+pub type EmotionImportMapValue = IndexMap<RcStr, EmotionImportItemConfig, FxBuildHasher>;
+
 #[turbo_tasks::value(shared, operation)]
 #[derive(Default, Clone, Debug)]
 #[serde(rename_all = "camelCase")]
 pub struct EmotionTransformConfig {
     pub sourcemap: Option<bool>,
     pub label_format: Option<String>,
     pub auto_label: Option<EmotionLabelKind>,
+    pub import_map: Option<IndexMap<RcStr, EmotionImportMapValue, FxBuildHasher>>,
 }
 
 #[turbo_tasks::value_impl]
@@ -78,7 +112,18 @@ impl EmotionTransformer {
             } else {
                 None
             },
-            ..Default::default()
+            import_map: config.import_map.as_ref().map(|map| {
+                map.iter()
+                    .map(|(k, v)| {
+                        (
+                            k.as_str().into(),
+                            swc_emotion::ImportMapValue::from_iter(v.iter().map(|(k, v)| {
+                                (k.as_str().into(), swc_emotion::ImportItemConfig::from(v))
+                            })),
+                        )
+                    })
+                    .collect()
+            }),
         };
 
         Some(EmotionTransformer { config })