Repository: oxc-project/oxc
Pull request #10347: fix(transformer/module_runner): hoist export
Author: login: hi-ogawa
Labels: name: C-bug
description: Category - Bug
name: A-transformer
description: Area - Transformer / Transpiler
Created: 2025-04-10T05:42:56Z | Merged: 2025-04-14T02:42:25Z

Aligning with https://github.com/vitejs/vite/pull/18983

I manually verified the test case output matches with the ones in Vite repo. 

~I add a new TODO about "export default" getter, but this difference existed already. I plan to fix this separately.~ (EDIT: It turned out Vite's transform is the wrong one https://github.com/vitejs/vite/issues/19834.)


Diff:
diff --git a/crates/oxc_transformer/src/plugins/module_runner_transform.rs b/crates/oxc_transformer/src/plugins/module_runner_transform.rs
index ff31c26c9ef95..dab63d255ae90 100644
--- a/crates/oxc_transformer/src/plugins/module_runner_transform.rs
+++ b/crates/oxc_transformer/src/plugins/module_runner_transform.rs
@@ -138,28 +138,31 @@ impl<'a> ModuleRunnerTransform<'a> {
         let mut new_stmts: ArenaVec<'a, Statement<'a>> =
             ctx.ast.vec_with_capacity(program.body.len() * 2);
 
-        let mut hoist_index = None;
         let mut hoist_imports = Vec::with_capacity(program.body.len());
+        let mut hoist_exports = Vec::with_capacity(program.body.len());
 
         for stmt in program.body.drain(..) {
             match stmt {
                 Statement::ImportDeclaration(import) => {
                     let ImportDeclaration { span, source, specifiers, .. } = import.unbox();
                     let import_statement = self.transform_import(span, source, specifiers, ctx);
-                    // Needs to hoist import statements to the above of the other statements
-                    if hoist_index.is_some() {
-                        hoist_imports.push(import_statement);
-                    } else {
-                        new_stmts.push(import_statement);
-                    }
-                    continue;
+                    hoist_imports.push(import_statement);
                 }
                 Statement::ExportAllDeclaration(export) => {
-                    self.transform_export_all_declaration(&mut new_stmts, export, ctx);
-                    continue;
+                    self.transform_export_all_declaration(
+                        &mut new_stmts,
+                        &mut hoist_exports,
+                        export,
+                        ctx,
+                    );
                 }
                 Statement::ExportNamedDeclaration(export) => {
-                    self.transform_export_named_declaration(&mut new_stmts, export, ctx);
+                    self.transform_export_named_declaration(
+                        &mut new_stmts,
+                        &mut hoist_exports,
+                        export,
+                        ctx,
+                    );
                 }
                 Statement::ExportDefaultDeclaration(export) => {
                     Self::transform_export_default_declaration(&mut new_stmts, export, ctx);
@@ -168,18 +171,9 @@ impl<'a> ModuleRunnerTransform<'a> {
                     new_stmts.push(stmt);
                 }
             }
-
-            // Found that non-import statements, we should start hoisting import statements after this
-            if hoist_index.is_none() {
-                hoist_index.replace(new_stmts.len() - 1);
-            }
         }
 
-        if let Some(index) = hoist_index {
-            if !hoist_imports.is_empty() {
-                new_stmts.splice(index..index, hoist_imports);
-            }
-        }
+        new_stmts.splice(0..0, hoist_exports.into_iter().chain(hoist_imports));
 
         program.body = new_stmts;
     }
@@ -354,8 +348,8 @@ impl<'a> ModuleRunnerTransform<'a> {
     /// ```js
     /// export function foo() {}
     /// // to
-    /// function foo() {}
     /// Object.defineProperty(__vite_ssr_exports__, 'foo', { enumerable: true, configurable: true, get() { return foo; }});
+    /// function foo() {}
     /// ```
     ///
     /// - Export specifiers
@@ -370,9 +364,9 @@ impl<'a> ModuleRunnerTransform<'a> {
     /// ```js
     /// export { foo, bar } from 'vue';
     /// // to
-    /// const __vite_ssr_import_0__ = await __vite_ssr_import__('vue', { importedNames: ['foo', 'bar'] });
     /// Object.defineProperty(__vite_ssr_exports__, 'foo', { enumerable: true, configurable: true, get() { return __vite_ssr_import_0__.foo; }});
     /// Object.defineProperty(__vite_ssr_exports__, 'bar', { enumerable: true, configurable: true, get() { return __vite_ssr_import_0__.bar; }});
+    /// const __vite_ssr_import_0__ = await __vite_ssr_import__('vue', { importedNames: ['foo', 'bar'] });
     /// ```
     ///
     /// - Export specifiers with renaming
@@ -384,6 +378,7 @@ impl<'a> ModuleRunnerTransform<'a> {
     fn transform_export_named_declaration(
         &mut self,
         new_stmts: &mut ArenaVec<'a, Statement<'a>>,
+        hoist_exports: &mut Vec<Statement<'a>>,
         export: ArenaBox<'a, ExportNamedDeclaration<'a>>,
         ctx: &mut TraverseCtx<'a>,
     ) {
@@ -397,7 +392,7 @@ impl<'a> ModuleRunnerTransform<'a> {
                     variable.bound_names(&mut |ident| {
                         let binding = BoundIdentifier::from_binding_ident(ident);
                         let ident = binding.create_read_expression(ctx);
-                        new_stmts.push(Self::create_export(span, ident, binding.name, ctx));
+                        hoist_exports.push(Self::create_export(span, ident, binding.name, ctx));
                     });
                     // Should be inserted before the exports
                     new_stmts.insert(new_stmts_index, Statement::from(declaration));
@@ -421,7 +416,8 @@ impl<'a> ModuleRunnerTransform<'a> {
                     );
                 }
             };
-            new_stmts.extend([Statement::from(declaration), export_expression]);
+            new_stmts.push(Statement::from(declaration));
+            hoist_exports.push(export_expression);
         } else {
             // If the source is Some, then we need to import the module first and then export them.
             let import_binding = source.map(|source| {
@@ -441,7 +437,7 @@ impl<'a> ModuleRunnerTransform<'a> {
                 binding
             });
 
-            new_stmts.extend(specifiers.into_iter().map(|specifier| {
+            hoist_exports.extend(specifiers.into_iter().map(|specifier| {
                 let ExportSpecifier { span, exported, local, .. } = specifier;
                 let expr = if let Some(import_binding) = &import_binding {
                     let object = import_binding.create_read_expression(ctx);
@@ -478,12 +474,13 @@ impl<'a> ModuleRunnerTransform<'a> {
     /// ```js
     /// export * as foo from 'vue';
     /// // to
-    /// const __vite_ssr_import_0__ = await __vite_ssr_import__('vue');
     /// Object.defineProperty(__vite_ssr_exports__, 'foo', { enumerable: true, configurable: true, get(){ return __vite_ssr_import_0__ } });
+    /// const __vite_ssr_import_0__ = await __vite_ssr_import__('vue');
     /// ```
     fn transform_export_all_declaration(
         &mut self,
         new_stmts: &mut ArenaVec<'a, Statement<'a>>,
+        hoist_exports: &mut Vec<Statement<'a>>,
         export: ArenaBox<'a, ExportAllDeclaration<'a>>,
         ctx: &mut TraverseCtx<'a>,
     ) {
@@ -497,18 +494,21 @@ impl<'a> ModuleRunnerTransform<'a> {
 
         let ident = binding.create_read_expression(ctx);
 
-        let export = if let Some(exported) = exported {
+        if let Some(exported) = exported {
             // `export * as foo from 'vue'` ->
             // `Object.defineProperty(__vite_ssr_exports__, 'foo', { enumerable: true, configurable: true, get(){ return __vite_ssr_import_0__ } });`
-            Self::create_export(span, ident, exported.name(), ctx)
+            let export = Self::create_export(span, ident, exported.name(), ctx);
+            new_stmts.push(import);
+            hoist_exports.push(export);
         } else {
             let callee = ctx.ast.expression_identifier(SPAN, SSR_EXPORT_ALL_KEY);
             let arguments = ctx.ast.vec1(Argument::from(ident));
             // `export * from 'vue'` -> `__vite_ssr_exportAll__(__vite_ssr_import_0__);`
             let call = ctx.ast.expression_call(SPAN, callee, NONE, arguments, false);
-            ctx.ast.statement_expression(span, call)
-        };
-        new_stmts.extend([import, export]);
+            let export = ctx.ast.statement_expression(span, call);
+            // we cannot hoist this case, so keep at the same position
+            new_stmts.extend([import, export]);
+        }
     }
 
     /// Transform export default declaration (`export default function foo() {}`).
@@ -870,6 +870,7 @@ mod test {
             .code
     }
 
+    #[track_caller]
     fn test_same(source_text: &str, expected: &str) {
         let expected = format_expected_code(expected);
         let result = transform(source_text, false).unwrap().code;
@@ -880,6 +881,7 @@ mod test {
         }
     }
 
+    #[track_caller]
     fn test_same_jsx(source_text: &str, expected: &str) {
         let expected = format_expected_code(expected);
         let result = transform(source_text, true).unwrap().code;
@@ -890,6 +892,7 @@ mod test {
         }
     }
 
+    #[track_caller]
     fn test_same_and_deps(source_text: &str, expected: &str, deps: &[&str], dynamic_deps: &[&str]) {
         let expected = format_expected_code(expected);
         let TransformReturn { code, deps: result_deps, dynamic_deps: result_dynamic_deps } =
@@ -953,14 +956,16 @@ function foo() {
     fn export_function_declaration() {
         test_same(
             "export function foo() {}",
-            "function foo() {}
+            "
 Object.defineProperty(__vite_ssr_exports__, 'foo', {
   enumerable: true,
   configurable: true,
   get() {
     return foo;
   }
-});",
+});
+function foo() {}
+",
         );
     }
 
@@ -968,14 +973,16 @@ Object.defineProperty(__vite_ssr_exports__, 'foo', {
     fn export_class_declaration() {
         test_same(
             "export class foo {}",
-            "class foo {}
+            "
 Object.defineProperty(__vite_ssr_exports__, 'foo', {
   enumerable: true,
   configurable: true,
   get() {
     return foo;
   }
-});",
+});
+class foo {}
+",
         );
     }
 
@@ -983,7 +990,7 @@ Object.defineProperty(__vite_ssr_exports__, 'foo', {
     fn export_var_declaration() {
         test_same(
             "export const a = 1, b = 2",
-            "const a = 1, b = 2;
+            "
 Object.defineProperty(__vite_ssr_exports__, 'a', {
   enumerable: true,
   configurable: true,
@@ -997,7 +1004,9 @@ Object.defineProperty(__vite_ssr_exports__, 'b', {
   get() {
     return b;
   }
-});",
+});
+const a = 1, b = 2;
+",
         );
     }
 
@@ -1005,7 +1014,7 @@ Object.defineProperty(__vite_ssr_exports__, 'b', {
     fn export_named() {
         test_same(
             "const a = 1, b = 2; export { a, b as c }",
-            "const a = 1, b = 2;
+            "
 Object.defineProperty(__vite_ssr_exports__, 'a', {
   enumerable: true,
   configurable: true,
@@ -1019,7 +1028,9 @@ Object.defineProperty(__vite_ssr_exports__, 'c', {
   get() {
     return b;
   }
-});",
+});
+const a = 1, b = 2;
+",
         );
     }
 
@@ -1027,7 +1038,7 @@ Object.defineProperty(__vite_ssr_exports__, 'c', {
     fn export_named_from() {
         test_same(
             "export { ref, computed as c } from 'vue'",
-            "const __vite_ssr_import_0__ = await __vite_ssr_import__('vue', { importedNames: ['ref', 'computed'] });
+            "
 Object.defineProperty(__vite_ssr_exports__, 'ref', {
   enumerable: true,
   configurable: true,
@@ -1041,7 +1052,9 @@ Object.defineProperty(__vite_ssr_exports__, 'c', {
   get() {
     return __vite_ssr_import_0__.computed;
   }
-});",
+});
+const __vite_ssr_import_0__ = await __vite_ssr_import__('vue', { importedNames: ['ref', 'computed'] });
+",
         );
     }
 
@@ -1053,7 +1066,6 @@ Object.defineProperty(__vite_ssr_exports__, 'c', {
             export { createApp }
             ",
             "
-            const __vite_ssr_import_0__ = await __vite_ssr_import__('vue', { importedNames: ['createApp'] });
             Object.defineProperty(__vite_ssr_exports__, 'createApp', {
               enumerable: true,
               configurable: true,
@@ -1061,6 +1073,7 @@ Object.defineProperty(__vite_ssr_exports__, 'c', {
                 return __vite_ssr_import_0__.createApp;
               }
             });
+            const __vite_ssr_import_0__ = await __vite_ssr_import__('vue', { importedNames: ['createApp'] });
             ",
         );
     }
@@ -1080,14 +1093,16 @@ __vite_ssr_exportAll__(__vite_ssr_import_1__);",
     fn export_star_as_from() {
         test_same(
             "export * as foo from 'vue'",
-            "const __vite_ssr_import_0__ = await __vite_ssr_import__('vue');
+            "
 Object.defineProperty(__vite_ssr_exports__, 'foo', {
   enumerable: true,
   configurable: true,
   get() {
     return __vite_ssr_import_0__;
   }
-});",
+});
+const __vite_ssr_import_0__ = await __vite_ssr_import__('vue');
+",
         );
     }
 
@@ -1095,28 +1110,32 @@ Object.defineProperty(__vite_ssr_exports__, 'foo', {
     fn re_export_by_imported_name() {
         test_same(
             "import * as foo from 'foo'; export * as foo from 'foo'",
-            "const __vite_ssr_import_0__ = await __vite_ssr_import__('foo');
-const __vite_ssr_import_1__ = await __vite_ssr_import__('foo');
+            "
 Object.defineProperty(__vite_ssr_exports__, 'foo', {
   enumerable: true,
   configurable: true,
   get() {
     return __vite_ssr_import_1__;
   }
-});",
+});
+const __vite_ssr_import_0__ = await __vite_ssr_import__('foo');
+const __vite_ssr_import_1__ = await __vite_ssr_import__('foo');
+",
         );
 
         test_same(
             "import { foo } from 'foo'; export { foo } from 'foo'",
-            "const __vite_ssr_import_0__ = await __vite_ssr_import__('foo', { importedNames: ['foo'] });
-const __vite_ssr_import_1__ = await __vite_ssr_import__('foo', { importedNames: ['foo'] });
+            "
 Object.defineProperty(__vite_ssr_exports__, 'foo', {
   enumerable: true,
   configurable: true,
   get() {
     return __vite_ssr_import_1__.foo;
   }
-});",
+});
+const __vite_ssr_import_0__ = await __vite_ssr_import__('foo', { importedNames: ['foo'] });
+const __vite_ssr_import_1__ = await __vite_ssr_import__('foo', { importedNames: ['foo'] });
+",
         );
     }
 
@@ -1124,38 +1143,44 @@ Object.defineProperty(__vite_ssr_exports__, 'foo', {
     fn export_arbitrary_namespace() {
         test_same(
             "export * as \"arbitrary string\" from 'vue'",
-            "const __vite_ssr_import_0__ = await __vite_ssr_import__('vue');
+            "
 Object.defineProperty(__vite_ssr_exports__, 'arbitrary string', {
   enumerable: true,
   configurable: true,
   get() {
     return __vite_ssr_import_0__;
   }
-});",
+});
+const __vite_ssr_import_0__ = await __vite_ssr_import__('vue');
+",
         );
 
         test_same(
             "const something = \"Something\"; export { something as \"arbitrary string\" }",
-            "const something = 'Something';
+            "
 Object.defineProperty(__vite_ssr_exports__, 'arbitrary string', {
   enumerable: true,
   configurable: true,
   get() {
     return something;
   }
-});",
+});
+const something = 'Something';
+",
         );
 
         test_same(
             "export { \"arbitrary string2\" as \"arbitrary string\" } from 'vue'",
-            "const __vite_ssr_import_0__ = await __vite_ssr_import__('vue', { importedNames: ['arbitrary string2'] });
+            "
 Object.defineProperty(__vite_ssr_exports__, 'arbitrary string', {
   enumerable: true,
   configurable: true,
   get() {
     return __vite_ssr_import_0__['arbitrary string2'];
   }
-});",
+});
+const __vite_ssr_import_0__ = await __vite_ssr_import__('vue', { importedNames: ['arbitrary string2'] });
+",
         );
     }
 
@@ -1166,11 +1191,14 @@ Object.defineProperty(__vite_ssr_exports__, 'arbitrary string', {
 
     #[test]
     fn export_then_import_minified() {
+        // `import` is hoisted but `export * from` is not. this is an expected behavior.
         test_same(
             "export * from 'vue';import {createApp} from 'vue'",
-            "const __vite_ssr_import_0__ = await __vite_ssr_import__('vue');
+            "
+const __vite_ssr_import_1__ = await __vite_ssr_import__('vue', { importedNames: ['createApp'] });
+const __vite_ssr_import_0__ = await __vite_ssr_import__('vue');
 __vite_ssr_exportAll__(__vite_ssr_import_0__);
-const __vite_ssr_import_1__ = await __vite_ssr_import__('vue', { importedNames: ['createApp'] });",
+",
         );
     }
 
@@ -1192,14 +1220,16 @@ __vite_ssr_import_0__.default.resolve('server.js');",
     fn dynamic_import() {
         test_same_and_deps(
             "export const i = () => import('./foo')",
-            "const i = () => __vite_ssr_dynamic_import__('./foo');
+            "
 Object.defineProperty(__vite_ssr_exports__, 'i', {
   enumerable: true,
   configurable: true,
   get() {
     return i;
   }
-});",
+});
+const i = () => __vite_ssr_dynamic_import__('./foo');
+",
             &[],
             &["./foo"],
         );
@@ -1406,23 +1436,23 @@ class A extends __vite_ssr_import_0__.Foo {}",
             export class B extends Foo {}
             ",
             "
-            const __vite_ssr_import_0__ = await __vite_ssr_import__('./dependency', { importedNames: ['Foo'] });
-            class A extends __vite_ssr_import_0__.Foo {}
-            Object.defineProperty(__vite_ssr_exports__, 'default', {
+            Object.defineProperty(__vite_ssr_exports__, 'B', {
               enumerable: true,
               configurable: true,
               get() {
-                return A;
+                return B;
               }
             });
-            class B extends __vite_ssr_import_0__.Foo {}
-            Object.defineProperty(__vite_ssr_exports__, 'B', {
+            const __vite_ssr_import_0__ = await __vite_ssr_import__('./dependency', { importedNames: ['Foo'] });
+            class A extends __vite_ssr_import_0__.Foo {}
+            Object.defineProperty(__vite_ssr_exports__, 'default', {
               enumerable: true,
               configurable: true,
               get() {
-                return B;
+                return A;
               }
             });
+            class B extends __vite_ssr_import_0__.Foo {}
             ",
         );
     }
@@ -1453,22 +1483,24 @@ foo.prototype = Object.prototype;",
         // default named classes
         test_same(
             "export default class A {}\nexport class B extends A {}",
-            "class A {}
-Object.defineProperty(__vite_ssr_exports__, 'default', {
+            "
+Object.defineProperty(__vite_ssr_exports__, 'B', {
   enumerable: true,
   configurable: true,
   get() {
-    return A;
+    return B;
   }
 });
-class B extends A {}
-Object.defineProperty(__vite_ssr_exports__, 'B', {
+class A {}
+Object.defineProperty(__vite_ssr_exports__, 'default', {
   enumerable: true,
   configurable: true,
   get() {
-    return B;
+    return A;
   }
-});",
+});
+class B extends A {}
+",
         );
     }
 
@@ -1769,8 +1801,7 @@ function Bar({ Slot = __vite_ssr_import_0__.default.createElement(__vite_ssr_imp
             "export function fn1() {
     }export function fn2() {
     }",
-            "function fn1() {
-}
+            "
 Object.defineProperty(__vite_ssr_exports__, 'fn1', {
   enumerable: true,
   configurable: true,
@@ -1778,15 +1809,16 @@ Object.defineProperty(__vite_ssr_exports__, 'fn1', {
     return fn1;
   }
 });
-function fn2() {
-}
 Object.defineProperty(__vite_ssr_exports__, 'fn2', {
   enumerable: true,
   configurable: true,
   get() {
     return fn2;
   }
-});",
+});
+function fn1() {}
+function fn2() {}
+",
         );
     }
 
@@ -1875,7 +1907,15 @@ console.log(\"it can parse the hashbang\");",
         }
       }
     }",
-            "const __vite_ssr_import_0__ = await __vite_ssr_import__('foobar', { importedNames: ['foo', 'bar'] });
+            "
+Object.defineProperty(__vite_ssr_exports__, 'Test', {
+  enumerable: true,
+  configurable: true,
+  get() {
+    return Test;
+  }
+});
+const __vite_ssr_import_0__ = await __vite_ssr_import__('foobar', { importedNames: ['foo', 'bar'] });
 if (false) {
   const foo = 'foo';
   console.log(foo);
@@ -1900,13 +1940,7 @@ class Test {
     }
   }
 }
-Object.defineProperty(__vite_ssr_exports__, 'Test', {
-  enumerable: true,
-  configurable: true,
-  get() {
-    return Test;
-  }
-});",
+",
         );
     }
 
@@ -2024,12 +2058,14 @@ __vite_ssr_dynamic_import__('./bar.json', { with: { type: 'json' } });",
         // and `foo` is `bar`, the logging order should be:
         // "mod a", "mod foo", "mod b", "bar1", "bar2"
         test_same(
-            "console.log(foo + 1)
+            "
+    console.log(foo + 1)
     export * from './a'
     import { foo } from './foo'
     export * from './b'
     console.log(foo + 2)",
-            "const __vite_ssr_import_1__ = await __vite_ssr_import__('./foo', { importedNames: ['foo']});
+            "
+const __vite_ssr_import_1__ = await __vite_ssr_import__('./foo', { importedNames: ['foo']});
 console.log(__vite_ssr_import_1__.foo + 1);
 const __vite_ssr_import_0__ = await __vite_ssr_import__('./a');__vite_ssr_exportAll__(__vite_ssr_import_0__);
 const __vite_ssr_import_2__ = await __vite_ssr_import__('./b');__vite_ssr_exportAll__(__vite_ssr_import_2__);
@@ -2040,13 +2076,13 @@ console.log(__vite_ssr_import_1__.foo + 2);",
     #[test]
     fn identity_function_is_declared_before_used() {
         test_same(
-            "import { foo } from './foo'
+            "
+    import { foo } from './foo'
     export default foo()
     export * as bar from './bar'
-    console.log(bar)",
-            "const __vite_ssr_import_0__ = await __vite_ssr_import__('./foo', { importedNames: ['foo'] });
-__vite_ssr_exports__.default = (0, __vite_ssr_import_0__.foo)();
-const __vite_ssr_import_1__ = await __vite_ssr_import__('./bar');
+    console.log(bar)
+",
+            "
 Object.defineProperty(__vite_ssr_exports__, 'bar', {
   enumerable: true,
   configurable: true,
@@ -2054,7 +2090,11 @@ Object.defineProperty(__vite_ssr_exports__, 'bar', {
     return __vite_ssr_import_1__;
   }
 });
-console.log(bar);",
+const __vite_ssr_import_0__ = await __vite_ssr_import__('./foo', { importedNames: ['foo'] });
+__vite_ssr_exports__.default = (0, __vite_ssr_import_0__.foo)();
+const __vite_ssr_import_1__ = await __vite_ssr_import__('./bar');
+console.log(bar);
+",
         );
     }
 
@@ -2230,8 +2270,6 @@ const c = () => {
         export * as A from "a";
         "#;
         let expected = "
-        const __vite_ssr_import_0__ = await __vite_ssr_import__('a', { importedNames: ['default'] });
-        const __vite_ssr_import_1__ = await __vite_ssr_import__('b', { importedNames: ['b'] });
         Object.defineProperty(__vite_ssr_exports__, 'b', {
           enumerable: true,
           configurable: true,
@@ -2239,18 +2277,13 @@ const c = () => {
                   return __vite_ssr_import_1__.b;
           }
         });
-        const __vite_ssr_import_2__ = await __vite_ssr_import__('c');
-        __vite_ssr_exportAll__(__vite_ssr_import_2__);
-        const __vite_ssr_import_3__ = await __vite_ssr_import__('d');
         Object.defineProperty(__vite_ssr_exports__, 'd', {
-        enumerable: true,
-        configurable: true,
-        get() {
-                return __vite_ssr_import_3__;
-        }
+          enumerable: true,
+          configurable: true,
+          get() {
+                  return __vite_ssr_import_3__;
+          }
         });
-        __vite_ssr_dynamic_import__('e');
-        const __vite_ssr_import_4__ = await __vite_ssr_import__('a');
         Object.defineProperty(__vite_ssr_exports__, 'A', {
                enumerable: true,
                configurable: true,
@@ -2258,6 +2291,13 @@ const c = () => {
                        return __vite_ssr_import_4__;
                }
         });
+        const __vite_ssr_import_0__ = await __vite_ssr_import__('a', { importedNames: ['default'] });
+        const __vite_ssr_import_1__ = await __vite_ssr_import__('b', { importedNames: ['b'] });
+        const __vite_ssr_import_2__ = await __vite_ssr_import__('c');
+        __vite_ssr_exportAll__(__vite_ssr_import_2__);
+        const __vite_ssr_import_3__ = await __vite_ssr_import__('d');
+        __vite_ssr_dynamic_import__('e');
+        const __vite_ssr_import_4__ = await __vite_ssr_import__('a');
         ";
         test_same_and_deps(code, expected, &["a", "b", "c", "d"], &["e"]);
     }
diff --git a/napi/transform/test/moduleRunnerTransform.test.ts b/napi/transform/test/moduleRunnerTransform.test.ts
index 1cba5d9e466ff..c052bc2cab906 100644
--- a/napi/transform/test/moduleRunnerTransform.test.ts
+++ b/napi/transform/test/moduleRunnerTransform.test.ts
@@ -3,18 +3,18 @@ import { moduleRunnerTransform } from '../index';
 
 describe('moduleRunnerTransform', () => {
   test('dynamic import', async () => {
-    const result = await moduleRunnerTransform('index.js', `export const i = () => import('./foo')`);
+    const result = moduleRunnerTransform('index.js', `export const i = () => import('./foo')`);
     expect(result?.code).toMatchInlineSnapshot(`
-			"const i = () => __vite_ssr_dynamic_import__("./foo");
-			Object.defineProperty(__vite_ssr_exports__, "i", {
-				enumerable: true,
-				configurable: true,
-				get() {
-					return i;
-				}
-			});
-			"
-		`);
+      "Object.defineProperty(__vite_ssr_exports__, "i", {
+      	enumerable: true,
+      	configurable: true,
+      	get() {
+      		return i;
+      	}
+      });
+      const i = () => __vite_ssr_dynamic_import__("./foo");
+      "
+    `);
     expect(result?.deps).toEqual([]);
     expect(result?.dynamicDeps).toEqual(['./foo']);
   });
@@ -32,7 +32,7 @@ describe('moduleRunnerTransform', () => {
 
     expect(map).toMatchInlineSnapshot(`
       {
-        "mappings": "AAAO,MAAM,IAAI;AAAjB",
+        "mappings": "AAAA;;;;;;;AAAO,MAAM,IAAI",
         "names": [],
         "sources": [
           "index.js",


Reviews:
user: login: Dunqing
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-11T08:53:31Z
user: login: hi-ogawa
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-11T09:28:52Z
user: login: Dunqing
state: APPROVED
author_association: MEMBER
submitted_at: 2025-04-14T02:35:55Z

Review comments:
diff_hunk: @@ -168,18 +171,10 @@ impl<'a> ModuleRunnerTransform<'a> {
                     new_stmts.push(stmt);
                 }
             }
-
-            // Found that non-import statements, we should start hoisting import statements after this
-            if hoist_index.is_none() {
-                hoist_index.replace(new_stmts.len() - 1);
-            }
         }
 
-        if let Some(index) = hoist_index {
-            if !hoist_imports.is_empty() {
-                new_stmts.splice(index..index, hoist_imports);
-            }
-        }
+        new_stmts.splice(0..0, hoist_imports);
+        new_stmts.splice(0..0, hoist_exports);
path: crates/oxc_transformer/src/plugins/module_runner_transform.rs
user: login: Dunqing
body: ```suggestion
        new_stmts.splice(0..0, hoist_exports.into_iter().chain(hoist_imports);
```

This looks like it would work better - What do you think?
created_at: 2025-04-11T08:53:31Z
author_association: MEMBER
diff_hunk: @@ -168,18 +171,10 @@ impl<'a> ModuleRunnerTransform<'a> {
                     new_stmts.push(stmt);
                 }
             }
-
-            // Found that non-import statements, we should start hoisting import statements after this
-            if hoist_index.is_none() {
-                hoist_index.replace(new_stmts.len() - 1);
-            }
         }
 
-        if let Some(index) = hoist_index {
-            if !hoist_imports.is_empty() {
-                new_stmts.splice(index..index, hoist_imports);
-            }
-        }
+        new_stmts.splice(0..0, hoist_imports);
+        new_stmts.splice(0..0, hoist_exports);
path: crates/oxc_transformer/src/plugins/module_runner_transform.rs
user: login: hi-ogawa
body: It works! I don't understand the perf difference though :smile: 
created_at: 2025-04-11T09:28:52Z
author_association: CONTRIBUTOR

PR comments:
user: login: hi-ogawa
created_at: 2025-04-10T05:43:11Z
body: * **#10560** <a href="https://app.graphite.dev/github/pr/oxc-project/oxc/10560?utm_source=stack-comment-icon" target="_blank"><img src="https://static.graphite.dev/graphite-32x32-black.png" alt="Graphite" width="10px" height="10px"/></a>
* **#10362** <a href="https://app.graphite.dev/github/pr/oxc-project/oxc/10362?utm_source=stack-comment-icon" target="_blank"><img src="https://static.graphite.dev/graphite-32x32-black.png" alt="Graphite" width="10px" height="10px"/></a>
* **#10347** <a href="https://app.graphite.dev/github/pr/oxc-project/oxc/10347?utm_source=stack-comment-icon" target="_blank"><img src="https://static.graphite.dev/graphite-32x32-black.png" alt="Graphite" width="10px" height="10px"/></a> 👈 <a href="https://app.graphite.dev/github/pr/oxc-project/oxc/10347?utm_source=stack-comment-view-in-graphite" target="_blank">(View in Graphite)</a>
* `main`
<hr />

## How to use the Graphite Merge Queue

Add either label to this PR to merge it via the merge queue:
- <kbd>0-merge</kbd> - adds this PR to the back of the merge queue
- <kbd>hotfix</kbd> - for urgent hot fixes, skip the queue and merge this PR next

You must have a Graphite account in order to use the merge queue. Sign up using [this link](https://app.graphite.dev/invite/github/oxc-project?ref=merge-queue-instructions-in-stack-comment&prId=10347).

<h2></h2>
<sub>

An organization admin has enabled the [Graphite Merge Queue](https://graphite.dev/docs/graphite-merge-queue) in this repository.

</sub>

<sub>

Please do not merge from GitHub as this will restart CI on PRs being processed by the merge queue.

</sub>

<h2></h2>

This stack of pull requests is managed by <a href="https://graphite.dev?utm-source=stack-comment"><b>Graphite</b></a>. Learn more about <a href="https://stacking.dev/?utm_source=stack-comment">stacking</a>.
<!-- Current dependencies on/for this PR: -->
author_association: CONTRIBUTOR
user: login: codspeed-hq[bot]
created_at: 2025-04-10T05:50:49Z
body: <!-- __CODSPEED_PERFORMANCE_REPORT_COMMENT__ -->
<!-- __CODSPEED_INSTRUMENTATION_PERFORMANCE_REPORT_COMMENT__ -->

## [CodSpeed Instrumentation Performance Report](https://codspeed.io/oxc-project/oxc/branches/03-21-feat_transformer_module_runner_hoist_export)

### Merging #10347 will **not alter performance**

<sub>Comparing <code>03-21-feat_transformer_module_runner_hoist_export</code> (1ff75bc) with <code>main</code> (2e1ef4c)</sub>



### Summary

`✅ 36` untouched benchmarks  




author_association: NONE
user: login: Dunqing
created_at: 2025-04-14T02:36:12Z
body: ### Merge activity

* **Apr 13, 10:36 PM EDT**: The merge label '0-merge' was detected. This PR will be added to the [Graphite merge queue](https://app.graphite.dev/merges?org=oxc-project&repo=oxc) once it meets the requirements.
* **Apr 13, 10:36 PM EDT**: `Dunqing` added this pull request to the [Graphite merge queue](https://app.graphite.dev/merges?org=oxc-project&repo=oxc).
* **Apr 13, 10:42 PM EDT**: Merged by the [Graphite merge queue](https://app.graphite.dev/merges?org=oxc-project&repo=oxc).

author_association: MEMBER