Repository: vercel/next.js
Pull request: Turbopack: fix `this` when accessing named properties of namespace

Previously, this namespace-import-to-named-import optimization always caused `this` to be unbound for function calls.
Now, it is preserved (if possible. There's nothing we can do when this is scope hoisted together).
```js
import * as R from "ramda";
import { pipe } from 'ramda'

expect((0, R.pipe)()).toBe(false);
expect(R.pipe()).toBe(true);
expect(pipe()).toBe(false);
```

```js
var __TURBOPACK__imported__module__ramda = __turbopack_context__.i(
  "[project]/turbopack/crates/turbopack-tests/tests/execution/turbopack/side-effects-optimization/import-star/input/node_modules/ramda/pipe.js [test] (ecmascript)"
);
it("should have the correct `this` context", () => {
  expect((0, __TURBOPACK__imported__module__ramda["pipe"])()).toBe(false);
  // this was previously also `(0, ns.pipe)()`
  expect(__TURBOPACK__imported__module__ramda["pipe"]()).toBe(true);
  expect((0, __TURBOPACK__imported__module__ramda["pipe"])()).toBe(false);
});
```

Also has  a small bundle size benefit

Closes PACK-4898

Changed files:
filename: turbopack/crates/turbopack-ecmascript/src/references/esm/binding.rs
patch: @@ -21,9 +21,10 @@ use crate::{
 
 #[derive(Hash, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, TraceRawVcs, NonLocalValue)]
 pub struct EsmBinding {
-    pub reference: ResolvedVc<EsmAssetReference>,
-    pub export: Option<RcStr>,
-    pub ast_path: AstPath,
+    reference: ResolvedVc<EsmAssetReference>,
+    export: Option<RcStr>,
+    ast_path: AstPath,
+    keep_this: bool,
 }
 
 impl EsmBinding {
@@ -36,6 +37,21 @@ impl EsmBinding {
             reference,
             export,
             ast_path,
+            keep_this: false,
+        }
+    }
+
+    /// Where possible, bind the namespace to `this` when the named import is called.
+    pub fn new_keep_this(
+        reference: ResolvedVc<EsmAssetReference>,
+        export: Option<RcStr>,
+        ast_path: AstPath,
+    ) -> Self {
+        EsmBinding {
+            reference,
+            export,
+            ast_path,
+            keep_this: true,
         }
     }
 
@@ -101,12 +117,13 @@ impl EsmBinding {
                 // Any other expression can be replaced with the import accessor.
                 Some(swc_core::ecma::visit::AstParentKind::Expr(_)) => {
                     ast_path.pop();
-                    let in_call = matches!(
-                        ast_path.last(),
-                        Some(swc_core::ecma::visit::AstParentKind::Callee(
-                            CalleeField::Expr
-                        ))
-                    );
+                    let in_call = !self.keep_this
+                        && matches!(
+                            ast_path.last(),
+                            Some(swc_core::ecma::visit::AstParentKind::Callee(
+                                CalleeField::Expr
+                            ))
+                        );
 
                     visitors.push(
                         create_visitor!(exact ast_path, visit_mut_expr(expr: &mut Expr) {

filename: turbopack/crates/turbopack-ecmascript/src/references/mod.rs
patch: @@ -1366,28 +1366,31 @@ pub(crate) async fn analyse_ecmascript_module_internal(
                             options.tree_shaking_mode,
                             Some(TreeShakingMode::ReexportsOnly)
                         ) {
-                            let r_ref = r.await?;
-                            if r_ref.export_name.is_none()
+                            let original_reference = r.await?;
+                            if original_reference.export_name.is_none()
                                 && export.is_some()
                                 && let Some(export) = export
                             {
-                                let r = analysis.add_esm_reference_namespace_resolved(
-                                    esm_reference_index,
-                                    export.clone(),
-                                    || {
-                                        EsmAssetReference::new(
-                                            r_ref.origin,
-                                            r_ref.request,
-                                            r_ref.issue_source.clone(),
-                                            r_ref.annotations.clone(),
-                                            Some(ModulePart::export(export.clone())),
-                                            r_ref.import_externals,
-                                        )
-                                        .resolved_cell()
-                                    },
-                                );
-                                analysis.add_code_gen(EsmBinding::new(
-                                    r,
+                                // Rewrite `import * as ns from 'foo'; foo.bar()` to behave like
+                                // `import {bar} from 'foo'; bar()` for tree shaking purposes.
+                                let named_reference = analysis
+                                    .add_esm_reference_namespace_resolved(
+                                        esm_reference_index,
+                                        export.clone(),
+                                        || {
+                                            EsmAssetReference::new(
+                                                original_reference.origin,
+                                                original_reference.request,
+                                                original_reference.issue_source.clone(),
+                                                original_reference.annotations.clone(),
+                                                Some(ModulePart::export(export.clone())),
+                                                original_reference.import_externals,
+                                            )
+                                            .resolved_cell()
+                                        },
+                                    );
+                                analysis.add_code_gen(EsmBinding::new_keep_this(
+                                    named_reference,
                                     Some(export),
                                     ast_path.into(),
                                 ));

filename: turbopack/crates/turbopack-tests/tests/execution/turbopack/side-effects-optimization/import-star/input/index.js
patch: @@ -1,7 +1,11 @@
 import * as R from 'ramda'
+import { pipe } from 'ramda'
 
-console.log((0, R.pipe)('a', 'b', 'c'))
-console.log(R.pipe('a', 'b', 'c'))
+it('should have the correct `this` context', () => {
+  expect((0, R.pipe)()).toBe(false)
+  expect(R.pipe()).toBe(true)
+  expect(pipe()).toBe(false)
+})
 
 it('should import only pipe.js', () => {
   const modules = Object.keys(__turbopack_modules__)

filename: turbopack/crates/turbopack-tests/tests/execution/turbopack/side-effects-optimization/import-star/input/node_modules/ramda/index.js
patch: @@ -1 +1 @@
-export * from './pipe';
+export * from './pipe'

filename: turbopack/crates/turbopack-tests/tests/execution/turbopack/side-effects-optimization/import-star/input/node_modules/ramda/pipe.js
patch: @@ -1,5 +1,3 @@
-
-
-export const pipe= (a,b,c)=> {
-  console.log(a,b,c)
+export function pipe() {
+  return this != null
 }