Repository: vercel/next.js
Pull request: fix(turbopack): Fix usage of `HELPERS` of SWC

### What?

Convert `Helpers` to `HelperData`, which is `: Send + Sync` so safe to pass around between threads.

### Why?

It's not `Send + Sync`, so we should not pass it between threads like this

Closes PACK-4875

Changed files:
filename: turbopack/crates/turbopack-ecmascript/src/parse.rs
patch: @@ -390,6 +390,8 @@ async fn parse_file_content(
                 EcmascriptModuleAssetType::Typescript { .. }
                     | EcmascriptModuleAssetType::TypescriptDeclaration
             );
+
+            let helpers=Helpers::new(true);
             let span = tracing::trace_span!("swc_resolver").entered();
 
             parsed_program.visit_mut_with(&mut resolver(
@@ -414,14 +416,19 @@ async fn parse_file_content(
             parsed_program.mutate(swc_core::ecma::lints::rules::lint_pass(rules));
             drop(span);
 
-            parsed_program.mutate(swc_core::ecma::transforms::proposal::explicit_resource_management::explicit_resource_management());
+            HELPERS.set(&helpers, || {
+                parsed_program.mutate(
+                    swc_core::ecma::transforms::proposal::explicit_resource_management::explicit_resource_management(),
+                );
+            });
 
             let var_with_ts_declare = if is_typescript {
                 VarDeclWithTsDeclareCollector::collect(&parsed_program)
             } else {
                 FxHashSet::default()
             };
 
+            let mut helpers = helpers.data();
             let transform_context = TransformContext {
                 comments: &comments,
                 source_map: &source_map,
@@ -436,8 +443,8 @@ async fn parse_file_content(
             let span = tracing::trace_span!("transforms");
             async {
                 for transform in transforms.iter() {
-                    transform
-                        .apply(&mut parsed_program, &transform_context)
+                    helpers = transform
+                        .apply(&mut parsed_program, &transform_context, helpers)
                         .await?;
                 }
                 anyhow::Ok(())
@@ -461,9 +468,12 @@ async fn parse_file_content(
                 return Ok(ParseResult::Unparseable { messages });
             }
 
-            parsed_program.visit_mut_with(
-                &mut swc_core::ecma::transforms::base::helpers::inject_helpers(unresolved_mark),
-            );
+            let helpers = Helpers::from_data(helpers);
+            HELPERS.set(&helpers, || {
+                parsed_program.mutate(
+                    swc_core::ecma::transforms::base::helpers::inject_helpers(unresolved_mark),
+                );
+            });
 
             let eval_context = EvalContext::new(
                 &parsed_program,
@@ -486,7 +496,7 @@ async fn parse_file_content(
         },
         |f, cx| {
             GLOBALS.set(globals_ref, || {
-                HANDLER.set(&handler, || HELPERS.set(&Helpers::new(true), || f.poll(cx)))
+                HANDLER.set(&handler, || f.poll(cx))
             })
         },
     )

filename: turbopack/crates/turbopack-ecmascript/src/transform/mod.rs
patch: @@ -8,9 +8,16 @@ use swc_core::{
     base::SwcComments,
     common::{Mark, SourceMap, comments::Comments},
     ecma::{
-        ast::{ModuleItem, Program},
+        ast::{ModuleItem, Pass, Program},
         preset_env::{self, Targets},
-        transforms::{base::assumptions::Assumptions, optimization::inline_globals, react::react},
+        transforms::{
+            base::{
+                assumptions::Assumptions,
+                helpers::{HELPERS, HelperData, Helpers},
+            },
+            optimization::inline_globals,
+            react::react,
+        },
     },
     quote,
 };
@@ -116,26 +123,36 @@ pub struct TransformContext<'a> {
 }
 
 impl EcmascriptInputTransform {
-    pub async fn apply(&self, program: &mut Program, ctx: &TransformContext<'_>) -> Result<()> {
+    pub async fn apply(
+        &self,
+        program: &mut Program,
+        ctx: &TransformContext<'_>,
+        helpers: HelperData,
+    ) -> Result<HelperData> {
         let &TransformContext {
             comments,
             source_map,
             top_level_mark,
             unresolved_mark,
             ..
         } = ctx;
-        match self {
+
+        Ok(match self {
             EcmascriptInputTransform::GlobalTypeofs { window_value } => {
                 let mut typeofs: FxHashMap<Atom, Atom> = Default::default();
                 typeofs.insert(Atom::from("window"), Atom::from(&**window_value));
 
-                program.mutate(inline_globals(
-                    unresolved_mark,
-                    Default::default(),
-                    Default::default(),
-                    Default::default(),
-                    Arc::new(typeofs),
-                ));
+                apply_transform(
+                    program,
+                    helpers,
+                    inline_globals(
+                        unresolved_mark,
+                        Default::default(),
+                        Default::default(),
+                        Default::default(),
+                        Arc::new(typeofs),
+                    ),
+                )
             }
             EcmascriptInputTransform::React {
                 development,
@@ -178,13 +195,17 @@ impl EcmascriptInputTransform {
 
                 // Explicit type annotation to ensure that we don't duplicate transforms in the
                 // final binary
-                program.mutate(react::<&dyn Comments>(
-                    source_map.clone(),
-                    Some(&comments),
-                    config,
-                    top_level_mark,
-                    unresolved_mark,
-                ));
+                let helpers = apply_transform(
+                    program,
+                    helpers,
+                    react::<&dyn Comments>(
+                        source_map.clone(),
+                        Some(&comments),
+                        config,
+                        top_level_mark,
+                        unresolved_mark,
+                    ),
+                );
 
                 if *refresh {
                     let stmt = quote!(
@@ -204,6 +225,8 @@ impl EcmascriptInputTransform {
                         }
                     }
                 }
+
+                helpers
             }
             EcmascriptInputTransform::PresetEnv(env) => {
                 let versions = env.runtime_versions().await?;
@@ -217,20 +240,28 @@ impl EcmascriptInputTransform {
 
                 // Explicit type annotation to ensure that we don't duplicate transforms in the
                 // final binary
-                program.mutate(preset_env::transform_from_env::<&'_ dyn Comments>(
-                    top_level_mark,
-                    Some(&comments),
-                    config,
-                    Assumptions::default(),
-                ));
+                apply_transform(
+                    program,
+                    helpers,
+                    preset_env::transform_from_env::<&'_ dyn Comments>(
+                        top_level_mark,
+                        Some(&comments),
+                        config,
+                        Assumptions::default(),
+                    ),
+                )
             }
             EcmascriptInputTransform::TypeScript {
                 // TODO(WEB-1213)
                 use_define_for_class_fields: _use_define_for_class_fields,
             } => {
                 use swc_core::ecma::transforms::typescript::typescript;
                 let config = Default::default();
-                program.mutate(typescript(config, unresolved_mark, top_level_mark));
+                apply_transform(
+                    program,
+                    helpers,
+                    typescript(config, unresolved_mark, top_level_mark),
+                )
             }
             EcmascriptInputTransform::Decorators {
                 is_legacy,
@@ -246,16 +277,25 @@ impl EcmascriptInputTransform {
                     ..Default::default()
                 };
 
-                program.mutate(decorators(config));
+                apply_transform(program, helpers, decorators(config))
             }
             EcmascriptInputTransform::Plugin(transform) => {
-                transform.await?.transform(program, ctx).await?
+                // We cannot pass helpers to plugins, so we return them as is
+                transform.await?.transform(program, ctx).await?;
+                helpers
             }
-        }
-        Ok(())
+        })
     }
 }
 
+fn apply_transform(program: &mut Program, helpers: HelperData, op: impl Pass) -> HelperData {
+    let helpers = Helpers::from_data(helpers);
+    HELPERS.set(&helpers, || {
+        program.mutate(op);
+    });
+    helpers.data()
+}
+
 pub fn remove_shebang(program: &mut Program) {
     match program {
         Program::Module(m) => {