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>, 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>, 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",