Repository: vercel/next.js
Pull request: Turbopack: derive de/serialize for loader config

Previously this manually implemented, and mismatched the derived `Serialize` implementation. Rather, this decouples `Vc<turbo_esregex::EsRegex>` from the deserialized config, instead using basic structs and creating the Vc later in `next-core`.

Test Plan: `pnpm test-dev-turbo test/e2e/app-dir/mdx/mdx.test.ts`

Closes PACK-4679
Closes PACK-4686

Changed files:
filename: crates/next-core/src/next_config.rs
patch: @@ -2,6 +2,7 @@ use anyhow::{Context, Result, bail};
 use rustc_hash::FxHashSet;
 use serde::{Deserialize, Deserializer, Serialize};
 use serde_json::Value as JsonValue;
+use turbo_esregex::EsRegex;
 use turbo_rcstr::RcStr;
 use turbo_tasks::{
     FxIndexMap, NonLocalValue, OperationValue, ResolvedVc, TaskInput, Vc, debug::ValueDebugFormat,
@@ -10,10 +11,8 @@ use turbo_tasks::{
 use turbo_tasks_env::EnvMap;
 use turbo_tasks_fs::FileSystemPath;
 use turbopack::module_options::{
-    LoaderRuleItem, OptionWebpackRules,
-    module_options_context::{
-        ConditionItem, ConditionPath, MdxTransformOptions, OptionWebpackConditions,
-    },
+    ConditionItem, ConditionPath, LoaderRuleItem, OptionWebpackRules,
+    module_options_context::{MdxTransformOptions, OptionWebpackConditions},
 };
 use turbopack_core::{
     issue::{Issue, IssueSeverity, IssueStage, OptionStyledString, StyledString},
@@ -551,48 +550,45 @@ pub struct TurbopackConfig {
     pub module_ids: Option<ModuleIds>,
 }
 
-#[derive(Clone, Debug, PartialEq, Serialize, TraceRawVcs, NonLocalValue)]
-pub struct ConfigConditionItem(ConditionItem);
+#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug)]
+pub struct RegexComponents {
+    source: RcStr,
+    flags: RcStr,
+}
 
-impl<'de> Deserialize<'de> for ConfigConditionItem {
-    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
-    where
-        D: Deserializer<'de>,
-    {
-        #[derive(Deserialize)]
-        struct RegexComponents {
-            source: RcStr,
-            flags: RcStr,
-        }
+#[derive(Clone, PartialEq, Eq, Debug, Serialize, Deserialize)]
+#[serde(tag = "type", content = "value", rename_all = "camelCase")]
+pub enum ConfigConditionPath {
+    Glob(RcStr),
+    Regex(RegexComponents),
+}
 
-        #[derive(Deserialize)]
-        struct ConfigPath {
-            path: RegexOrGlob,
-        }
+impl TryInto<ConditionPath> for ConfigConditionPath {
+    fn try_into(self) -> Result<ConditionPath> {
+        Ok(match self {
+            ConfigConditionPath::Glob(path) => ConditionPath::Glob(path),
+            ConfigConditionPath::Regex(path) => {
+                ConditionPath::Regex(EsRegex::new(&path.source, &path.flags)?.resolved_cell())
+            }
+        })
+    }
 
-        #[derive(Deserialize)]
-        #[serde(tag = "type", rename_all = "lowercase")]
-        enum RegexOrGlob {
-            Regexp { value: RegexComponents },
-            Glob { value: String },
-        }
+    type Error = anyhow::Error;
+}
 
-        let config_path = ConfigPath::deserialize(deserializer)?;
-        let condition_item = match config_path.path {
-            RegexOrGlob::Regexp { value } => {
-                let regex = turbo_esregex::EsRegex::new(&value.source, &value.flags)
-                    .map_err(serde::de::Error::custom)?;
-                ConditionItem {
-                    path: ConditionPath::Regex(regex.resolved_cell()),
-                }
-            }
-            RegexOrGlob::Glob { value } => ConditionItem {
-                path: ConditionPath::Glob(value.into()),
-            },
-        };
+#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
+pub struct ConfigConditionItem {
+    pub path: ConfigConditionPath,
+}
 
-        Ok(ConfigConditionItem(condition_item))
+impl TryInto<ConditionItem> for ConfigConditionItem {
+    fn try_into(self) -> Result<ConditionItem> {
+        Ok(ConditionItem {
+            path: self.path.try_into()?,
+        })
     }
+
+    type Error = anyhow::Error;
 }
 
 #[derive(
@@ -1285,19 +1281,21 @@ impl NextConfig {
     }
 
     #[turbo_tasks::function]
-    pub fn webpack_conditions(&self) -> Vc<OptionWebpackConditions> {
+    pub fn webpack_conditions(&self) -> Result<Vc<OptionWebpackConditions>> {
         let Some(config_conditions) = self.turbopack.as_ref().and_then(|t| t.conditions.as_ref())
         else {
-            return Vc::cell(None);
+            return Ok(Vc::cell(None));
         };
 
-        let conditions = FxIndexMap::from_iter(
-            config_conditions
-                .iter()
-                .map(|(k, v)| (k.clone(), v.0.clone())),
-        );
+        let conditions = config_conditions
+            .iter()
+            .map(|(k, v)| {
+                let item: Result<ConditionItem> = TryInto::<ConditionItem>::try_into((*v).clone());
+                item.map(|item| (k.clone(), item))
+            })
+            .collect::<Result<FxIndexMap<RcStr, ConditionItem>>>()?;
 
-        Vc::cell(Some(ResolvedVc::cell(conditions)))
+        Ok(Vc::cell(Some(ResolvedVc::cell(conditions))))
     }
 
     #[turbo_tasks::function]

filename: packages/next/src/build/swc/index.ts
patch: @@ -891,7 +891,7 @@ function bindingToApi(
       type SerializedConditions = {
         [key: string]: {
           path:
-            | { type: 'regexp'; value: { source: string; flags: string } }
+            | { type: 'regex'; value: { source: string; flags: string } }
             | { type: 'glob'; value: string }
         }
       }
@@ -903,7 +903,7 @@ function bindingToApi(
           path:
             value.path instanceof RegExp
               ? {
-                  type: 'regexp',
+                  type: 'regex',
                   value: { source: value.path.source, flags: value.path.flags },
                 }
               : { type: 'glob', value: value.path },

filename: turbopack/crates/turbo-esregex/src/lib.rs
patch: @@ -18,8 +18,8 @@ pub struct EsRegex {
     delegate: EsRegexImpl,
     // Store the original arguments used to construct
     // this regex to support equality and serialization.
-    pattern: String,
-    flags: String,
+    pub pattern: String,
+    pub flags: String,
 }
 
 #[derive(Debug, Clone)]