From 714ecadec360b718e6847740f04be3e050253648 Mon Sep 17 00:00:00 2001 From: Dunqing Date: Fri, 21 Feb 2025 00:18:26 +0800 Subject: [PATCH 01/30] feat(transformer): support ModuleRunnerTransform --- crates/oxc_transformer/src/lib.rs | 1 + crates/oxc_transformer/src/vite/mod.rs | 1 + .../src/vite/module_runner_transform.rs | 139 ++++++++++++++++++ 3 files changed, 141 insertions(+) create mode 100644 crates/oxc_transformer/src/vite/mod.rs create mode 100644 crates/oxc_transformer/src/vite/module_runner_transform.rs diff --git a/crates/oxc_transformer/src/lib.rs b/crates/oxc_transformer/src/lib.rs index 0a0a4f3c565b6..6c9f804a45b23 100644 --- a/crates/oxc_transformer/src/lib.rs +++ b/crates/oxc_transformer/src/lib.rs @@ -36,6 +36,7 @@ mod typescript; mod decorator; mod plugins; +mod vite; use common::Common; use context::TransformCtx; diff --git a/crates/oxc_transformer/src/vite/mod.rs b/crates/oxc_transformer/src/vite/mod.rs new file mode 100644 index 0000000000000..ae3ae6c38beaf --- /dev/null +++ b/crates/oxc_transformer/src/vite/mod.rs @@ -0,0 +1 @@ +pub mod module_runner_transform; diff --git a/crates/oxc_transformer/src/vite/module_runner_transform.rs b/crates/oxc_transformer/src/vite/module_runner_transform.rs new file mode 100644 index 0000000000000..3101d9f4e4733 --- /dev/null +++ b/crates/oxc_transformer/src/vite/module_runner_transform.rs @@ -0,0 +1,139 @@ +/// ### Complicated parts if we want to support this in main transformer: +/// 1. In Vite, it will collect import deps and dynamic import deps during the transform process, and return them +/// at the end of function. We can do this, but how to return it? +/// 2. In case other plugins will insert imports/exports, we must transform them in exit_program, but it will pose +/// another problem: how to transform identifiers which refers to imports? We must collect some information from imports, +/// but it already at the end of the visitor. To solve this, we may introduce a new visitor to transform identifier, +/// dynamic import and meta property. +use oxc_allocator::Vec as ArenaVec; +use oxc_ast::ast::*; +use oxc_traverse::{Traverse, TraverseCtx}; + +pub struct ModuleRunnerTransform { + import_uid: u32, +} + +impl ModuleRunnerTransform { + pub fn new() -> Self { + Self { import_uid: 0 } + } +} + +impl<'a> Traverse<'a> for ModuleRunnerTransform { + #[inline] + fn enter_program(&mut self, program: &mut Program<'a>, ctx: &mut TraverseCtx<'a>) { + self.transform_imports_and_exports(program, ctx); + } + + #[inline] + fn enter_expression(&mut self, expr: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>) { + match expr { + Expression::Identifier(_) => self.transform_identifier(expr, ctx), + Expression::MetaProperty(_) => self.transform_meta_property(expr, ctx), + Expression::ImportExpression(_) => self.transform_import_expression(expr, ctx), + _ => {} + } + } +} + +impl<'a> ModuleRunnerTransform { + #[inline] + fn transform_imports_and_exports( + &mut self, + program: &mut Program<'a>, + ctx: &mut TraverseCtx<'a>, + ) { + for statement in &mut program.body { + match statement { + Statement::ImportDeclaration(import) => { + self.define_import(import, ctx); + } + Statement::ExportAllDeclaration(export) => {} + Statement::ExportNamedDeclaration(export) => {} + Statement::ExportDefaultDeclaration(export) => {} + _ => {} + } + } + } + + #[inline] + fn transform_import_expression( + &mut self, + expr: &mut Expression<'a>, + ctx: &mut TraverseCtx<'a>, + ) { + let Expression::ImportExpression(import_expr) = expr else { + unreachable!(); + }; + } + + #[inline] + fn transform_identifier(&mut self, expr: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>) { + let Expression::Identifier(import_expr) = expr else { + unreachable!(); + }; + } + + #[inline] + fn transform_meta_property(&mut self, expr: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>) { + let Expression::MetaProperty(import_expr) = expr else { + unreachable!(); + }; + } + + fn define_import( + &mut self, + source: &str, + specifiers: Option<&mut ArenaVec<'a, ImportSpecifier<'a>>>, + ctx: &mut TraverseCtx<'a>, + ) { + + + `__vite_ssr_import_${uid++}__` + } +} + +#[cfg(test)] +mod test { + use std::path::Path; + + use oxc_allocator::Allocator; + use oxc_codegen::{CodeGenerator, CodegenOptions}; + use oxc_diagnostics::OxcDiagnostic; + use oxc_parser::Parser; + use oxc_semantic::SemanticBuilder; + use oxc_span::SourceType; + use oxc_traverse::traverse_mut; + + use super::ModuleRunnerTransform; + + fn test(source_text: &str) -> Result> { + let source_type = SourceType::default(); + let allocator = Allocator::default(); + let ret = Parser::new(&allocator, source_text, source_type).parse(); + let mut program = ret.program; + let (symbols, scopes) = + SemanticBuilder::new().build(&program).semantic.into_symbol_table_and_scope_tree(); + + let mut module_runner_transform = ModuleRunnerTransform::new(); + traverse_mut(&mut module_runner_transform, &allocator, &mut program, symbols, scopes); + + if !ret.errors.is_empty() { + return Err(ret.errors); + } + let code = CodeGenerator::new() + .with_options(CodegenOptions { single_quote: true, ..CodegenOptions::default() }) + .build(&program) + .code; + Ok(code) + } + + fn test_same(source_text: &str, expected: &str) { + debug_assert_eq!(test(source_text).ok(), Some(expected.to_string())); + } + + #[test] + fn default_import() { + test_same("import { foo } from 'vue';console.log(foo.bar)", "const __vite_ssr_import_0__ = await __vite_ssr_import__(\"vue\", {\"importedNames\":[\"foo\"]});console.log(__vite_ssr_import_0__.foo.bar)"); + } +} From 65880db5d37baedcf9df33c6490e888922a6461d Mon Sep 17 00:00:00 2001 From: Dunqing Date: Tue, 25 Feb 2025 23:41:00 +0800 Subject: [PATCH 02/30] transform imports --- .../src/vite/module_runner_transform.rs | 154 +++++++++++++++--- 1 file changed, 130 insertions(+), 24 deletions(-) diff --git a/crates/oxc_transformer/src/vite/module_runner_transform.rs b/crates/oxc_transformer/src/vite/module_runner_transform.rs index 3101d9f4e4733..13a59d163d67d 100644 --- a/crates/oxc_transformer/src/vite/module_runner_transform.rs +++ b/crates/oxc_transformer/src/vite/module_runner_transform.rs @@ -5,21 +5,33 @@ /// another problem: how to transform identifiers which refers to imports? We must collect some information from imports, /// but it already at the end of the visitor. To solve this, we may introduce a new visitor to transform identifier, /// dynamic import and meta property. -use oxc_allocator::Vec as ArenaVec; -use oxc_ast::ast::*; -use oxc_traverse::{Traverse, TraverseCtx}; +use compact_str::ToCompactString; +use rustc_hash::FxHashMap; -pub struct ModuleRunnerTransform { +use oxc_allocator::{String as ArenaString, Vec as ArenaVec}; +use oxc_ast::{NONE, ast::*}; +use oxc_semantic::{ReferenceFlags, SymbolFlags, SymbolId}; +use oxc_span::SPAN; +use oxc_traverse::{BoundIdentifier, Traverse, TraverseCtx}; + +pub struct ModuleRunnerTransform<'a> { import_uid: u32, + import_bindings: FxHashMap>, } -impl ModuleRunnerTransform { +impl ModuleRunnerTransform<'_> { pub fn new() -> Self { - Self { import_uid: 0 } + Self { import_uid: 0, import_bindings: FxHashMap::default() } } } -impl<'a> Traverse<'a> for ModuleRunnerTransform { +const SSR_MODULE_EXPORTS_KEY: &str = "__vite_ssr_exports__"; +const SSR_IMPORT_KEY: &str = "__vite_ssr_import__"; +const SSR_DYNAMIC_IMPORT_KEY: &str = "__vite_ssr_dynamic_import__"; +const SSR_EXPORT_ALL_KEY: &str = "__vite_ssr_exportAll__"; +const SSR_IMPORT_META_KEY: &str = "__vite_ssr_import_meta__"; + +impl<'a> Traverse<'a> for ModuleRunnerTransform<'a> { #[inline] fn enter_program(&mut self, program: &mut Program<'a>, ctx: &mut TraverseCtx<'a>) { self.transform_imports_and_exports(program, ctx); @@ -36,22 +48,25 @@ impl<'a> Traverse<'a> for ModuleRunnerTransform { } } -impl<'a> ModuleRunnerTransform { +impl<'a> ModuleRunnerTransform<'a> { #[inline] fn transform_imports_and_exports( &mut self, program: &mut Program<'a>, ctx: &mut TraverseCtx<'a>, ) { - for statement in &mut program.body { - match statement { - Statement::ImportDeclaration(import) => { - self.define_import(import, ctx); + for stmt in &mut program.body { + if Self::should_transform_statement(stmt) { + match ctx.ast.move_statement(stmt) { + Statement::ImportDeclaration(import) => { + let ImportDeclaration { span, source, specifiers, .. } = import.unbox(); + *stmt = self.transform_import(span, source, specifiers, ctx); + } + Statement::ExportAllDeclaration(export) => {} + Statement::ExportNamedDeclaration(export) => {} + Statement::ExportDefaultDeclaration(export) => {} + _ => {} } - Statement::ExportAllDeclaration(export) => {} - Statement::ExportNamedDeclaration(export) => {} - Statement::ExportDefaultDeclaration(export) => {} - _ => {} } } } @@ -81,22 +96,110 @@ impl<'a> ModuleRunnerTransform { }; } - fn define_import( + fn transform_import( &mut self, - source: &str, - specifiers: Option<&mut ArenaVec<'a, ImportSpecifier<'a>>>, + span: Span, + source: StringLiteral<'a>, + specifiers: Option>>, ctx: &mut TraverseCtx<'a>, - ) { + ) -> Statement<'a> { + self.import_uid += 1; + let uid = self.import_uid.to_compact_string(); + let capacity = 20 + uid.len(); + let mut string = ArenaString::with_capacity_in(capacity, ctx.ast.allocator); + string.push_str("__vite_ssr_import_"); + string.push_str(&uid); + string.push_str("__"); + + let binding = ctx.generate_binding_in_current_scope( + Atom::from(string), + SymbolFlags::BlockScopedVariable, + ); + + // `await __vite_ssr_import__('vue', {importedNames: ['foo']});` + let callee = + ctx.create_unbound_ident_expr(SPAN, Atom::from(SSR_IMPORT_KEY), ReferenceFlags::Read); + let mut arguments = ctx.ast.vec_with_capacity(1 + usize::from(specifiers.is_some())); + arguments.push(Argument::from(Expression::StringLiteral(ctx.ast.alloc(source)))); + if let Some(specifiers) = specifiers { + arguments.push(self.transform_import_metadata(&binding, specifiers, ctx)); + } + let call = ctx.ast.expression_call(SPAN, callee, NONE, arguments, false); + let init = ctx.ast.expression_await(SPAN, call); + + // `const __vite_ssr_import_1__ = await __vite_ssr_import__('vue', {importedNames: ['foo']});` + let kind = VariableDeclarationKind::Const; + let pattern = binding.create_binding_pattern(ctx); + let declarator = ctx.ast.variable_declarator(SPAN, kind, pattern, Some(init), false); + let declaration = ctx.ast.declaration_variable(span, kind, ctx.ast.vec1(declarator), false); + Statement::from(declaration) + } + fn transform_import_metadata( + &mut self, + binding: &BoundIdentifier<'a>, + specifiers: ArenaVec<'a, ImportDeclarationSpecifier<'a>>, + ctx: &mut TraverseCtx<'a>, + ) -> Argument<'a> { + let elements = + ctx.ast.vec_from_iter(specifiers.into_iter().map(|specifier| match specifier { + ImportDeclarationSpecifier::ImportSpecifier(specifier) => { + self.transform_import_binding(binding, specifier.unbox().local, ctx) + } + ImportDeclarationSpecifier::ImportNamespaceSpecifier(specifier) => { + self.transform_import_binding(binding, specifier.unbox().local, ctx) + } + ImportDeclarationSpecifier::ImportDefaultSpecifier(specifier) => { + self.transform_import_binding(binding, specifier.unbox().local, ctx) + } + })); + let value = ctx.ast.expression_array(SPAN, elements, None); + let key = ctx.ast.property_key_static_identifier(SPAN, Atom::from("importedNames")); + let imported_names = ctx.ast.object_property_kind_object_property( + SPAN, + PropertyKind::Init, + key, + value, + false, + false, + false, + ); + // { importedNames: ['foo', 'bar'] } + Argument::from(ctx.ast.expression_object(SPAN, ctx.ast.vec1(imported_names), None)) + } + + fn transform_import_binding( + &mut self, + binding: &BoundIdentifier<'a>, + ident: BindingIdentifier<'a>, + ctx: &mut TraverseCtx<'a>, + ) -> ArrayExpressionElement<'a> { + let BindingIdentifier { span, name, symbol_id } = ident; + let scopes = ctx.scopes_mut(); + scopes.remove_binding(scopes.root_scope_id(), &name); + + let symbol_id = symbol_id.get().unwrap(); + if !ctx.symbols().get_resolved_reference_ids(symbol_id).is_empty() { + self.import_bindings.insert(symbol_id, binding.clone()); + } - `__vite_ssr_import_${uid++}__` + ArrayExpressionElement::from(ctx.ast.expression_string_literal(span, name, None)) + } + + #[inline] + fn should_transform_statement(statement: &Statement<'a>) -> bool { + matches!( + statement, + Statement::ImportDeclaration(_) + | Statement::ExportAllDeclaration(_) + | Statement::ExportNamedDeclaration(_) + | Statement::ExportDefaultDeclaration(_) + ) } } #[cfg(test)] mod test { - use std::path::Path; - use oxc_allocator::Allocator; use oxc_codegen::{CodeGenerator, CodegenOptions}; use oxc_diagnostics::OxcDiagnostic; @@ -134,6 +237,9 @@ mod test { #[test] fn default_import() { - test_same("import { foo } from 'vue';console.log(foo.bar)", "const __vite_ssr_import_0__ = await __vite_ssr_import__(\"vue\", {\"importedNames\":[\"foo\"]});console.log(__vite_ssr_import_0__.foo.bar)"); + test_same( + "import { foo } from 'vue';console.log(foo.bar)", + "const __vite_ssr_import_1__ = await __vite_ssr_import__('vue', { importedNames: ['foo'] });\nconsole.log(foo.bar);\n", + ); } } From 207e304ba4a3ef55012056d6224c5e27794b4535 Mon Sep 17 00:00:00 2001 From: Dunqing Date: Wed, 26 Feb 2025 17:49:41 +0800 Subject: [PATCH 03/30] transform identifier --- .../src/decorator/legacy/metadata.rs | 4 +- .../oxc_transformer/src/utils/ast_builder.rs | 14 +- .../src/vite/module_runner_transform.rs | 140 ++++++++++++++---- 3 files changed, 128 insertions(+), 30 deletions(-) diff --git a/crates/oxc_transformer/src/decorator/legacy/metadata.rs b/crates/oxc_transformer/src/decorator/legacy/metadata.rs index 221e94bdba165..d8733304118a5 100644 --- a/crates/oxc_transformer/src/decorator/legacy/metadata.rs +++ b/crates/oxc_transformer/src/decorator/legacy/metadata.rs @@ -375,7 +375,7 @@ impl<'a> LegacyDecoratorMetadata<'a, '_> { let binding = MaybeBoundIdentifier::from_identifier_reference(ident, ctx); let ident1 = binding.create_read_expression(ctx); let ident2 = binding.create_read_expression(ctx); - let member = create_property_access(ident1, &qualified.right.name, ctx); + let member = create_property_access(SPAN, ident1, &qualified.right.name, ctx); Self::create_checked_value(ident2, member, ctx) } else { // `A.B.C` -> `typeof A !== "undefined" && (_a = A.B) !== void 0 && _a.C` @@ -401,7 +401,7 @@ impl<'a> LegacyDecoratorMetadata<'a, '_> { ); let object = binding.create_read_expression(ctx); - let member = create_property_access(object, &qualified.right.name, ctx); + let member = create_property_access(SPAN, object, &qualified.right.name, ctx); ctx.ast.expression_logical(SPAN, left, LogicalOperator::And, member) } } diff --git a/crates/oxc_transformer/src/utils/ast_builder.rs b/crates/oxc_transformer/src/utils/ast_builder.rs index 05773bc097609..4945f169b0b83 100644 --- a/crates/oxc_transformer/src/utils/ast_builder.rs +++ b/crates/oxc_transformer/src/utils/ast_builder.rs @@ -84,10 +84,22 @@ pub fn create_prototype_member<'a>( /// `object` -> `object.a`. pub fn create_property_access<'a>( + span: Span, object: Expression<'a>, property: &str, ctx: &TraverseCtx<'a>, ) -> Expression<'a> { let property = ctx.ast.identifier_name(SPAN, ctx.ast.atom(property)); - Expression::from(ctx.ast.member_expression_static(SPAN, object, property, false)) + Expression::from(ctx.ast.member_expression_static(span, object, property, false)) +} + +/// `object` -> `object['a']`. +pub fn create_compute_property_access<'a>( + span: Span, + object: Expression<'a>, + property: &str, + ctx: &TraverseCtx<'a>, +) -> Expression<'a> { + let expression = ctx.ast.expression_string_literal(SPAN, ctx.ast.atom(property), None); + Expression::from(ctx.ast.member_expression_computed(span, object, expression, false)) } diff --git a/crates/oxc_transformer/src/vite/module_runner_transform.rs b/crates/oxc_transformer/src/vite/module_runner_transform.rs index 13a59d163d67d..98355a5f666b1 100644 --- a/crates/oxc_transformer/src/vite/module_runner_transform.rs +++ b/crates/oxc_transformer/src/vite/module_runner_transform.rs @@ -1,4 +1,11 @@ -/// ### Complicated parts if we want to support this in main transformer: +/// Module runner transform +/// +/// ## Implementation +/// +/// Based on https://github.com/vitejs/vite/blob/00deea4ff88e30e299cb40a801b5dc0205ac913d/packages/vite/src/node/ssr/ssrTransform.ts +/// +/// +/// ## Complicated parts if we want to support this in main transformer: /// 1. In Vite, it will collect import deps and dynamic import deps during the transform process, and return them /// at the end of function. We can do this, but how to return it? /// 2. In case other plugins will insert imports/exports, we must transform them in exit_program, but it will pose @@ -6,6 +13,7 @@ /// but it already at the end of the visitor. To solve this, we may introduce a new visitor to transform identifier, /// dynamic import and meta property. use compact_str::ToCompactString; +use oxc_syntax::identifier::is_identifier_name; use rustc_hash::FxHashMap; use oxc_allocator::{String as ArenaString, Vec as ArenaVec}; @@ -14,9 +22,11 @@ use oxc_semantic::{ReferenceFlags, SymbolFlags, SymbolId}; use oxc_span::SPAN; use oxc_traverse::{BoundIdentifier, Traverse, TraverseCtx}; +use crate::utils::ast_builder::{create_compute_property_access, create_property_access}; + pub struct ModuleRunnerTransform<'a> { import_uid: u32, - import_bindings: FxHashMap>, + import_bindings: FxHashMap, Option>)>, } impl ModuleRunnerTransform<'_> { @@ -83,10 +93,31 @@ impl<'a> ModuleRunnerTransform<'a> { } #[inline] - fn transform_identifier(&mut self, expr: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>) { - let Expression::Identifier(import_expr) = expr else { + fn transform_identifier(&self, expr: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>) { + let Expression::Identifier(ident) = expr else { unreachable!(); }; + + let Some(reference_id) = ident.reference_id.get() else { + return; + }; + let Some(symbol_id) = ctx.symbols().get_reference(reference_id).symbol_id() else { + return; + }; + if let Some((binding, property)) = self.import_bindings.get(&symbol_id) { + let object = binding.create_read_expression(ctx); + if let Some(property) = property { + // TODO(improvement): It looks like here always return a computed member expression, + // so that we don't need to check if it's a identifier name + if is_identifier_name(property) { + *expr = create_property_access(ident.span, object, property, ctx); + } else { + *expr = create_compute_property_access(ident.span, object, property, ctx); + } + } else { + *expr = object; + } + } } #[inline] @@ -110,26 +141,51 @@ impl<'a> ModuleRunnerTransform<'a> { string.push_str("__vite_ssr_import_"); string.push_str(&uid); string.push_str("__"); - - let binding = ctx.generate_binding_in_current_scope( - Atom::from(string), - SymbolFlags::BlockScopedVariable, - ); + let string = Atom::from(string); // `await __vite_ssr_import__('vue', {importedNames: ['foo']});` let callee = ctx.create_unbound_ident_expr(SPAN, Atom::from(SSR_IMPORT_KEY), ReferenceFlags::Read); let mut arguments = ctx.ast.vec_with_capacity(1 + usize::from(specifiers.is_some())); arguments.push(Argument::from(Expression::StringLiteral(ctx.ast.alloc(source)))); - if let Some(specifiers) = specifiers { - arguments.push(self.transform_import_metadata(&binding, specifiers, ctx)); - } + + let pattern = if let Some(mut specifiers) = specifiers { + // `import * as vue from 'vue';` -> `const __vite_ssr_import_1__ = await __vite_ssr_import__('vue');` + if matches!( + specifiers.last(), + Some(ImportDeclarationSpecifier::ImportNamespaceSpecifier(_)) + ) { + let Some(ImportDeclarationSpecifier::ImportNamespaceSpecifier(specifier)) = + specifiers.pop() + else { + unreachable!() + }; + + // Reuse the `vue` binding identifier by renaming it to `__vite_ssr_import_1__` + let mut local = specifier.unbox().local; + local.name = string; + let binding = BoundIdentifier::from_binding_ident(&local); + ctx.symbols_mut().set_name(binding.symbol_id, &binding.name); + self.import_bindings.insert(binding.symbol_id, (binding, None)); + + let binding_pattern_kind = BindingPatternKind::BindingIdentifier(ctx.alloc(local)); + ctx.ast.binding_pattern(binding_pattern_kind, NONE, false) + } else { + let binding = + ctx.generate_binding_in_current_scope(string, SymbolFlags::BlockScopedVariable); + arguments.push(self.transform_import_metadata(&binding, specifiers, ctx)); + binding.create_binding_pattern(ctx) + } + } else { + ctx.generate_binding_in_current_scope(string, SymbolFlags::BlockScopedVariable) + .create_binding_pattern(ctx) + }; + let call = ctx.ast.expression_call(SPAN, callee, NONE, arguments, false); let init = ctx.ast.expression_await(SPAN, call); - // `const __vite_ssr_import_1__ = await __vite_ssr_import__('vue', {importedNames: ['foo']});` + // `const __vite_ssr_import_1__ = await __vite_ssr_import__('vue', { importedNames: ['foo'] });` let kind = VariableDeclarationKind::Const; - let pattern = binding.create_binding_pattern(ctx); let declarator = ctx.ast.variable_declarator(SPAN, kind, pattern, Some(init), false); let declaration = ctx.ast.declaration_variable(span, kind, ctx.ast.vec1(declarator), false); Statement::from(declaration) @@ -141,18 +197,21 @@ impl<'a> ModuleRunnerTransform<'a> { specifiers: ArenaVec<'a, ImportDeclarationSpecifier<'a>>, ctx: &mut TraverseCtx<'a>, ) -> Argument<'a> { - let elements = + let elements_iter = ctx.ast.vec_from_iter(specifiers.into_iter().map(|specifier| match specifier { ImportDeclarationSpecifier::ImportSpecifier(specifier) => { - self.transform_import_binding(binding, specifier.unbox().local, ctx) - } - ImportDeclarationSpecifier::ImportNamespaceSpecifier(specifier) => { - self.transform_import_binding(binding, specifier.unbox().local, ctx) + let ImportSpecifier { span, local, imported, .. } = specifier.unbox(); + self.insert_import_binding(span, binding, local, imported.name(), ctx) } ImportDeclarationSpecifier::ImportDefaultSpecifier(specifier) => { - self.transform_import_binding(binding, specifier.unbox().local, ctx) + let ImportDefaultSpecifier { span, local } = specifier.unbox(); + self.insert_import_binding(span, binding, local, Atom::from("default"), ctx) + } + ImportDeclarationSpecifier::ImportNamespaceSpecifier(_) => { + unreachable!() } })); + let elements = ctx.ast.vec_from_iter(elements_iter); let value = ctx.ast.expression_array(SPAN, elements, None); let key = ctx.ast.property_key_static_identifier(SPAN, Atom::from("importedNames")); let imported_names = ctx.ast.object_property_kind_object_property( @@ -168,22 +227,26 @@ impl<'a> ModuleRunnerTransform<'a> { Argument::from(ctx.ast.expression_object(SPAN, ctx.ast.vec1(imported_names), None)) } - fn transform_import_binding( + /// Insert an import binding into the import bindings map and then return an imported name. + fn insert_import_binding( &mut self, + span: Span, binding: &BoundIdentifier<'a>, ident: BindingIdentifier<'a>, + key: Atom<'a>, ctx: &mut TraverseCtx<'a>, ) -> ArrayExpressionElement<'a> { - let BindingIdentifier { span, name, symbol_id } = ident; + let BindingIdentifier { name, symbol_id, .. } = ident; + let scopes = ctx.scopes_mut(); scopes.remove_binding(scopes.root_scope_id(), &name); let symbol_id = symbol_id.get().unwrap(); if !ctx.symbols().get_resolved_reference_ids(symbol_id).is_empty() { - self.import_bindings.insert(symbol_id, binding.clone()); + self.import_bindings.insert(symbol_id, (binding.clone(), Some(key))); } - ArrayExpressionElement::from(ctx.ast.expression_string_literal(span, name, None)) + ArrayExpressionElement::from(ctx.ast.expression_string_literal(span, key, None)) } #[inline] @@ -234,12 +297,35 @@ mod test { fn test_same(source_text: &str, expected: &str) { debug_assert_eq!(test(source_text).ok(), Some(expected.to_string())); } - #[test] fn default_import() { test_same( - "import { foo } from 'vue';console.log(foo.bar)", - "const __vite_ssr_import_1__ = await __vite_ssr_import__('vue', { importedNames: ['foo'] });\nconsole.log(foo.bar);\n", + "import foo from 'vue';console.log(foo.bar)", + "const __vite_ssr_import_1__ = await __vite_ssr_import__('vue', { importedNames: ['default'] });\nconsole.log(__vite_ssr_import_1__.default.bar);\n", + ); + } + + #[test] + fn named_import() { + test_same( + "import { ref } from 'vue';function foo() { return ref(0) }", + "const __vite_ssr_import_1__ = await __vite_ssr_import__('vue', { importedNames: ['ref'] });\nfunction foo() {\n\treturn __vite_ssr_import_1__.ref(0);\n}\n", + ); + } + + #[test] + fn named_import_arbitrary_module_namespace() { + test_same( + "import { \"some thing\" as ref } from 'vue';function foo() { return ref(0) }", + "const __vite_ssr_import_1__ = await __vite_ssr_import__('vue', { importedNames: ['some thing'] });\nfunction foo() {\n\treturn __vite_ssr_import_1__['some thing'](0);\n}\n", + ); + } + + #[test] + fn namespace_import() { + test_same( + "import * as vue from 'vue';function foo() { return vue.ref(0) }", + "const __vite_ssr_import_1__ = await __vite_ssr_import__('vue');\nfunction foo() {\n\treturn __vite_ssr_import_1__.ref(0);\n}\n", ); } } From cfe07b8a2a2a2a7e1ec2f6cfac7eb5798f3d7dca Mon Sep 17 00:00:00 2001 From: Dunqing Date: Thu, 27 Feb 2025 01:46:49 +0800 Subject: [PATCH 04/30] transform export named declaration --- .../src/vite/module_runner_transform.rs | 370 +++++++++++++++--- 1 file changed, 317 insertions(+), 53 deletions(-) diff --git a/crates/oxc_transformer/src/vite/module_runner_transform.rs b/crates/oxc_transformer/src/vite/module_runner_transform.rs index 98355a5f666b1..98c4f3a452aed 100644 --- a/crates/oxc_transformer/src/vite/module_runner_transform.rs +++ b/crates/oxc_transformer/src/vite/module_runner_transform.rs @@ -13,16 +13,19 @@ /// but it already at the end of the visitor. To solve this, we may introduce a new visitor to transform identifier, /// dynamic import and meta property. use compact_str::ToCompactString; +use oxc_ecmascript::BoundNames; use oxc_syntax::identifier::is_identifier_name; use rustc_hash::FxHashMap; -use oxc_allocator::{String as ArenaString, Vec as ArenaVec}; +use oxc_allocator::{Box as ArenaBox, String as ArenaString, Vec as ArenaVec}; use oxc_ast::{NONE, ast::*}; -use oxc_semantic::{ReferenceFlags, SymbolFlags, SymbolId}; +use oxc_semantic::{ReferenceFlags, ScopeFlags, SymbolFlags, SymbolId}; use oxc_span::SPAN; use oxc_traverse::{BoundIdentifier, Traverse, TraverseCtx}; -use crate::utils::ast_builder::{create_compute_property_access, create_property_access}; +use crate::utils::ast_builder::{ + create_compute_property_access, create_member_callee, create_property_access, +}; pub struct ModuleRunnerTransform<'a> { import_uid: u32, @@ -59,26 +62,34 @@ impl<'a> Traverse<'a> for ModuleRunnerTransform<'a> { } impl<'a> ModuleRunnerTransform<'a> { - #[inline] fn transform_imports_and_exports( &mut self, program: &mut Program<'a>, ctx: &mut TraverseCtx<'a>, ) { - for stmt in &mut program.body { - if Self::should_transform_statement(stmt) { - match ctx.ast.move_statement(stmt) { - Statement::ImportDeclaration(import) => { - let ImportDeclaration { span, source, specifiers, .. } = import.unbox(); - *stmt = self.transform_import(span, source, specifiers, ctx); - } - Statement::ExportAllDeclaration(export) => {} - Statement::ExportNamedDeclaration(export) => {} - Statement::ExportDefaultDeclaration(export) => {} - _ => {} + let should_transform = program.body.iter().any(Self::should_transform_statement); + if !should_transform { + return; + } + // Reserve enough space for new statements + let mut new_stmts: ArenaVec<'a, Statement<'a>> = + ctx.ast.vec_with_capacity(program.body.len() * 2); + for stmt in &mut program.body.drain(..) { + match stmt { + Statement::ImportDeclaration(import) => { + let ImportDeclaration { span, source, specifiers, .. } = import.unbox(); + new_stmts.push(self.transform_import(span, source, specifiers, ctx)); } + Statement::ExportNamedDeclaration(export) => { + self.transform_export_named_declaration(&mut new_stmts, export, ctx); + } + Statement::ExportDefaultDeclaration(export) => {} + Statement::ExportAllDeclaration(export) => {} + _ => new_stmts.push(stmt), } } + + program.body = new_stmts; } #[inline] @@ -134,21 +145,9 @@ impl<'a> ModuleRunnerTransform<'a> { specifiers: Option>>, ctx: &mut TraverseCtx<'a>, ) -> Statement<'a> { - self.import_uid += 1; - let uid = self.import_uid.to_compact_string(); - let capacity = 20 + uid.len(); - let mut string = ArenaString::with_capacity_in(capacity, ctx.ast.allocator); - string.push_str("__vite_ssr_import_"); - string.push_str(&uid); - string.push_str("__"); - let string = Atom::from(string); - - // `await __vite_ssr_import__('vue', {importedNames: ['foo']});` - let callee = - ctx.create_unbound_ident_expr(SPAN, Atom::from(SSR_IMPORT_KEY), ReferenceFlags::Read); + // ['vue', { importedNames: ['foo'] }]` let mut arguments = ctx.ast.vec_with_capacity(1 + usize::from(specifiers.is_some())); arguments.push(Argument::from(Expression::StringLiteral(ctx.ast.alloc(source)))); - let pattern = if let Some(mut specifiers) = specifiers { // `import * as vue from 'vue';` -> `const __vite_ssr_import_1__ = await __vite_ssr_import__('vue');` if matches!( @@ -163,7 +162,7 @@ impl<'a> ModuleRunnerTransform<'a> { // Reuse the `vue` binding identifier by renaming it to `__vite_ssr_import_1__` let mut local = specifier.unbox().local; - local.name = string; + local.name = self.generate_import_binding_name(ctx); let binding = BoundIdentifier::from_binding_ident(&local); ctx.symbols_mut().set_name(binding.symbol_id, &binding.name); self.import_bindings.insert(binding.symbol_id, (binding, None)); @@ -171,24 +170,102 @@ impl<'a> ModuleRunnerTransform<'a> { let binding_pattern_kind = BindingPatternKind::BindingIdentifier(ctx.alloc(local)); ctx.ast.binding_pattern(binding_pattern_kind, NONE, false) } else { - let binding = - ctx.generate_binding_in_current_scope(string, SymbolFlags::BlockScopedVariable); + let binding = self.generate_import_binding(ctx); arguments.push(self.transform_import_metadata(&binding, specifiers, ctx)); binding.create_binding_pattern(ctx) } } else { - ctx.generate_binding_in_current_scope(string, SymbolFlags::BlockScopedVariable) - .create_binding_pattern(ctx) + let binding = self.generate_import_binding(ctx); + binding.create_binding_pattern(ctx) }; - let call = ctx.ast.expression_call(SPAN, callee, NONE, arguments, false); - let init = ctx.ast.expression_await(SPAN, call); + Self::create_imports(span, pattern, arguments, ctx) + } - // `const __vite_ssr_import_1__ = await __vite_ssr_import__('vue', { importedNames: ['foo'] });` - let kind = VariableDeclarationKind::Const; - let declarator = ctx.ast.variable_declarator(SPAN, kind, pattern, Some(init), false); - let declaration = ctx.ast.declaration_variable(span, kind, ctx.ast.vec1(declarator), false); - Statement::from(declaration) + fn transform_export_named_declaration( + &mut self, + new_stmts: &mut ArenaVec<'a, Statement<'a>>, + export: ArenaBox<'a, ExportNamedDeclaration<'a>>, + ctx: &mut TraverseCtx<'a>, + ) { + let ExportNamedDeclaration { span, source, specifiers, declaration, .. } = export.unbox(); + + if let Some(declaration) = declaration { + let export_expression = match &declaration { + Declaration::VariableDeclaration(variable) => { + let new_stmts_index = new_stmts.len(); + variable.bound_names(&mut |binding| { + let binding = BoundIdentifier::from_binding_ident(binding); + let ident = binding.create_read_expression(ctx); + new_stmts.push(ctx.ast.statement_expression( + SPAN, + Self::create_exports(ident, &binding.name, ctx), + )); + }); + // Should be inserted before the exports + new_stmts.insert(new_stmts_index, Statement::from(declaration)); + return; + } + Declaration::FunctionDeclaration(func) => { + let binding = BoundIdentifier::from_binding_ident(func.id.as_ref().unwrap()); + let ident = binding.create_read_expression(ctx); + Self::create_exports(ident, &binding.name, ctx) + } + Declaration::ClassDeclaration(class) => { + let binding = BoundIdentifier::from_binding_ident(class.id.as_ref().unwrap()); + let ident = binding.create_read_expression(ctx); + Self::create_exports(ident, &binding.name, ctx) + } + _ => { + unreachable!( + "Unsupported for transforming typescript declaration in named export" + ); + } + }; + new_stmts.extend([ + Statement::from(declaration), + ctx.ast.statement_expression(SPAN, export_expression), + ]); + } else { + // ```js + // export { foo, bar } from 'vue'; + // // to + // const __vite_ssr_import_1__ = await __vite_ssr_import__('vue', { importedNames: ['foo', 'bar'] }); + // Object.defineProperty(__vite_ssr_exports__, 'foo', { enumerable: true, configurable: true, get(){ return __vite_ssr_import_1__.foo } }); + // Object.defineProperty(__vite_ssr_exports__, 'bar', { enumerable: true, configurable: true, get(){ return __vite_ssr_import_1__.bar } }); + // ``` + let import_binding = source.map(|source| { + let binding = self.generate_import_binding(ctx); + let pattern = binding.create_binding_pattern(ctx); + let imported_names = ctx.ast.vec_from_iter(specifiers.iter().map(|specifier| { + let local_name = specifier.local.name(); + let local_name_expr = ctx.ast.expression_string_literal(SPAN, local_name, None); + ArrayExpressionElement::from(local_name_expr) + })); + let arguments = ctx.ast.vec_from_array([ + Argument::from(Expression::StringLiteral(ctx.ast.alloc(source))), + Self::create_imported_names_object(imported_names, ctx), + ]); + new_stmts.push(Self::create_imports(SPAN, pattern, arguments, ctx)); + binding + }); + + new_stmts.extend(specifiers.into_iter().map(|specifier| { + let ExportSpecifier { span, exported, local, .. } = specifier; + let export = if let Some(import_binding) = &import_binding { + let object = import_binding.create_read_expression(ctx); + let expr = create_property_access(SPAN, object, &local.name(), ctx); + Self::create_exports(expr, &exported.name(), ctx) + } else { + let ModuleExportName::IdentifierReference(ident) = local else { + unreachable!() + }; + let expr = Expression::Identifier(ctx.ast.alloc(ident)); + Self::create_exports(expr, &exported.name(), ctx) + }; + ctx.ast.statement_expression(span, export) + })); + } } fn transform_import_metadata( @@ -212,19 +289,7 @@ impl<'a> ModuleRunnerTransform<'a> { } })); let elements = ctx.ast.vec_from_iter(elements_iter); - let value = ctx.ast.expression_array(SPAN, elements, None); - let key = ctx.ast.property_key_static_identifier(SPAN, Atom::from("importedNames")); - let imported_names = ctx.ast.object_property_kind_object_property( - SPAN, - PropertyKind::Init, - key, - value, - false, - false, - false, - ); - // { importedNames: ['foo', 'bar'] } - Argument::from(ctx.ast.expression_object(SPAN, ctx.ast.vec1(imported_names), None)) + Self::create_imported_names_object(elements, ctx) } /// Insert an import binding into the import bindings map and then return an imported name. @@ -259,6 +324,149 @@ impl<'a> ModuleRunnerTransform<'a> { | Statement::ExportDefaultDeclaration(_) ) } + + /// Generate a unique import binding name like `__vite_ssr_import_{uid}__`. + fn generate_import_binding_name(&mut self, ctx: &TraverseCtx<'a>) -> Atom<'a> { + self.import_uid += 1; + let uid = self.import_uid.to_compact_string(); + let capacity = 20 + uid.len(); + let mut string = ArenaString::with_capacity_in(capacity, ctx.ast.allocator); + string.push_str("__vite_ssr_import_"); + string.push_str(&uid); + string.push_str("__"); + Atom::from(string) + } + + /// Generate a unique import binding whose name is like `__vite_ssr_import_{uid}__`. + #[inline] + fn generate_import_binding(&mut self, ctx: &mut TraverseCtx<'a>) -> BoundIdentifier<'a> { + let name = self.generate_import_binding_name(ctx); + ctx.generate_binding_in_current_scope(name, SymbolFlags::BlockScopedVariable) + } + + // { importedNames: ['foo', 'bar'] } + fn create_imported_names_object( + elements: ArenaVec<'a, ArrayExpressionElement<'a>>, + ctx: &TraverseCtx<'a>, + ) -> Argument<'a> { + let value = ctx.ast.expression_array(SPAN, elements, None); + let key = ctx.ast.property_key_static_identifier(SPAN, Atom::from("importedNames")); + let imported_names = ctx.ast.object_property_kind_object_property( + SPAN, + PropertyKind::Init, + key, + value, + false, + false, + false, + ); + Argument::from(ctx.ast.expression_object(SPAN, ctx.ast.vec1(imported_names), None)) + } + + // `const __vite_ssr_import_1__ = await __vite_ssr_import__('vue', { importedNames: ['foo'] });` + fn create_imports( + span: Span, + pattern: BindingPattern<'a>, + arguments: ArenaVec<'a, Argument<'a>>, + ctx: &mut TraverseCtx<'a>, + ) -> Statement<'a> { + let callee = + ctx.create_unbound_ident_expr(SPAN, Atom::from(SSR_IMPORT_KEY), ReferenceFlags::Read); + let call = ctx.ast.expression_call(SPAN, callee, NONE, arguments, false); + let init = ctx.ast.expression_await(SPAN, call); + + let kind = VariableDeclarationKind::Const; + let declarator = ctx.ast.variable_declarator(SPAN, kind, pattern, Some(init), false); + let declaration = ctx.ast.declaration_variable(span, kind, ctx.ast.vec1(declarator), false); + Statement::from(declaration) + } + + // `Object.defineProperty(...arguments)` + fn create_define_property( + arguments: ArenaVec<'a, Argument<'a>>, + ctx: &mut TraverseCtx<'a>, + ) -> Expression<'a> { + let object = + ctx.create_unbound_ident_expr(SPAN, Atom::from("Object"), ReferenceFlags::Read); + let member = create_member_callee(object, "defineProperty", ctx); + ctx.ast.expression_call(SPAN, member, NONE, arguments, false) + } + + // `key: value` or `key() {}` + fn create_object_property( + key: &str, + value: Option>, + ctx: &TraverseCtx<'a>, + ) -> ObjectPropertyKind<'a> { + let is_method = value.is_some(); + ctx.ast.object_property_kind_object_property( + SPAN, + PropertyKind::Init, + ctx.ast.property_key_static_identifier(SPAN, key), + value.unwrap_or_else(|| ctx.ast.expression_boolean_literal(SPAN, true)), + is_method, + false, + false, + ) + } + + /// `{ enumerable: true, configurable: true, get(){ return expr } }` + fn create_function_with_return_statement( + expr: Expression<'a>, + ctx: &mut TraverseCtx<'a>, + ) -> Expression<'a> { + let kind = FormalParameterKind::FormalParameter; + let params = ctx.ast.formal_parameters(SPAN, kind, ctx.ast.vec(), NONE); + let statement = ctx.ast.statement_return(SPAN, Some(expr)); + let body = ctx.ast.function_body(SPAN, ctx.ast.vec(), ctx.ast.vec1(statement)); + let r#type = FunctionType::FunctionExpression; + let scope_id = ctx.create_child_scope(ctx.scopes().root_scope_id(), ScopeFlags::Function); + let function = ctx.ast.alloc_function_with_scope_id( + SPAN, + r#type, + None, + false, + false, + false, + NONE, + NONE, + params, + NONE, + Some(body), + scope_id, + ); + Expression::FunctionExpression(function) + } + + // `Object.defineProperty(__vite_ssr_exports__, 'foo', {enumerable: true, configurable: true, get(){ return foo }});` + fn create_exports( + expr: Expression<'a>, + exported_name: &str, + ctx: &mut TraverseCtx<'a>, + ) -> Expression<'a> { + let getter = Self::create_function_with_return_statement(expr, ctx); + let object = ctx.ast.expression_object( + SPAN, + ctx.ast.vec_from_array([ + Self::create_object_property("enumerable", None, ctx), + Self::create_object_property("configurable", None, ctx), + Self::create_object_property("get", Some(getter), ctx), + ]), + None, + ); + + let arguments = ctx.ast.vec_from_array([ + Argument::from(ctx.create_unbound_ident_expr( + SPAN, + Atom::from(SSR_MODULE_EXPORTS_KEY), + ReferenceFlags::Read, + )), + Argument::from(ctx.ast.expression_string_literal(SPAN, exported_name, None)), + Argument::from(object), + ]); + + Self::create_define_property(arguments, ctx) + } } #[cfg(test)] @@ -328,4 +536,60 @@ mod test { "const __vite_ssr_import_1__ = await __vite_ssr_import__('vue');\nfunction foo() {\n\treturn __vite_ssr_import_1__.ref(0);\n}\n", ); } + + #[test] + fn export_function_declaration() { + test_same( + "export function foo() {}", + "function foo() {}\nObject.defineProperty(__vite_ssr_exports__, 'foo', {\n\tenumerable: true,\n\tconfigurable: true,\n\tget() {\n\t\treturn foo;\n\t}\n});\n", + ); + } + + #[test] + fn export_class_declaration() { + test_same( + "export class foo {}", + "class foo {}\nObject.defineProperty(__vite_ssr_exports__, 'foo', {\n\tenumerable: true,\n\tconfigurable: true,\n\tget() {\n\t\treturn foo;\n\t}\n});\n", + ); + } + + #[test] + fn export_var_declaration() { + test_same( + "export const a = 1, b = 2", + "const a = 1, b = 2;\nObject.defineProperty(__vite_ssr_exports__, 'a', {\n\tenumerable: true,\n\tconfigurable: true,\n\tget() {\n\t\treturn a;\n\t}\n});\nObject.defineProperty(__vite_ssr_exports__, 'b', {\n\tenumerable: true,\n\tconfigurable: true,\n\tget() {\n\t\treturn b;\n\t}\n});\n", + ); + } + + #[test] + fn export_named() { + test_same( + "const a = 1, b = 2; export { a, b as c }", + "const a = 1, b = 2;\nObject.defineProperty(__vite_ssr_exports__, 'a', {\n\tenumerable: true,\n\tconfigurable: true,\n\tget() {\n\t\treturn a;\n\t}\n});\nObject.defineProperty(__vite_ssr_exports__, 'c', {\n\tenumerable: true,\n\tconfigurable: true,\n\tget() {\n\t\treturn b;\n\t}\n});\n", + ); + } + + #[test] + fn export_named_from() { + test_same( + "export { ref, computed as c } from 'vue'", + "const __vite_ssr_import_1__ = await __vite_ssr_import__('vue', { importedNames: ['ref', 'computed'] });\nObject.defineProperty(__vite_ssr_exports__, 'ref', {\n\tenumerable: true,\n\tconfigurable: true,\n\tget() {\n\t\treturn __vite_ssr_import_1__.ref;\n\t}\n});\nObject.defineProperty(__vite_ssr_exports__, 'c', {\n\tenumerable: true,\n\tconfigurable: true,\n\tget() {\n\t\treturn __vite_ssr_import_1__.computed;\n\t}\n});\n", + ); + } + + #[test] + fn export_star_from() { + test_same( + "export * from 'vue'\nexport * from 'react'", + "const __vite_ssr_import_1__ = await __vite_ssr_import__('vue');\n__vite_ssr_exportAll__(__vite_ssr_import_1__);\nconst __vite_ssr_import_2__ = await __vite_ssr_import__('react');\n__vite_ssr_exportAll__(__vite_ssr_import_2__);\n", + ); + } + + #[test] + fn export_star_as_from() { + test_same( + "export * as foo from 'vue'", + "const __vite_ssr_import_1__ = await __vite_ssr_import__('vue');\nObject.defineProperty(__vite_ssr_exports__, 'foo', {\n\tenumerable: true, \n\t\n\tconfigurable: true, \n\tget(){ return __vite_ssr_import_1__ }});\n", + ); + } } From 7eaf913f1bff94f0a976c498172542f7d8501503 Mon Sep 17 00:00:00 2001 From: Dunqing Date: Thu, 27 Feb 2025 11:16:42 +0800 Subject: [PATCH 05/30] transform export all declaration --- .../src/vite/module_runner_transform.rs | 89 +++++++++++++++++-- 1 file changed, 80 insertions(+), 9 deletions(-) diff --git a/crates/oxc_transformer/src/vite/module_runner_transform.rs b/crates/oxc_transformer/src/vite/module_runner_transform.rs index 98c4f3a452aed..890c434474a9b 100644 --- a/crates/oxc_transformer/src/vite/module_runner_transform.rs +++ b/crates/oxc_transformer/src/vite/module_runner_transform.rs @@ -83,8 +83,10 @@ impl<'a> ModuleRunnerTransform<'a> { Statement::ExportNamedDeclaration(export) => { self.transform_export_named_declaration(&mut new_stmts, export, ctx); } + Statement::ExportAllDeclaration(export) => { + self.transform_export_all_declaration(&mut new_stmts, export, ctx); + } Statement::ExportDefaultDeclaration(export) => {} - Statement::ExportAllDeclaration(export) => {} _ => new_stmts.push(stmt), } } @@ -118,13 +120,13 @@ impl<'a> ModuleRunnerTransform<'a> { if let Some((binding, property)) = self.import_bindings.get(&symbol_id) { let object = binding.create_read_expression(ctx); if let Some(property) = property { - // TODO(improvement): It looks like here always return a computed member expression, - // so that we don't need to check if it's a identifier name - if is_identifier_name(property) { - *expr = create_property_access(ident.span, object, property, ctx); + // TODO(improvement): It looks like here could always return a computed member expression, + // so that we don't need to check if it's an identifier name. + *expr = if is_identifier_name(property) { + create_property_access(ident.span, object, property, ctx) } else { - *expr = create_compute_property_access(ident.span, object, property, ctx); - } + create_compute_property_access(ident.span, object, property, ctx) + }; } else { *expr = object; } @@ -254,7 +256,14 @@ impl<'a> ModuleRunnerTransform<'a> { let ExportSpecifier { span, exported, local, .. } = specifier; let export = if let Some(import_binding) = &import_binding { let object = import_binding.create_read_expression(ctx); - let expr = create_property_access(SPAN, object, &local.name(), ctx); + let property = local.name(); + // TODO(improvement): It looks like here could always return a computed member expression, + // so that we don't need to check if it's an identifier name. + let expr = if is_identifier_name(&property) { + create_property_access(SPAN, object, &property, ctx) + } else { + create_compute_property_access(SPAN, object, &property, ctx) + }; Self::create_exports(expr, &exported.name(), ctx) } else { let ModuleExportName::IdentifierReference(ident) = local else { @@ -268,6 +277,34 @@ impl<'a> ModuleRunnerTransform<'a> { } } + fn transform_export_all_declaration( + &mut self, + new_stmts: &mut ArenaVec<'a, Statement<'a>>, + export: ArenaBox<'a, ExportAllDeclaration<'a>>, + ctx: &mut TraverseCtx<'a>, + ) { + let ExportAllDeclaration { span, source, exported, .. } = export.unbox(); + let binding = self.generate_import_binding(ctx); + let pattern = binding.create_binding_pattern(ctx); + let arguments = + ctx.ast.vec1(Argument::from(Expression::StringLiteral(ctx.ast.alloc(source)))); + new_stmts.push(Self::create_imports(span, pattern, arguments, ctx)); + + let ident = binding.create_read_expression(ctx); + + let export = if let Some(exported) = exported { + // `export * as foo from 'vue'` -> + // `defineProperty(__vite_ssr_exports__, 'foo', { enumerable: true, configurable: true, get(){ return __vite_ssr_import_1__ } });` + Self::create_exports(ident, &exported.name(), ctx) + } else { + let callee = ctx.ast.expression_identifier(SPAN, Atom::from(SSR_EXPORT_ALL_KEY)); + let arguments = ctx.ast.vec1(Argument::from(ident)); + // `export * from 'vue'` -> `__vite_ssr_exportAll__(__vite_ssr_import_1__);` + ctx.ast.expression_call(SPAN, callee, NONE, arguments, false) + }; + new_stmts.push(ctx.ast.statement_expression(SPAN, export)); + } + fn transform_import_metadata( &mut self, binding: &BoundIdentifier<'a>, @@ -589,7 +626,41 @@ mod test { fn export_star_as_from() { test_same( "export * as foo from 'vue'", - "const __vite_ssr_import_1__ = await __vite_ssr_import__('vue');\nObject.defineProperty(__vite_ssr_exports__, 'foo', {\n\tenumerable: true, \n\t\n\tconfigurable: true, \n\tget(){ return __vite_ssr_import_1__ }});\n", + "const __vite_ssr_import_1__ = await __vite_ssr_import__('vue');\nObject.defineProperty(__vite_ssr_exports__, 'foo', {\n\tenumerable: true,\n\tconfigurable: true,\n\tget() {\n\t\treturn __vite_ssr_import_1__;\n\t}\n});\n", + ); + } + + #[test] + fn re_export_by_imported_name() { + test_same( + "import * as foo from 'foo'; export * as foo from 'foo'", + "const __vite_ssr_import_1__ = await __vite_ssr_import__('foo');\nconst __vite_ssr_import_2__ = await __vite_ssr_import__('foo');\nObject.defineProperty(__vite_ssr_exports__, 'foo', {\n\tenumerable: true,\n\tconfigurable: true,\n\tget() {\n\t\treturn __vite_ssr_import_2__;\n\t}\n});\n", + ); + + test_same( + "import { foo } from 'foo'; export { foo } from 'foo'", + "const __vite_ssr_import_1__ = await __vite_ssr_import__('foo', { importedNames: ['foo'] });\nconst __vite_ssr_import_2__ = await __vite_ssr_import__('foo', { importedNames: ['foo'] });\nObject.defineProperty(__vite_ssr_exports__, 'foo', {\n\tenumerable: true,\n\tconfigurable: true,\n\tget() {\n\t\treturn __vite_ssr_import_2__.foo;\n\t}\n});\n", + ); + } + + #[test] + fn export_arbitrary_namespace() { + test_same( + "export * as \"arbitrary string\" from 'vue'", + "const __vite_ssr_import_1__ = await __vite_ssr_import__('vue');\nObject.defineProperty(__vite_ssr_exports__, 'arbitrary string', {\n\tenumerable: true,\n\tconfigurable: true,\n\tget() {\n\t\treturn __vite_ssr_import_1__;\n\t}\n});\n", + ); + + test_same( + "const something = \"Something\"; export { something as \"arbitrary string\" }", + "const something = 'Something';\nObject.defineProperty(__vite_ssr_exports__, 'arbitrary string', {\n\tenumerable: true,\n\tconfigurable: true,\n\tget() {\n\t\treturn something;\n\t}\n});\n", + ); + + test_same( + "export { \"arbitrary string2\" as \"arbitrary string\" } from 'vue'", + "const __vite_ssr_import_1__ = await __vite_ssr_import__('vue', { importedNames: ['arbitrary string2'] });\nObject.defineProperty(__vite_ssr_exports__, 'arbitrary string', {\n\tenumerable: true,\n\tconfigurable: true,\n\tget() {\n\t\treturn __vite_ssr_import_1__['arbitrary string2'];\n\t}\n});\n", + ); + } + ); } } From d2f081ade3f0c025792d86811ea9848eb46b858d Mon Sep 17 00:00:00 2001 From: Dunqing Date: Thu, 27 Feb 2025 11:42:03 +0800 Subject: [PATCH 06/30] transform export default declaration --- .../src/vite/module_runner_transform.rs | 97 ++++++++++++++++++- 1 file changed, 96 insertions(+), 1 deletion(-) diff --git a/crates/oxc_transformer/src/vite/module_runner_transform.rs b/crates/oxc_transformer/src/vite/module_runner_transform.rs index 890c434474a9b..b9afd1de9b6ec 100644 --- a/crates/oxc_transformer/src/vite/module_runner_transform.rs +++ b/crates/oxc_transformer/src/vite/module_runner_transform.rs @@ -86,7 +86,9 @@ impl<'a> ModuleRunnerTransform<'a> { Statement::ExportAllDeclaration(export) => { self.transform_export_all_declaration(&mut new_stmts, export, ctx); } - Statement::ExportDefaultDeclaration(export) => {} + Statement::ExportDefaultDeclaration(export) => { + self.transform_export_default_declaration(&mut new_stmts, export, ctx); + } _ => new_stmts.push(stmt), } } @@ -305,6 +307,56 @@ impl<'a> ModuleRunnerTransform<'a> { new_stmts.push(ctx.ast.statement_expression(SPAN, export)); } + fn transform_export_default_declaration( + &self, + new_stmts: &mut ArenaVec<'a, Statement<'a>>, + export: ArenaBox<'a, ExportDefaultDeclaration<'a>>, + ctx: &mut TraverseCtx<'a>, + ) { + let ExportDefaultDeclaration { span, declaration, .. } = export.unbox(); + let expr = match declaration { + ExportDefaultDeclarationKind::FunctionDeclaration(mut func) => { + // `export default function foo() {}` -> + // `function foo() {}; Object.defineProperty(__vite_ssr_exports__, 'default', { enumerable: true, configurable: true, get(){ return foo } });` + if let Some(id) = &func.id { + let ident = BoundIdentifier::from_binding_ident(id).create_read_expression(ctx); + let export = Self::create_exports(ident, "default", ctx); + new_stmts.extend([ + Statement::FunctionDeclaration(func), + ctx.ast.statement_expression(span, export), + ]); + return; + } + func.r#type = FunctionType::FunctionExpression; + Expression::FunctionExpression(func) + } + ExportDefaultDeclarationKind::ClassDeclaration(mut class) => { + // `export default class Foo {}` -> + // `class Foo {}; Object.defineProperty(__vite_ssr_exports__, 'default', { enumerable: true, configurable: true, get(){ return Foo } });` + if let Some(id) = &class.id { + let ident = BoundIdentifier::from_binding_ident(id).create_read_expression(ctx); + let export = Self::create_exports(ident, "default", ctx); + new_stmts.extend([ + Statement::ClassDeclaration(class), + ctx.ast.statement_expression(span, export), + ]); + return; + } + + class.r#type = ClassType::ClassExpression; + Expression::ClassExpression(class) + } + ExportDefaultDeclarationKind::TSInterfaceDeclaration(_) => { + // Do nothing for `interface Foo {}` + return; + } + expr @ match_expression!(ExportDefaultDeclarationKind) => expr.into_expression(), + }; + // `export default expr` -> `Object.defineProperty(__vite_ssr_exports__, 'default', { enumerable: true, configurable: true, get(){ return expr } });` + let export = Self::create_exports(expr, "default", ctx); + new_stmts.push(ctx.ast.statement_expression(span, export)); + } + fn transform_import_metadata( &mut self, binding: &BoundIdentifier<'a>, @@ -661,6 +713,49 @@ mod test { ); } + #[test] + fn export_default() { + test_same( + "export default {}", + "Object.defineProperty(__vite_ssr_exports__, 'default', {\n\tenumerable: true,\n\tconfigurable: true,\n\tget() {\n\t\treturn {};\n\t}\n});\n", + ); + } + + #[test] + fn export_then_import() { + test_same( + "export * from 'vue';import {createApp} from 'vue'", + "const __vite_ssr_import_1__ = await __vite_ssr_import__('vue');\n__vite_ssr_exportAll__(__vite_ssr_import_1__);\nconst __vite_ssr_import_2__ = await __vite_ssr_import__('vue', { importedNames: ['createApp'] });\n", + ); + } + + /// + #[test] + fn handle_default_export_variants() { + // default anonymous functions + test_same( + "export default function() {}", + "Object.defineProperty(__vite_ssr_exports__, 'default', {\n\tenumerable: true,\n\tconfigurable: true,\n\tget() {\n\t\treturn function() {};\n\t}\n});\n", + ); + + // default anonymous class + test_same( + "export default class {}", + "Object.defineProperty(__vite_ssr_exports__, 'default', {\n\tenumerable: true,\n\tconfigurable: true,\n\tget() {\n\t\treturn class {};\n\t}\n});\n", + ); + + // default named functions + test_same( + "export default function foo() {}\nfoo.prototype = Object.prototype;", + // "function foo() {}\nfoo.prototype = Object.prototype;\nObject.defineProperty(__vite_ssr_exports__, 'default', {\n\tenumerable: true,\n\tconfigurable: true,\n\tget() {\n\t\treturn foo;\n\t}\n});\n", + "function foo() {}\nObject.defineProperty(__vite_ssr_exports__, 'default', {\n\tenumerable: true,\n\tconfigurable: true,\n\tget() {\n\t\treturn foo;\n\t}\n});\nfoo.prototype = Object.prototype;\n", + ); + + // default named classes + test_same( + "export default class A {}\nexport class B extends A {}", + // "class A {}\nclass B extends A {}\nObject.defineProperty(__vite_ssr_exports__, 'B', {\n\tenumerable: true,\n\tconfigurable: true,\n\tget() {\n\t\treturn B;\n\t}\n});\nObject.defineProperty(__vite_ssr_exports__, 'default', {\n\tenumerable: true,\n\tconfigurable: true,\n\tget() {\n\t\treturn A;\n\t}\n});\n", + "class A {}\nObject.defineProperty(__vite_ssr_exports__, 'default', {\n\tenumerable: true,\n\tconfigurable: true,\n\tget() {\n\t\treturn A;\n\t}\n});\nclass B extends A {}\nObject.defineProperty(__vite_ssr_exports__, 'B', {\n\tenumerable: true,\n\tconfigurable: true,\n\tget() {\n\t\treturn B;\n\t}\n});\n", ); } } From b26833cbf4daa826b3c9d6d84ceaa32e044691b2 Mon Sep 17 00:00:00 2001 From: Dunqing Date: Thu, 27 Feb 2025 13:53:15 +0800 Subject: [PATCH 07/30] support hoisting imports --- .../src/vite/module_runner_transform.rs | 32 ++++++++++++++++--- 1 file changed, 28 insertions(+), 4 deletions(-) diff --git a/crates/oxc_transformer/src/vite/module_runner_transform.rs b/crates/oxc_transformer/src/vite/module_runner_transform.rs index b9afd1de9b6ec..ad85ce3b63b46 100644 --- a/crates/oxc_transformer/src/vite/module_runner_transform.rs +++ b/crates/oxc_transformer/src/vite/module_runner_transform.rs @@ -74,11 +74,22 @@ impl<'a> ModuleRunnerTransform<'a> { // Reserve enough space for new statements let mut new_stmts: ArenaVec<'a, Statement<'a>> = ctx.ast.vec_with_capacity(program.body.len() * 2); - for stmt in &mut program.body.drain(..) { + + let mut hoist_index = None; + + for stmt in program.body.drain(..) { match stmt { Statement::ImportDeclaration(import) => { let ImportDeclaration { span, source, specifiers, .. } = import.unbox(); - new_stmts.push(self.transform_import(span, source, specifiers, ctx)); + let import_statement = self.transform_import(span, source, specifiers, ctx); + // Need to hoist import statements to the above of the other statements + if let Some(index) = hoist_index { + new_stmts.insert(index, import_statement); + hoist_index.replace(index + 1); + } else { + new_stmts.push(import_statement); + } + continue; } Statement::ExportNamedDeclaration(export) => { self.transform_export_named_declaration(&mut new_stmts, export, ctx); @@ -89,7 +100,12 @@ impl<'a> ModuleRunnerTransform<'a> { Statement::ExportDefaultDeclaration(export) => { self.transform_export_default_declaration(&mut new_stmts, export, ctx); } - _ => new_stmts.push(stmt), + _ => { + new_stmts.push(stmt); + } + } + if hoist_index.is_none() { + hoist_index.replace(new_stmts.len() - 1); } } @@ -722,13 +738,21 @@ mod test { } #[test] - fn export_then_import() { + fn export_then_import_minified() { test_same( "export * from 'vue';import {createApp} from 'vue'", "const __vite_ssr_import_1__ = await __vite_ssr_import__('vue');\n__vite_ssr_exportAll__(__vite_ssr_import_1__);\nconst __vite_ssr_import_2__ = await __vite_ssr_import__('vue', { importedNames: ['createApp'] });\n", ); } + #[test] + fn hoist_import_to_top() { + test_same( + "path.resolve('server.js');import path from 'node:path';", + "const __vite_ssr_import_1__ = await __vite_ssr_import__('node:path', { importedNames: ['default'] });\n__vite_ssr_import_1__.default.resolve('server.js');\n", + ); + } + /// #[test] fn handle_default_export_variants() { From 2eee373994d4efdd8f090e1ccda18b438f557988 Mon Sep 17 00:00:00 2001 From: Dunqing Date: Thu, 27 Feb 2025 13:57:42 +0800 Subject: [PATCH 08/30] transform import meta --- .../src/vite/module_runner_transform.rs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/crates/oxc_transformer/src/vite/module_runner_transform.rs b/crates/oxc_transformer/src/vite/module_runner_transform.rs index ad85ce3b63b46..f3d5ec18a111d 100644 --- a/crates/oxc_transformer/src/vite/module_runner_transform.rs +++ b/crates/oxc_transformer/src/vite/module_runner_transform.rs @@ -153,9 +153,12 @@ impl<'a> ModuleRunnerTransform<'a> { #[inline] fn transform_meta_property(&mut self, expr: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>) { - let Expression::MetaProperty(import_expr) = expr else { + let Expression::MetaProperty(meta) = expr else { unreachable!(); }; + + let name = Atom::from(SSR_IMPORT_META_KEY); + *expr = ctx.create_unbound_ident_expr(meta.span, name, ReferenceFlags::Read); } fn transform_import( @@ -753,6 +756,11 @@ mod test { ); } + #[test] + fn import_meta() { + test_same("console.log(import.meta.url)", "console.log(__vite_ssr_import_meta__.url);\n"); + } + /// #[test] fn handle_default_export_variants() { From 89023fbbb5c8e6b93075fbfbba1bef07b57d1a9c Mon Sep 17 00:00:00 2001 From: Dunqing Date: Thu, 27 Feb 2025 14:00:22 +0800 Subject: [PATCH 09/30] Use `Atom::new_const` to initialize constant keys --- .../src/vite/module_runner_transform.rs | 20 +++++++++---------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/crates/oxc_transformer/src/vite/module_runner_transform.rs b/crates/oxc_transformer/src/vite/module_runner_transform.rs index f3d5ec18a111d..77f66d983eaeb 100644 --- a/crates/oxc_transformer/src/vite/module_runner_transform.rs +++ b/crates/oxc_transformer/src/vite/module_runner_transform.rs @@ -38,11 +38,11 @@ impl ModuleRunnerTransform<'_> { } } -const SSR_MODULE_EXPORTS_KEY: &str = "__vite_ssr_exports__"; -const SSR_IMPORT_KEY: &str = "__vite_ssr_import__"; -const SSR_DYNAMIC_IMPORT_KEY: &str = "__vite_ssr_dynamic_import__"; -const SSR_EXPORT_ALL_KEY: &str = "__vite_ssr_exportAll__"; -const SSR_IMPORT_META_KEY: &str = "__vite_ssr_import_meta__"; +const SSR_MODULE_EXPORTS_KEY: Atom<'static> = Atom::new_const("__vite_ssr_exports__"); +const SSR_IMPORT_KEY: Atom<'static> = Atom::new_const("__vite_ssr_import__"); +const SSR_DYNAMIC_IMPORT_KEY: Atom<'static> = Atom::new_const("__vite_ssr_dynamic_import__"); +const SSR_EXPORT_ALL_KEY: Atom<'static> = Atom::new_const("__vite_ssr_exportAll__"); +const SSR_IMPORT_META_KEY: Atom<'static> = Atom::new_const("__vite_ssr_import_meta__"); impl<'a> Traverse<'a> for ModuleRunnerTransform<'a> { #[inline] @@ -157,8 +157,7 @@ impl<'a> ModuleRunnerTransform<'a> { unreachable!(); }; - let name = Atom::from(SSR_IMPORT_META_KEY); - *expr = ctx.create_unbound_ident_expr(meta.span, name, ReferenceFlags::Read); + *expr = ctx.create_unbound_ident_expr(meta.span, SSR_IMPORT_META_KEY, ReferenceFlags::Read); } fn transform_import( @@ -318,7 +317,7 @@ impl<'a> ModuleRunnerTransform<'a> { // `defineProperty(__vite_ssr_exports__, 'foo', { enumerable: true, configurable: true, get(){ return __vite_ssr_import_1__ } });` Self::create_exports(ident, &exported.name(), ctx) } else { - let callee = ctx.ast.expression_identifier(SPAN, Atom::from(SSR_EXPORT_ALL_KEY)); + 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_1__);` ctx.ast.expression_call(SPAN, callee, NONE, arguments, false) @@ -478,8 +477,7 @@ impl<'a> ModuleRunnerTransform<'a> { arguments: ArenaVec<'a, Argument<'a>>, ctx: &mut TraverseCtx<'a>, ) -> Statement<'a> { - let callee = - ctx.create_unbound_ident_expr(SPAN, Atom::from(SSR_IMPORT_KEY), ReferenceFlags::Read); + let callee = ctx.create_unbound_ident_expr(SPAN, SSR_IMPORT_KEY, ReferenceFlags::Read); let call = ctx.ast.expression_call(SPAN, callee, NONE, arguments, false); let init = ctx.ast.expression_await(SPAN, call); @@ -566,7 +564,7 @@ impl<'a> ModuleRunnerTransform<'a> { let arguments = ctx.ast.vec_from_array([ Argument::from(ctx.create_unbound_ident_expr( SPAN, - Atom::from(SSR_MODULE_EXPORTS_KEY), + SSR_MODULE_EXPORTS_KEY, ReferenceFlags::Read, )), Argument::from(ctx.ast.expression_string_literal(SPAN, exported_name, None)), From ca75a4ba67a85b635ea542043bdbeea2e8eab368 Mon Sep 17 00:00:00 2001 From: Dunqing Date: Thu, 27 Feb 2025 14:10:52 +0800 Subject: [PATCH 10/30] Simplify methods --- .../src/vite/module_runner_transform.rs | 65 +++++++++---------- 1 file changed, 29 insertions(+), 36 deletions(-) diff --git a/crates/oxc_transformer/src/vite/module_runner_transform.rs b/crates/oxc_transformer/src/vite/module_runner_transform.rs index 77f66d983eaeb..d2615e0139ec2 100644 --- a/crates/oxc_transformer/src/vite/module_runner_transform.rs +++ b/crates/oxc_transformer/src/vite/module_runner_transform.rs @@ -201,7 +201,7 @@ impl<'a> ModuleRunnerTransform<'a> { binding.create_binding_pattern(ctx) }; - Self::create_imports(span, pattern, arguments, ctx) + Self::create_import(span, pattern, arguments, ctx) } fn transform_export_named_declaration( @@ -216,13 +216,10 @@ impl<'a> ModuleRunnerTransform<'a> { let export_expression = match &declaration { Declaration::VariableDeclaration(variable) => { let new_stmts_index = new_stmts.len(); - variable.bound_names(&mut |binding| { - let binding = BoundIdentifier::from_binding_ident(binding); + variable.bound_names(&mut |ident| { + let binding = BoundIdentifier::from_binding_ident(ident); let ident = binding.create_read_expression(ctx); - new_stmts.push(ctx.ast.statement_expression( - SPAN, - Self::create_exports(ident, &binding.name, ctx), - )); + new_stmts.push(Self::create_export(span, ident, binding.name, ctx)); }); // Should be inserted before the exports new_stmts.insert(new_stmts_index, Statement::from(declaration)); @@ -231,12 +228,12 @@ impl<'a> ModuleRunnerTransform<'a> { Declaration::FunctionDeclaration(func) => { let binding = BoundIdentifier::from_binding_ident(func.id.as_ref().unwrap()); let ident = binding.create_read_expression(ctx); - Self::create_exports(ident, &binding.name, ctx) + Self::create_export(span, ident, binding.name, ctx) } Declaration::ClassDeclaration(class) => { let binding = BoundIdentifier::from_binding_ident(class.id.as_ref().unwrap()); let ident = binding.create_read_expression(ctx); - Self::create_exports(ident, &binding.name, ctx) + Self::create_export(span, ident, binding.name, ctx) } _ => { unreachable!( @@ -244,10 +241,7 @@ impl<'a> ModuleRunnerTransform<'a> { ); } }; - new_stmts.extend([ - Statement::from(declaration), - ctx.ast.statement_expression(SPAN, export_expression), - ]); + new_stmts.extend([Statement::from(declaration), export_expression]); } else { // ```js // export { foo, bar } from 'vue'; @@ -268,31 +262,29 @@ impl<'a> ModuleRunnerTransform<'a> { Argument::from(Expression::StringLiteral(ctx.ast.alloc(source))), Self::create_imported_names_object(imported_names, ctx), ]); - new_stmts.push(Self::create_imports(SPAN, pattern, arguments, ctx)); + new_stmts.push(Self::create_import(SPAN, pattern, arguments, ctx)); binding }); new_stmts.extend(specifiers.into_iter().map(|specifier| { let ExportSpecifier { span, exported, local, .. } = specifier; - let export = if let Some(import_binding) = &import_binding { + let expr = if let Some(import_binding) = &import_binding { let object = import_binding.create_read_expression(ctx); let property = local.name(); // TODO(improvement): It looks like here could always return a computed member expression, // so that we don't need to check if it's an identifier name. - let expr = if is_identifier_name(&property) { + if is_identifier_name(&property) { create_property_access(SPAN, object, &property, ctx) } else { create_compute_property_access(SPAN, object, &property, ctx) - }; - Self::create_exports(expr, &exported.name(), ctx) + } } else { let ModuleExportName::IdentifierReference(ident) = local else { unreachable!() }; - let expr = Expression::Identifier(ctx.ast.alloc(ident)); - Self::create_exports(expr, &exported.name(), ctx) + Expression::Identifier(ctx.ast.alloc(ident)) }; - ctx.ast.statement_expression(span, export) + Self::create_export(span, expr, exported.name(), ctx) })); } } @@ -308,21 +300,22 @@ impl<'a> ModuleRunnerTransform<'a> { let pattern = binding.create_binding_pattern(ctx); let arguments = ctx.ast.vec1(Argument::from(Expression::StringLiteral(ctx.ast.alloc(source)))); - new_stmts.push(Self::create_imports(span, pattern, arguments, ctx)); + new_stmts.push(Self::create_import(span, pattern, arguments, ctx)); let ident = binding.create_read_expression(ctx); let export = if let Some(exported) = exported { // `export * as foo from 'vue'` -> // `defineProperty(__vite_ssr_exports__, 'foo', { enumerable: true, configurable: true, get(){ return __vite_ssr_import_1__ } });` - Self::create_exports(ident, &exported.name(), ctx) + Self::create_export(span, ident, exported.name(), ctx) } 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_1__);` - ctx.ast.expression_call(SPAN, callee, NONE, arguments, false) + let call = ctx.ast.expression_call(SPAN, callee, NONE, arguments, false); + ctx.ast.statement_expression(span, call) }; - new_stmts.push(ctx.ast.statement_expression(SPAN, export)); + new_stmts.push(export); } fn transform_export_default_declaration( @@ -331,6 +324,7 @@ impl<'a> ModuleRunnerTransform<'a> { export: ArenaBox<'a, ExportDefaultDeclaration<'a>>, ctx: &mut TraverseCtx<'a>, ) { + let default = Atom::new_const("default"); let ExportDefaultDeclaration { span, declaration, .. } = export.unbox(); let expr = match declaration { ExportDefaultDeclarationKind::FunctionDeclaration(mut func) => { @@ -338,13 +332,13 @@ impl<'a> ModuleRunnerTransform<'a> { // `function foo() {}; Object.defineProperty(__vite_ssr_exports__, 'default', { enumerable: true, configurable: true, get(){ return foo } });` if let Some(id) = &func.id { let ident = BoundIdentifier::from_binding_ident(id).create_read_expression(ctx); - let export = Self::create_exports(ident, "default", ctx); new_stmts.extend([ Statement::FunctionDeclaration(func), - ctx.ast.statement_expression(span, export), + Self::create_export(span, ident, default, ctx), ]); return; } + func.r#type = FunctionType::FunctionExpression; Expression::FunctionExpression(func) } @@ -353,10 +347,9 @@ impl<'a> ModuleRunnerTransform<'a> { // `class Foo {}; Object.defineProperty(__vite_ssr_exports__, 'default', { enumerable: true, configurable: true, get(){ return Foo } });` if let Some(id) = &class.id { let ident = BoundIdentifier::from_binding_ident(id).create_read_expression(ctx); - let export = Self::create_exports(ident, "default", ctx); new_stmts.extend([ Statement::ClassDeclaration(class), - ctx.ast.statement_expression(span, export), + Self::create_export(span, ident, default, ctx), ]); return; } @@ -371,8 +364,7 @@ impl<'a> ModuleRunnerTransform<'a> { expr @ match_expression!(ExportDefaultDeclarationKind) => expr.into_expression(), }; // `export default expr` -> `Object.defineProperty(__vite_ssr_exports__, 'default', { enumerable: true, configurable: true, get(){ return expr } });` - let export = Self::create_exports(expr, "default", ctx); - new_stmts.push(ctx.ast.statement_expression(span, export)); + new_stmts.push(Self::create_export(span, expr, default, ctx)); } fn transform_import_metadata( @@ -471,7 +463,7 @@ impl<'a> ModuleRunnerTransform<'a> { } // `const __vite_ssr_import_1__ = await __vite_ssr_import__('vue', { importedNames: ['foo'] });` - fn create_imports( + fn create_import( span: Span, pattern: BindingPattern<'a>, arguments: ArenaVec<'a, Argument<'a>>, @@ -545,11 +537,12 @@ impl<'a> ModuleRunnerTransform<'a> { } // `Object.defineProperty(__vite_ssr_exports__, 'foo', {enumerable: true, configurable: true, get(){ return foo }});` - fn create_exports( + fn create_export( + span: Span, expr: Expression<'a>, - exported_name: &str, + exported_name: Atom<'a>, ctx: &mut TraverseCtx<'a>, - ) -> Expression<'a> { + ) -> Statement<'a> { let getter = Self::create_function_with_return_statement(expr, ctx); let object = ctx.ast.expression_object( SPAN, @@ -571,7 +564,7 @@ impl<'a> ModuleRunnerTransform<'a> { Argument::from(object), ]); - Self::create_define_property(arguments, ctx) + ctx.ast.statement_expression(span, Self::create_define_property(arguments, ctx)) } } From ace7e604ed2914977f3d6c65b40743fc386b716b Mon Sep 17 00:00:00 2001 From: Dunqing Date: Thu, 27 Feb 2025 14:23:34 +0800 Subject: [PATCH 11/30] transform dynamic import --- .../src/vite/module_runner_transform.rs | 22 ++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/crates/oxc_transformer/src/vite/module_runner_transform.rs b/crates/oxc_transformer/src/vite/module_runner_transform.rs index d2615e0139ec2..48409067aa809 100644 --- a/crates/oxc_transformer/src/vite/module_runner_transform.rs +++ b/crates/oxc_transformer/src/vite/module_runner_transform.rs @@ -13,14 +13,15 @@ /// but it already at the end of the visitor. To solve this, we may introduce a new visitor to transform identifier, /// dynamic import and meta property. use compact_str::ToCompactString; -use oxc_ecmascript::BoundNames; -use oxc_syntax::identifier::is_identifier_name; use rustc_hash::FxHashMap; +use std::iter; use oxc_allocator::{Box as ArenaBox, String as ArenaString, Vec as ArenaVec}; use oxc_ast::{NONE, ast::*}; +use oxc_ecmascript::BoundNames; use oxc_semantic::{ReferenceFlags, ScopeFlags, SymbolFlags, SymbolId}; use oxc_span::SPAN; +use oxc_syntax::identifier::is_identifier_name; use oxc_traverse::{BoundIdentifier, Traverse, TraverseCtx}; use crate::utils::ast_builder::{ @@ -118,9 +119,16 @@ impl<'a> ModuleRunnerTransform<'a> { expr: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>, ) { - let Expression::ImportExpression(import_expr) = expr else { + let Expression::ImportExpression(import_expr) = ctx.ast.move_expression(expr) else { unreachable!(); }; + + let ImportExpression { span, source, arguments, .. } = import_expr.unbox(); + let flags = ReferenceFlags::Read; + let callee = ctx.create_unbound_ident_expr(SPAN, SSR_DYNAMIC_IMPORT_KEY, flags); + let arguments = arguments.into_iter().map(Argument::from); + let arguments = ctx.ast.vec_from_iter(iter::once(Argument::from(source)).chain(arguments)); + *expr = ctx.ast.expression_call(span, callee, NONE, arguments, false); } #[inline] @@ -752,6 +760,14 @@ mod test { test_same("console.log(import.meta.url)", "console.log(__vite_ssr_import_meta__.url);\n"); } + #[test] + fn dynamic_import() { + test_same( + "export const i = () => import('./foo')", + "const i = () => __vite_ssr_dynamic_import__('./foo');\nObject.defineProperty(__vite_ssr_exports__, 'i', {\n\tenumerable: true,\n\tconfigurable: true,\n\tget() {\n\t\treturn i;\n\t}\n});\n", + ); + } + /// #[test] fn handle_default_export_variants() { From 0c04df4b5efd7f7d2df9e5422a7997dfad5114da Mon Sep 17 00:00:00 2001 From: Dunqing Date: Thu, 27 Feb 2025 15:52:53 +0800 Subject: [PATCH 12/30] Add a lot of tests --- Cargo.lock | 2 + crates/oxc_transformer/Cargo.toml | 2 + .../src/vite/module_runner_transform.rs | 821 +++++++++++++++++- 3 files changed, 797 insertions(+), 28 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 01d7181af21fe..decd51f895bf9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2228,12 +2228,14 @@ dependencies = [ "oxc_sourcemap", "oxc_span", "oxc_syntax", + "oxc_tasks_common", "oxc_traverse", "pico-args", "rustc-hash", "serde", "serde_json", "sha1", + "similar", ] [[package]] diff --git a/crates/oxc_transformer/Cargo.toml b/crates/oxc_transformer/Cargo.toml index 0036c7fc15416..43d546ca51372 100644 --- a/crates/oxc_transformer/Cargo.toml +++ b/crates/oxc_transformer/Cargo.toml @@ -47,9 +47,11 @@ sha1 = { workspace = true } [dev-dependencies] insta = { workspace = true } +similar = { workspace = true } oxc_codegen = { workspace = true } oxc_parser = { workspace = true } oxc_sourcemap = { workspace = true } +oxc_tasks_common = { workspace = true } pico-args = { workspace = true } [features] diff --git a/crates/oxc_transformer/src/vite/module_runner_transform.rs b/crates/oxc_transformer/src/vite/module_runner_transform.rs index 48409067aa809..cd7e401cca15c 100644 --- a/crates/oxc_transformer/src/vite/module_runner_transform.rs +++ b/crates/oxc_transformer/src/vite/module_runner_transform.rs @@ -97,9 +97,11 @@ impl<'a> ModuleRunnerTransform<'a> { } Statement::ExportAllDeclaration(export) => { self.transform_export_all_declaration(&mut new_stmts, export, ctx); + continue; } Statement::ExportDefaultDeclaration(export) => { self.transform_export_default_declaration(&mut new_stmts, export, ctx); + continue; } _ => { new_stmts.push(stmt); @@ -179,7 +181,7 @@ impl<'a> ModuleRunnerTransform<'a> { let mut arguments = ctx.ast.vec_with_capacity(1 + usize::from(specifiers.is_some())); arguments.push(Argument::from(Expression::StringLiteral(ctx.ast.alloc(source)))); let pattern = if let Some(mut specifiers) = specifiers { - // `import * as vue from 'vue';` -> `const __vite_ssr_import_1__ = await __vite_ssr_import__('vue');` + // `import * as vue from 'vue';` -> `const __vite_ssr_import_0__ = await __vite_ssr_import__('vue');` if matches!( specifiers.last(), Some(ImportDeclarationSpecifier::ImportNamespaceSpecifier(_)) @@ -190,7 +192,7 @@ impl<'a> ModuleRunnerTransform<'a> { unreachable!() }; - // Reuse the `vue` binding identifier by renaming it to `__vite_ssr_import_1__` + // Reuse the `vue` binding identifier by renaming it to `__vite_ssr_import_0__` let mut local = specifier.unbox().local; local.name = self.generate_import_binding_name(ctx); let binding = BoundIdentifier::from_binding_ident(&local); @@ -254,9 +256,9 @@ impl<'a> ModuleRunnerTransform<'a> { // ```js // export { foo, bar } from 'vue'; // // to - // const __vite_ssr_import_1__ = await __vite_ssr_import__('vue', { importedNames: ['foo', 'bar'] }); - // Object.defineProperty(__vite_ssr_exports__, 'foo', { enumerable: true, configurable: true, get(){ return __vite_ssr_import_1__.foo } }); - // Object.defineProperty(__vite_ssr_exports__, 'bar', { enumerable: true, configurable: true, get(){ return __vite_ssr_import_1__.bar } }); + // 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 } }); // ``` let import_binding = source.map(|source| { let binding = self.generate_import_binding(ctx); @@ -314,12 +316,12 @@ impl<'a> ModuleRunnerTransform<'a> { let export = if let Some(exported) = exported { // `export * as foo from 'vue'` -> - // `defineProperty(__vite_ssr_exports__, 'foo', { enumerable: true, configurable: true, get(){ return __vite_ssr_import_1__ } });` + // `defineProperty(__vite_ssr_exports__, 'foo', { enumerable: true, configurable: true, get(){ return __vite_ssr_import_0__ } });` Self::create_export(span, ident, exported.name(), ctx) } 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_1__);` + // `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) }; @@ -434,8 +436,8 @@ impl<'a> ModuleRunnerTransform<'a> { /// Generate a unique import binding name like `__vite_ssr_import_{uid}__`. fn generate_import_binding_name(&mut self, ctx: &TraverseCtx<'a>) -> Atom<'a> { - self.import_uid += 1; let uid = self.import_uid.to_compact_string(); + self.import_uid += 1; let capacity = 20 + uid.len(); let mut string = ArenaString::with_capacity_in(capacity, ctx.ast.allocator); string.push_str("__vite_ssr_import_"); @@ -470,7 +472,7 @@ impl<'a> ModuleRunnerTransform<'a> { Argument::from(ctx.ast.expression_object(SPAN, ctx.ast.vec1(imported_names), None)) } - // `const __vite_ssr_import_1__ = await __vite_ssr_import__('vue', { importedNames: ['foo'] });` + // `const __vite_ssr_import_0__ = await __vite_ssr_import__('vue', { importedNames: ['foo'] });` fn create_import( span: Span, pattern: BindingPattern<'a>, @@ -578,24 +580,40 @@ impl<'a> ModuleRunnerTransform<'a> { #[cfg(test)] mod test { + use std::path::Path; + + use oxc_ast::AstBuilder; + use similar::TextDiff; + use oxc_allocator::Allocator; use oxc_codegen::{CodeGenerator, CodegenOptions}; use oxc_diagnostics::OxcDiagnostic; use oxc_parser::Parser; use oxc_semantic::SemanticBuilder; use oxc_span::SourceType; + use oxc_tasks_common::print_diff_in_terminal; use oxc_traverse::traverse_mut; + use crate::{JsxOptions, JsxRuntime, TransformOptions, context::TransformCtx, jsx::Jsx}; + use super::ModuleRunnerTransform; - fn test(source_text: &str) -> Result> { - let source_type = SourceType::default(); + fn transform(source_text: &str, is_jsx: bool) -> Result> { + let source_type = SourceType::default().with_jsx(is_jsx); let allocator = Allocator::default(); let ret = Parser::new(&allocator, source_text, source_type).parse(); let mut program = ret.program; - let (symbols, scopes) = + let (mut symbols, mut scopes) = SemanticBuilder::new().build(&program).semantic.into_symbol_table_and_scope_tree(); - + if is_jsx { + let mut jsx_options = JsxOptions::enable(); + jsx_options.runtime = JsxRuntime::Classic; + jsx_options.jsx_plugin = true; + let ctx = TransformCtx::new(Path::new(""), &TransformOptions::default()); + let mut jsx = Jsx::new(jsx_options, None, AstBuilder::new(&allocator), &ctx); + + (symbols, scopes) = traverse_mut(&mut jsx, &allocator, &mut program, symbols, scopes); + } let mut module_runner_transform = ModuleRunnerTransform::new(); traverse_mut(&mut module_runner_transform, &allocator, &mut program, symbols, scopes); @@ -609,14 +627,42 @@ mod test { Ok(code) } + fn format_expected_code(source_text: &str) -> String { + let source_type = SourceType::default(); + let allocator = Allocator::default(); + let ret = Parser::new(&allocator, source_text, source_type).parse(); + + CodeGenerator::new() + .with_options(CodegenOptions { single_quote: true, ..CodegenOptions::default() }) + .build(&ret.program) + .code + } + fn test_same(source_text: &str, expected: &str) { - debug_assert_eq!(test(source_text).ok(), Some(expected.to_string())); + let expected = format_expected_code(expected); + let result = transform(source_text, false).unwrap(); + if result != expected { + let diff = TextDiff::from_lines(&expected, &result); + print_diff_in_terminal(&diff); + panic!("Expected code does not match the result"); + } + } + + fn test_same_jsx(source_text: &str, expected: &str) { + let expected = format_expected_code(expected); + let result = transform(source_text, true).unwrap(); + if result != expected { + let diff = TextDiff::from_lines(&expected, &result); + print_diff_in_terminal(&diff); + panic!("Expected code does not match the result"); + } } + #[test] fn default_import() { test_same( "import foo from 'vue';console.log(foo.bar)", - "const __vite_ssr_import_1__ = await __vite_ssr_import__('vue', { importedNames: ['default'] });\nconsole.log(__vite_ssr_import_1__.default.bar);\n", + "const __vite_ssr_import_0__ = await __vite_ssr_import__('vue', { importedNames: ['default'] });\nconsole.log(__vite_ssr_import_0__.default.bar);\n", ); } @@ -624,7 +670,7 @@ mod test { fn named_import() { test_same( "import { ref } from 'vue';function foo() { return ref(0) }", - "const __vite_ssr_import_1__ = await __vite_ssr_import__('vue', { importedNames: ['ref'] });\nfunction foo() {\n\treturn __vite_ssr_import_1__.ref(0);\n}\n", + "const __vite_ssr_import_0__ = await __vite_ssr_import__('vue', { importedNames: ['ref'] });\nfunction foo() {\n\treturn __vite_ssr_import_0__.ref(0);\n}\n", ); } @@ -632,7 +678,7 @@ mod test { fn named_import_arbitrary_module_namespace() { test_same( "import { \"some thing\" as ref } from 'vue';function foo() { return ref(0) }", - "const __vite_ssr_import_1__ = await __vite_ssr_import__('vue', { importedNames: ['some thing'] });\nfunction foo() {\n\treturn __vite_ssr_import_1__['some thing'](0);\n}\n", + "const __vite_ssr_import_0__ = await __vite_ssr_import__('vue', { importedNames: ['some thing'] });\nfunction foo() {\n\treturn __vite_ssr_import_0__['some thing'](0);\n}\n", ); } @@ -640,7 +686,7 @@ mod test { fn namespace_import() { test_same( "import * as vue from 'vue';function foo() { return vue.ref(0) }", - "const __vite_ssr_import_1__ = await __vite_ssr_import__('vue');\nfunction foo() {\n\treturn __vite_ssr_import_1__.ref(0);\n}\n", + "const __vite_ssr_import_0__ = await __vite_ssr_import__('vue');\nfunction foo() {\n\treturn __vite_ssr_import_0__.ref(0);\n}\n", ); } @@ -680,7 +726,7 @@ mod test { fn export_named_from() { test_same( "export { ref, computed as c } from 'vue'", - "const __vite_ssr_import_1__ = await __vite_ssr_import__('vue', { importedNames: ['ref', 'computed'] });\nObject.defineProperty(__vite_ssr_exports__, 'ref', {\n\tenumerable: true,\n\tconfigurable: true,\n\tget() {\n\t\treturn __vite_ssr_import_1__.ref;\n\t}\n});\nObject.defineProperty(__vite_ssr_exports__, 'c', {\n\tenumerable: true,\n\tconfigurable: true,\n\tget() {\n\t\treturn __vite_ssr_import_1__.computed;\n\t}\n});\n", + "const __vite_ssr_import_0__ = await __vite_ssr_import__('vue', { importedNames: ['ref', 'computed'] });\nObject.defineProperty(__vite_ssr_exports__, 'ref', {\n\tenumerable: true,\n\tconfigurable: true,\n\tget() {\n\t\treturn __vite_ssr_import_0__.ref;\n\t}\n});\nObject.defineProperty(__vite_ssr_exports__, 'c', {\n\tenumerable: true,\n\tconfigurable: true,\n\tget() {\n\t\treturn __vite_ssr_import_0__.computed;\n\t}\n});\n", ); } @@ -688,7 +734,7 @@ mod test { fn export_star_from() { test_same( "export * from 'vue'\nexport * from 'react'", - "const __vite_ssr_import_1__ = await __vite_ssr_import__('vue');\n__vite_ssr_exportAll__(__vite_ssr_import_1__);\nconst __vite_ssr_import_2__ = await __vite_ssr_import__('react');\n__vite_ssr_exportAll__(__vite_ssr_import_2__);\n", + "const __vite_ssr_import_0__ = await __vite_ssr_import__('vue');\n__vite_ssr_exportAll__(__vite_ssr_import_0__);\nconst __vite_ssr_import_1__ = await __vite_ssr_import__('react');\n__vite_ssr_exportAll__(__vite_ssr_import_1__);\n", ); } @@ -696,7 +742,7 @@ mod test { fn export_star_as_from() { test_same( "export * as foo from 'vue'", - "const __vite_ssr_import_1__ = await __vite_ssr_import__('vue');\nObject.defineProperty(__vite_ssr_exports__, 'foo', {\n\tenumerable: true,\n\tconfigurable: true,\n\tget() {\n\t\treturn __vite_ssr_import_1__;\n\t}\n});\n", + "const __vite_ssr_import_0__ = await __vite_ssr_import__('vue');\nObject.defineProperty(__vite_ssr_exports__, 'foo', {\n\tenumerable: true,\n\tconfigurable: true,\n\tget() {\n\t\treturn __vite_ssr_import_0__;\n\t}\n});\n", ); } @@ -704,12 +750,12 @@ mod test { fn re_export_by_imported_name() { test_same( "import * as foo from 'foo'; export * as foo from 'foo'", - "const __vite_ssr_import_1__ = await __vite_ssr_import__('foo');\nconst __vite_ssr_import_2__ = await __vite_ssr_import__('foo');\nObject.defineProperty(__vite_ssr_exports__, 'foo', {\n\tenumerable: true,\n\tconfigurable: true,\n\tget() {\n\t\treturn __vite_ssr_import_2__;\n\t}\n});\n", + "const __vite_ssr_import_0__ = await __vite_ssr_import__('foo');\nconst __vite_ssr_import_1__ = await __vite_ssr_import__('foo');\nObject.defineProperty(__vite_ssr_exports__, 'foo', {\n\tenumerable: true,\n\tconfigurable: true,\n\tget() {\n\t\treturn __vite_ssr_import_1__;\n\t}\n});\n", ); test_same( "import { foo } from 'foo'; export { foo } from 'foo'", - "const __vite_ssr_import_1__ = await __vite_ssr_import__('foo', { importedNames: ['foo'] });\nconst __vite_ssr_import_2__ = await __vite_ssr_import__('foo', { importedNames: ['foo'] });\nObject.defineProperty(__vite_ssr_exports__, 'foo', {\n\tenumerable: true,\n\tconfigurable: true,\n\tget() {\n\t\treturn __vite_ssr_import_2__.foo;\n\t}\n});\n", + "const __vite_ssr_import_0__ = await __vite_ssr_import__('foo', { importedNames: ['foo'] });\nconst __vite_ssr_import_1__ = await __vite_ssr_import__('foo', { importedNames: ['foo'] });\nObject.defineProperty(__vite_ssr_exports__, 'foo', {\n\tenumerable: true,\n\tconfigurable: true,\n\tget() {\n\t\treturn __vite_ssr_import_1__.foo;\n\t}\n});\n", ); } @@ -717,7 +763,7 @@ mod test { fn export_arbitrary_namespace() { test_same( "export * as \"arbitrary string\" from 'vue'", - "const __vite_ssr_import_1__ = await __vite_ssr_import__('vue');\nObject.defineProperty(__vite_ssr_exports__, 'arbitrary string', {\n\tenumerable: true,\n\tconfigurable: true,\n\tget() {\n\t\treturn __vite_ssr_import_1__;\n\t}\n});\n", + "const __vite_ssr_import_0__ = await __vite_ssr_import__('vue');\nObject.defineProperty(__vite_ssr_exports__, 'arbitrary string', {\n\tenumerable: true,\n\tconfigurable: true,\n\tget() {\n\t\treturn __vite_ssr_import_0__;\n\t}\n});\n", ); test_same( @@ -727,7 +773,7 @@ mod test { test_same( "export { \"arbitrary string2\" as \"arbitrary string\" } from 'vue'", - "const __vite_ssr_import_1__ = await __vite_ssr_import__('vue', { importedNames: ['arbitrary string2'] });\nObject.defineProperty(__vite_ssr_exports__, 'arbitrary string', {\n\tenumerable: true,\n\tconfigurable: true,\n\tget() {\n\t\treturn __vite_ssr_import_1__['arbitrary string2'];\n\t}\n});\n", + "const __vite_ssr_import_0__ = await __vite_ssr_import__('vue', { importedNames: ['arbitrary string2'] });\nObject.defineProperty(__vite_ssr_exports__, 'arbitrary string', {\n\tenumerable: true,\n\tconfigurable: true,\n\tget() {\n\t\treturn __vite_ssr_import_0__['arbitrary string2'];\n\t}\n});\n", ); } @@ -743,7 +789,7 @@ mod test { fn export_then_import_minified() { test_same( "export * from 'vue';import {createApp} from 'vue'", - "const __vite_ssr_import_1__ = await __vite_ssr_import__('vue');\n__vite_ssr_exportAll__(__vite_ssr_import_1__);\nconst __vite_ssr_import_2__ = await __vite_ssr_import__('vue', { importedNames: ['createApp'] });\n", + "const __vite_ssr_import_0__ = await __vite_ssr_import__('vue');\n__vite_ssr_exportAll__(__vite_ssr_import_0__);\nconst __vite_ssr_import_1__ = await __vite_ssr_import__('vue', { importedNames: ['createApp'] });\n", ); } @@ -751,7 +797,7 @@ mod test { fn hoist_import_to_top() { test_same( "path.resolve('server.js');import path from 'node:path';", - "const __vite_ssr_import_1__ = await __vite_ssr_import__('node:path', { importedNames: ['default'] });\n__vite_ssr_import_1__.default.resolve('server.js');\n", + "const __vite_ssr_import_0__ = await __vite_ssr_import__('node:path', { importedNames: ['default'] });\n__vite_ssr_import_0__.default.resolve('server.js');\n", ); } @@ -768,9 +814,65 @@ mod test { ); } + #[test] + fn do_not_rewrite_method_definition() { + test_same( + "import { fn } from 'vue';class A { fn() { fn() } }", + "const __vite_ssr_import_0__ = await __vite_ssr_import__('vue', { importedNames: ['fn'] });\nclass A {\n\tfn() {\n\t\t__vite_ssr_import_0__.fn();\n\t}\n}\n", + ); + } + + #[test] + fn do_not_rewrite_when_variable_in_scope() { + test_same( + "import { fn } from 'vue';function A(){ const fn = () => {}; return { fn }; }", + "const __vite_ssr_import_0__ = await __vite_ssr_import__('vue', { importedNames: ['fn'] });\nfunction A() {\n\tconst fn = () => {};\n\treturn { fn };\n}\n", + ); + } + + #[test] + fn do_not_rewrite_destructuring_object() { + test_same( + "import { fn } from 'vue';function A(){ let {fn, test} = {fn: 'foo', test: 'bar'}; return { fn }; }", + "const __vite_ssr_import_0__ = await __vite_ssr_import__('vue', { importedNames: ['fn'] });\nfunction A() {\n\tlet { fn, test } = {\n\t\tfn: 'foo',\n\t\ttest: 'bar'\n\t};\n\treturn { fn };\n}\n", + ); + } + + #[test] + fn do_not_rewrite_destructuring_array() { + test_same( + "import { fn } from 'vue';function A(){ let [fn, test] = ['foo', 'bar']; return { fn }; }", + "const __vite_ssr_import_0__ = await __vite_ssr_import__('vue', { importedNames: ['fn'] });\nfunction A() {\n\tlet [fn, test] = ['foo', 'bar'];\n\treturn { fn };\n}\n", + ); + } + + #[test] + fn do_not_rewrite_catch_clause() { + test_same( + "import {error} from './dependency';try {} catch(error) {}", + "const __vite_ssr_import_0__ = await __vite_ssr_import__('./dependency', { importedNames: ['error'] });\ntry {} catch (error) {}\n", + ); + } + + #[test] + fn should_declare_imported_super_class() { + test_same( + "import { Foo } from './dependency'; class A extends Foo {}", + "const __vite_ssr_import_0__ = await __vite_ssr_import__('./dependency', { importedNames: ['Foo'] });\nclass A extends __vite_ssr_import_0__.Foo {}\n", + ); + } + + #[test] + fn should_declare_exported_super_class() { + test_same( + "import { Foo } from './dependency'; export default class A extends Foo {} export class B extends Foo {}", + "const __vite_ssr_import_0__ = await __vite_ssr_import__('./dependency', { importedNames: ['Foo'] });\nclass A extends __vite_ssr_import_0__.Foo {}\nObject.defineProperty(__vite_ssr_exports__, 'default', {\n\tenumerable: true,\n\tconfigurable: true,\n\tget() {\n\t\treturn A;\n\t}\n});\nclass B extends __vite_ssr_import_0__.Foo {}\nObject.defineProperty(__vite_ssr_exports__, 'B', {\n\tenumerable: true,\n\tconfigurable: true,\n\tget() {\n\t\treturn B;\n\t}\n});\n", + ); + } + /// #[test] - fn handle_default_export_variants() { + fn should_handle_default_export_variants() { // default anonymous functions test_same( "export default function() {}", @@ -797,4 +899,667 @@ mod test { "class A {}\nObject.defineProperty(__vite_ssr_exports__, 'default', {\n\tenumerable: true,\n\tconfigurable: true,\n\tget() {\n\t\treturn A;\n\t}\n});\nclass B extends A {}\nObject.defineProperty(__vite_ssr_exports__, 'B', {\n\tenumerable: true,\n\tconfigurable: true,\n\tget() {\n\t\treturn B;\n\t}\n});\n", ); } + + #[test] + fn overwrite_bindings() { + test_same( + "import { inject } from 'vue'; + const a = { inject } + const b = { test: inject } + function c() { const { test: inject } = { test: true }; console.log(inject) } + const d = inject + function f() { console.log(inject) } + function e() { const { inject } = { inject: true } } + function g() { const f = () => { const inject = true }; console.log(inject) }", + "const __vite_ssr_import_0__ = await __vite_ssr_import__('vue', { importedNames: ['inject'] });\nconst a = { inject: __vite_ssr_import_0__.inject };\nconst b = { test: __vite_ssr_import_0__.inject };\nfunction c() {\n\tconst { test: inject } = { test: true };\n\tconsole.log(inject);\n}\nconst d = __vite_ssr_import_0__.inject;\nfunction f() {\n\tconsole.log(__vite_ssr_import_0__.inject);\n}\nfunction e() {\n\tconst { inject } = { inject: true };\n}\nfunction g() {\n\tconst f = () => {\n\t\tconst inject = true;\n\t};\n\tconsole.log(__vite_ssr_import_0__.inject);\n}\n", + ); + } + + #[test] + fn empty_array_pattern() { + test_same("const [, LHS, RHS] = inMatch;", "const [, LHS, RHS] = inMatch;\n"); + } + + #[test] + fn function_argument_destructure() { + test_same( + " + import { foo, bar } from 'foo' + const a = ({ _ = foo() }) => {} + function b({ _ = bar() }) {} + function c({ _ = bar() + foo() }) {}", + "const __vite_ssr_import_0__ = await __vite_ssr_import__('foo', { importedNames: ['foo', 'bar'] }); +const a = ({ _ = __vite_ssr_import_0__.foo() }) => {}; +function b({ _ = __vite_ssr_import_0__.bar() }) {} +function c({ _ = __vite_ssr_import_0__.bar() + __vite_ssr_import_0__.foo() }) {}\n", + ); + } + + #[test] + fn object_destructure_alias() { + test_same( + " + import { n } from 'foo' + const a = () => { + const { type: n = 'bar' } = {} + console.log(n) + }", + "const __vite_ssr_import_0__ = await __vite_ssr_import__('foo', { importedNames: ['n'] }); +const a = () => { +\tconst { type: n = 'bar' } = {}; +\tconsole.log(n); +};\n", + ); + + // https://github.com/vitejs/vite/issues/9585 + test_same( + " + import { n, m } from 'foo' + const foo = {} + { + const { [n]: m } = foo + }", + "const __vite_ssr_import_0__ = await __vite_ssr_import__('foo', { importedNames: ['n', 'm'] }); +const foo = {}; +{ +\tconst { [__vite_ssr_import_0__.n]: m } = foo; +}\n", + ); + } + + #[test] + fn nested_object_destructure_alias() { + test_same( + "import { remove, add, get, set, rest, objRest } from 'vue' + + function a() { + const { + o: { remove }, + a: { b: { c: [ add ] }}, + d: [{ get }, set, ...rest], + ...objRest + } = foo + + remove() + add() + get() + set() + rest() + objRest() + } + + remove() + add() + get() + set() + rest() + objRest()", + "const __vite_ssr_import_0__ = await __vite_ssr_import__('vue', { importedNames: ['remove', 'add', 'get', 'set', 'rest', 'objRest'] }); + + function a() { + const { + o: { remove }, + a: { b: { c: [ add ] }}, + d: [{ get }, set, ...rest], + ...objRest + } = foo; + + remove(); + add(); + get(); + set(); + rest(); + objRest(); + } + + __vite_ssr_import_0__.remove(); + __vite_ssr_import_0__.add(); + __vite_ssr_import_0__.get(); + __vite_ssr_import_0__.set(); + __vite_ssr_import_0__.rest(); + __vite_ssr_import_0__.objRest();", + ); + } + + #[test] + fn object_props_and_methods() { + test_same( + "import foo from 'foo' + + const bar = 'bar' + + const obj = { + foo() {}, + [foo]() {}, + [bar]() {}, + foo: () => {}, + [foo]: () => {}, + [bar]: () => {}, + bar(foo) {} + }", + "const __vite_ssr_import_0__ = await __vite_ssr_import__('foo', { importedNames: ['default'] }); + + const bar = 'bar'; + + const obj = { + foo() {}, + [__vite_ssr_import_0__.default]() {}, + [bar]() {}, + foo: () => {}, + [__vite_ssr_import_0__.default]: () => {}, + [bar]: () => {}, + bar(foo) {} + };", + ); + } + + // FIXME: The output has adjusted, need to confirm if it's correct + #[test] + fn class_props() { + test_same( + "import { remove, add } from 'vue' + + class A { + remove = 1 + add = null + }", + "const __vite_ssr_import_0__ = await __vite_ssr_import__('vue', { importedNames: ['remove', 'add'] }); + + // const add = __vite_ssr_import_0__.add; + // const remove = __vite_ssr_import_0__.remove; + class A { + remove = 1; + add = null; + }", + ); + } + + #[test] + fn class_methods() { + test_same( + "import foo from 'foo' + + const bar = 'bar' + + class A { + foo() {} + [foo]() {} + [bar]() {} + #foo() {} + bar(foo) {} + }", + "const __vite_ssr_import_0__ = await __vite_ssr_import__('foo', { importedNames: ['default'] }); + + const bar = 'bar'; + + class A { + foo() {} + [__vite_ssr_import_0__.default]() {} + [bar]() {} + #foo() {} + bar(foo) {} + }", + ); + } + + #[test] + fn declare_scope() { + test_same( + "import { aaa, bbb, ccc, ddd } from 'vue' + + function foobar() { + ddd() + + const aaa = () => { + bbb(ccc) + ddd() + } + const bbb = () => { + console.log('hi') + } + const ccc = 1 + function ddd() {} + + aaa() + bbb() + ccc() + } + + aaa() + bbb()", + r"const __vite_ssr_import_0__ = await __vite_ssr_import__('vue', { importedNames: ['aaa', 'bbb', 'ccc', 'ddd'] }); + + function foobar() { + ddd(); + + const aaa = () => { + bbb(ccc); + ddd(); + }; + const bbb = () => { + console.log('hi'); + }; + const ccc = 1; + function ddd() {} + + aaa(); + bbb(); + ccc(); + } + + __vite_ssr_import_0__.aaa(); + __vite_ssr_import_0__.bbb();", + ); + } + + #[test] + fn jsx() { + test_same_jsx( + "import React from 'react' + import { Foo, Slot } from 'foo' + + function Bar({ Slot = }) { + return ( + <> + + + ) + }", + "const __vite_ssr_import_0__ = await __vite_ssr_import__('react', { importedNames: ['default'] }); + const __vite_ssr_import_1__ = await __vite_ssr_import__('foo', { importedNames: ['Foo', 'Slot'] }); + function Bar({ Slot = __vite_ssr_import_0__.default.createElement(__vite_ssr_import_1__.Foo, null) }) { + \treturn __vite_ssr_import_0__.default.createElement(__vite_ssr_import_0__.default.Fragment, null, __vite_ssr_import_0__.default.createElement(Slot, null)); + }\n", + ); + } + + #[test] + fn continuous_exports() { + test_same( + "export function fn1() { + }export function fn2() { + }", + "function fn1() { + } + Object.defineProperty(__vite_ssr_exports__, 'fn1', { + \tenumerable: true, + \tconfigurable: true, + \tget() { + \t\treturn fn1; + \t} + });\nfunction fn2() { + } + Object.defineProperty(__vite_ssr_exports__, 'fn2', { + \tenumerable: true, + \tconfigurable: true, + \tget() { + \t\treturn fn2; + \t} + });\n", + ); + } + + // https://github.com/vitest-dev/vitest/issues/1141 + #[test] + fn export_default_expression() { + // esbuild transform result of following TS code + // export default function getRandom() { + // return Math.random() + // } + test_same( + "export default (function getRandom() { + return Math.random(); + });", + "Object.defineProperty(__vite_ssr_exports__, 'default', { + \tenumerable: true, + \tconfigurable: true, + \tget() { + \t\treturn function getRandom() { + \t\t\treturn Math.random(); + \t\t}; + \t} + });\n", + ); + + test_same( + "export default (class A {});", + "Object.defineProperty(__vite_ssr_exports__, 'default', { + \tenumerable: true, + \tconfigurable: true, + \tget() { + \t\treturn class A {}; + \t} + });\n", + ); + } + + // https://github.com/vitejs/vite/issues/8002 + #[test] + fn with_hashbang() { + test_same( + "#!/usr/bin/env node + console.log(\"it can parse the hashbang\")", + "#!/usr/bin/env node + console.log(\"it can parse the hashbang\");\n", + ); + } + + #[test] + fn import_hoisted_after_hashbang() { + test_same( + "#!/usr/bin/env node + console.log(foo); + import foo from \"foo\"", + "#!/usr/bin/env node + const __vite_ssr_import_0__ = await __vite_ssr_import__('foo', { importedNames: ['default'] });\nconsole.log(__vite_ssr_import_0__.default);\n", + ); + } + + #[test] + fn identity_function_helper_injected_after_hashbang() { + test_same( + "#!/usr/bin/env node + import { foo } from \"foo\" + foo()", + "#!/usr/bin/env node + const __vite_ssr_import_0__ = await __vite_ssr_import__('foo', { importedNames: ['foo'] });\n__vite_ssr_import_0__.foo();\n", + ); + } + + // https://github.com/vitejs/vite/issues/10289 + #[test] + fn track_scope_by_class_function_condition_blocks() { + test_same( + "import { foo, bar } from 'foobar' + if (false) { + const foo = 'foo' + console.log(foo) + } else if (false) { + const [bar] = ['bar'] + console.log(bar) + } else { + console.log(foo) + console.log(bar) + } + export class Test { + constructor() { + if (false) { + const foo = 'foo' + console.log(foo) + } else if (false) { + const [bar] = ['bar'] + console.log(bar) + } else { + console.log(foo) + console.log(bar) + } + } + }", + "const __vite_ssr_import_0__ = await __vite_ssr_import__('foobar', { importedNames: ['foo', 'bar'] });\nif (false) {\n\tconst foo = 'foo';\n\tconsole.log(foo);\n} else if (false) {\n\tconst [bar] = ['bar'];\n\tconsole.log(bar);\n} else {\n\tconsole.log(__vite_ssr_import_0__.foo);\n\tconsole.log(__vite_ssr_import_0__.bar);\n}\nclass Test {\n\tconstructor() {\n\t\tif (false) {\n\t\t\tconst foo = 'foo';\n\t\t\tconsole.log(foo);\n\t\t} else if (false) {\n\t\t\tconst [bar] = ['bar'];\n\t\t\tconsole.log(bar);\n\t\t} else {\n\t\t\tconsole.log(__vite_ssr_import_0__.foo);\n\t\t\tconsole.log(__vite_ssr_import_0__.bar);\n\t\t}\n\t}\n}\nObject.defineProperty(__vite_ssr_exports__, 'Test', {\n\tenumerable: true,\n\tconfigurable: true,\n\tget() {\n\t\treturn Test;\n\t}\n});\n", + ); + } + + // https://github.com/vitejs/vite/issues/10386 + #[test] + fn track_var_scope_by_function() { + test_same( + "import { foo, bar } from 'foobar' + function test() { + if (true) { + var foo = () => { var why = 'would' }, bar = 'someone' + } + return [foo, bar] + }", + "const __vite_ssr_import_0__ = await __vite_ssr_import__('foobar', { importedNames: ['foo', 'bar'] });\nfunction test() {\n\tif (true) {\n\t\tvar foo = () => { var why = 'would'; }, bar = 'someone';\n\t}\n\treturn [foo, bar];\n}\n", + ); + } + + // https://github.com/vitejs/vite/issues/11806 + #[test] + fn track_scope_by_blocks() { + test_same( + "import { foo, bar, baz } from 'foobar' + function test() { + [foo]; + { + let foo = 10; + let bar = 10; + } + try {} catch (baz){ baz } + return bar; + }", + "const __vite_ssr_import_0__ = await __vite_ssr_import__('foobar', { importedNames: ['foo', 'bar', 'baz'] });\nfunction test() {\n\t[__vite_ssr_import_0__.foo];\n\t{\n\t\tlet foo = 10;\n\t\tlet bar = 10;\n\t}\n\ttry {} catch (baz) { baz; }\n\treturn __vite_ssr_import_0__.bar;\n}\n", + ); + } + + #[test] + fn track_scope_in_for_loops() { + test_same( + "import { test } from './test.js' + + for (const test of tests) { + console.log(test) + } + + for (let test = 0; test < 10; test++) { + console.log(test) + } + + for (const test in tests) { + console.log(test) + }", + "const __vite_ssr_import_0__ = await __vite_ssr_import__('./test.js', { importedNames: ['test'] });\nfor (const test of tests) {\n\tconsole.log(test);\n}\nfor (let test = 0; test < 10; test++) {\n\tconsole.log(test);\n}\nfor (const test in tests) {\n\tconsole.log(test);\n}\n", + ); + } + + #[test] + fn avoid_binding_class_expression() { + test_same( + "import Foo, { Bar } from './foo'; + + console.log(Foo, Bar); + const obj = { + foo: class Foo {}, + bar: class Bar {} + } + const Baz = class extends Foo {}", + "const __vite_ssr_import_0__ = await __vite_ssr_import__('./foo', { importedNames: ['default', 'Bar'] });\nconsole.log(__vite_ssr_import_0__.default, __vite_ssr_import_0__.Bar);\nconst obj = {\n\tfoo: class Foo {},\n\tbar: class Bar {}\n};\nconst Baz = class extends __vite_ssr_import_0__.default {};\n", + ); + } + + #[test] + fn import_assertion_attribute() { + test_same( + "import * as foo from './foo.json' with { type: 'json' }; + import('./bar.json', { with: { type: 'json' } });", + "const __vite_ssr_import_0__ = await __vite_ssr_import__('./foo.json');\n__vite_ssr_dynamic_import__('./bar.json', { with: { type: 'json' } });\n", + ); + } + + #[test] + fn import_and_export_ordering() { + // Given all imported modules logs `mod ${mod}` on execution, + // and `foo` is `bar`, the logging order should be: + // "mod a", "mod foo", "mod b", "bar1", "bar2" + test_same( + "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']}); + 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__); + console.log(__vite_ssr_import_1__.foo + 2) + ", + ); + } + + #[test] + fn identity_function_is_declared_before_used() { + test_same( + "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'] });\nObject.defineProperty(__vite_ssr_exports__, 'default', {\n\tenumerable: true,\n\tconfigurable: true,\n\tget() {\n\t\treturn __vite_ssr_import_0__.foo();\n\t}\n});\nconst __vite_ssr_import_1__ = await __vite_ssr_import__('./bar');\nObject.defineProperty(__vite_ssr_exports__, 'bar', {\n\tenumerable: true,\n\tconfigurable: true,\n\tget() {\n\t\treturn __vite_ssr_import_1__;\n\t}\n});\nconsole.log(bar);\n", + ); + } + + #[test] + fn inject_semicolon_call_wrapper() { + let code = "import { f } from './f' + + let x = 0; + + x + f() + + if (1) + x + f() + + if (1) + x + else + x + f() + + + let y = x + f() + + x /*;;*/ /*;;*/ + f() + + function z() { + x + f() + + if (1) { + x + f() + } + } + + let a = {} + f() + + let b = () => {} + f() + + function c() { + } + f() + + class D { + } + f() + + { + x + } + f() + + switch (1) { + case 1: + x + f() + break + } + + if(0){}f() + + if(0){}else{}f() + + switch(1){}f() + + {}f(1)"; + + let expected = " + + const __vite_ssr_import_0__ = await __vite_ssr_import__('./f', {importedNames:['f']}); + + let x = 0; + + x; + (0,__vite_ssr_import_0__.f)(); + + if (1) + x; + (0,__vite_ssr_import_0__.f)(); + + if (1) + x + else + x; + (0,__vite_ssr_import_0__.f)(); + + + let y = x; + (0,__vite_ssr_import_0__.f)(); + + x; /*;;*/ /*;;*/ + (0,__vite_ssr_import_0__.f)(); + + function z() { + x; + (0,__vite_ssr_import_0__.f)(); + + if (1) { + x; + (0,__vite_ssr_import_0__.f)() + } + } + + let a = {}; + (0,__vite_ssr_import_0__.f)(); + + let b = () => {}; + (0,__vite_ssr_import_0__.f)(); + + function c() { + } + (0,__vite_ssr_import_0__.f)(); + + class D { + } + (0,__vite_ssr_import_0__.f)(); + + { + x + } + (0,__vite_ssr_import_0__.f)(); + + switch (1) { + case 1: + x; + (0,__vite_ssr_import_0__.f)(); + break + }; + + if(0){};(0,__vite_ssr_import_0__.f)(); + + if(0){}else{};(0,__vite_ssr_import_0__.f)(); + + switch(1){};(0,__vite_ssr_import_0__.f)(); + + {}(0,__vite_ssr_import_0__.f)(1) + "; + test_same(code, expected); + } + + #[test] + fn does_not_break_minified_code() { + // Based on https://unpkg.com/@headlessui/vue@1.7.23/dist/components/transitions/transition.js + test_same( + "import O from 'a'; + const c = () => { + if(true){return}O(1,{}) + }", + "const __vite_ssr_import_0__ = await __vite_ssr_import__('a', { importedNames: ['default'] });\nconst c = () => {\n\tif (true) {\n\t\treturn;\n\t}\n\t__vite_ssr_import_0__.default(1, {});\n};\n", + ); + } } From d076d421dfec04c0ea43bb19a409ce24c27cb5c9 Mon Sep 17 00:00:00 2001 From: Dunqing Date: Thu, 27 Feb 2025 16:00:34 +0800 Subject: [PATCH 13/30] wrap with (0, ...) to avoid method binding `this` --- .../src/vite/module_runner_transform.rs | 27 +++++++++++++------ 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/crates/oxc_transformer/src/vite/module_runner_transform.rs b/crates/oxc_transformer/src/vite/module_runner_transform.rs index cd7e401cca15c..73c30bf0328d3 100644 --- a/crates/oxc_transformer/src/vite/module_runner_transform.rs +++ b/crates/oxc_transformer/src/vite/module_runner_transform.rs @@ -22,7 +22,7 @@ use oxc_ecmascript::BoundNames; use oxc_semantic::{ReferenceFlags, ScopeFlags, SymbolFlags, SymbolId}; use oxc_span::SPAN; use oxc_syntax::identifier::is_identifier_name; -use oxc_traverse::{BoundIdentifier, Traverse, TraverseCtx}; +use oxc_traverse::{Ancestor, BoundIdentifier, Traverse, TraverseCtx}; use crate::utils::ast_builder::{ create_compute_property_access, create_member_callee, create_property_access, @@ -147,14 +147,25 @@ impl<'a> ModuleRunnerTransform<'a> { }; if let Some((binding, property)) = self.import_bindings.get(&symbol_id) { let object = binding.create_read_expression(ctx); - if let Some(property) = property { + let object = if let Some(property) = property { // TODO(improvement): It looks like here could always return a computed member expression, // so that we don't need to check if it's an identifier name. - *expr = if is_identifier_name(property) { + if is_identifier_name(property) { create_property_access(ident.span, object, property, ctx) } else { create_compute_property_access(ident.span, object, property, ctx) - }; + } + } else { + object + }; + + if matches!(ctx.parent(), Ancestor::CallExpressionCallee(_)) { + // wrap with (0, ...) to avoid method binding `this` + // + let zero = + ctx.ast.expression_numeric_literal(SPAN, 0f64, None, NumberBase::Decimal); + let expressions = ctx.ast.vec_from_array([zero, object]); + *expr = ctx.ast.expression_sequence(ident.span, expressions); } else { *expr = object; } @@ -1538,13 +1549,13 @@ const foo = {}; x; (0,__vite_ssr_import_0__.f)(); break - }; + } - if(0){};(0,__vite_ssr_import_0__.f)(); + if(0){}(0,__vite_ssr_import_0__.f)(); - if(0){}else{};(0,__vite_ssr_import_0__.f)(); + if(0){}else{}(0,__vite_ssr_import_0__.f)(); - switch(1){};(0,__vite_ssr_import_0__.f)(); + switch(1){}(0,__vite_ssr_import_0__.f)(); {}(0,__vite_ssr_import_0__.f)(1) "; From ec98b8cfa56f6da52b8de406d5e13e222aa58d83 Mon Sep 17 00:00:00 2001 From: Dunqing Date: Thu, 27 Feb 2025 16:23:16 +0800 Subject: [PATCH 14/30] format tests and fix incorrect tests --- .../src/vite/module_runner_transform.rs | 644 +++++++++++++----- 1 file changed, 476 insertions(+), 168 deletions(-) diff --git a/crates/oxc_transformer/src/vite/module_runner_transform.rs b/crates/oxc_transformer/src/vite/module_runner_transform.rs index 73c30bf0328d3..93765149ac796 100644 --- a/crates/oxc_transformer/src/vite/module_runner_transform.rs +++ b/crates/oxc_transformer/src/vite/module_runner_transform.rs @@ -673,7 +673,8 @@ mod test { fn default_import() { test_same( "import foo from 'vue';console.log(foo.bar)", - "const __vite_ssr_import_0__ = await __vite_ssr_import__('vue', { importedNames: ['default'] });\nconsole.log(__vite_ssr_import_0__.default.bar);\n", + "const __vite_ssr_import_0__ = await __vite_ssr_import__('vue', { importedNames: ['default'] }); +console.log(__vite_ssr_import_0__.default.bar);", ); } @@ -681,7 +682,10 @@ mod test { fn named_import() { test_same( "import { ref } from 'vue';function foo() { return ref(0) }", - "const __vite_ssr_import_0__ = await __vite_ssr_import__('vue', { importedNames: ['ref'] });\nfunction foo() {\n\treturn __vite_ssr_import_0__.ref(0);\n}\n", + "const __vite_ssr_import_0__ = await __vite_ssr_import__('vue', { importedNames: ['ref'] }); +function foo() { + return (0, __vite_ssr_import_0__.ref)(0); +}", ); } @@ -689,7 +693,10 @@ mod test { fn named_import_arbitrary_module_namespace() { test_same( "import { \"some thing\" as ref } from 'vue';function foo() { return ref(0) }", - "const __vite_ssr_import_0__ = await __vite_ssr_import__('vue', { importedNames: ['some thing'] });\nfunction foo() {\n\treturn __vite_ssr_import_0__['some thing'](0);\n}\n", + "const __vite_ssr_import_0__ = await __vite_ssr_import__('vue', { importedNames: ['some thing'] }); +function foo() { + return (0, __vite_ssr_import_0__['some thing'])(0); +}", ); } @@ -697,7 +704,10 @@ mod test { fn namespace_import() { test_same( "import * as vue from 'vue';function foo() { return vue.ref(0) }", - "const __vite_ssr_import_0__ = await __vite_ssr_import__('vue');\nfunction foo() {\n\treturn __vite_ssr_import_0__.ref(0);\n}\n", + "const __vite_ssr_import_0__ = await __vite_ssr_import__('vue'); +function foo() { + return __vite_ssr_import_0__.ref(0); +}", ); } @@ -705,7 +715,14 @@ mod test { fn export_function_declaration() { test_same( "export function foo() {}", - "function foo() {}\nObject.defineProperty(__vite_ssr_exports__, 'foo', {\n\tenumerable: true,\n\tconfigurable: true,\n\tget() {\n\t\treturn foo;\n\t}\n});\n", + "function foo() {} +Object.defineProperty(__vite_ssr_exports__, 'foo', { + enumerable: true, + configurable: true, + get() { + return foo; + } +});", ); } @@ -713,7 +730,14 @@ mod test { fn export_class_declaration() { test_same( "export class foo {}", - "class foo {}\nObject.defineProperty(__vite_ssr_exports__, 'foo', {\n\tenumerable: true,\n\tconfigurable: true,\n\tget() {\n\t\treturn foo;\n\t}\n});\n", + "class foo {} +Object.defineProperty(__vite_ssr_exports__, 'foo', { + enumerable: true, + configurable: true, + get() { + return foo; + } +});", ); } @@ -721,7 +745,21 @@ mod test { fn export_var_declaration() { test_same( "export const a = 1, b = 2", - "const a = 1, b = 2;\nObject.defineProperty(__vite_ssr_exports__, 'a', {\n\tenumerable: true,\n\tconfigurable: true,\n\tget() {\n\t\treturn a;\n\t}\n});\nObject.defineProperty(__vite_ssr_exports__, 'b', {\n\tenumerable: true,\n\tconfigurable: true,\n\tget() {\n\t\treturn b;\n\t}\n});\n", + "const a = 1, b = 2; +Object.defineProperty(__vite_ssr_exports__, 'a', { + enumerable: true, + configurable: true, + get() { + return a; + } +}); +Object.defineProperty(__vite_ssr_exports__, 'b', { + enumerable: true, + configurable: true, + get() { + return b; + } +});", ); } @@ -729,7 +767,21 @@ mod test { fn export_named() { test_same( "const a = 1, b = 2; export { a, b as c }", - "const a = 1, b = 2;\nObject.defineProperty(__vite_ssr_exports__, 'a', {\n\tenumerable: true,\n\tconfigurable: true,\n\tget() {\n\t\treturn a;\n\t}\n});\nObject.defineProperty(__vite_ssr_exports__, 'c', {\n\tenumerable: true,\n\tconfigurable: true,\n\tget() {\n\t\treturn b;\n\t}\n});\n", + "const a = 1, b = 2; +Object.defineProperty(__vite_ssr_exports__, 'a', { + enumerable: true, + configurable: true, + get() { + return a; + } +}); +Object.defineProperty(__vite_ssr_exports__, 'c', { + enumerable: true, + configurable: true, + get() { + return b; + } +});", ); } @@ -737,7 +789,21 @@ mod test { 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'] });\nObject.defineProperty(__vite_ssr_exports__, 'ref', {\n\tenumerable: true,\n\tconfigurable: true,\n\tget() {\n\t\treturn __vite_ssr_import_0__.ref;\n\t}\n});\nObject.defineProperty(__vite_ssr_exports__, 'c', {\n\tenumerable: true,\n\tconfigurable: true,\n\tget() {\n\t\treturn __vite_ssr_import_0__.computed;\n\t}\n});\n", + "const __vite_ssr_import_0__ = await __vite_ssr_import__('vue', { importedNames: ['ref', 'computed'] }); +Object.defineProperty(__vite_ssr_exports__, 'ref', { + enumerable: true, + configurable: true, + get() { + return __vite_ssr_import_0__.ref; + } +}); +Object.defineProperty(__vite_ssr_exports__, 'c', { + enumerable: true, + configurable: true, + get() { + return __vite_ssr_import_0__.computed; + } +});", ); } @@ -745,7 +811,10 @@ mod test { fn export_star_from() { test_same( "export * from 'vue'\nexport * from 'react'", - "const __vite_ssr_import_0__ = await __vite_ssr_import__('vue');\n__vite_ssr_exportAll__(__vite_ssr_import_0__);\nconst __vite_ssr_import_1__ = await __vite_ssr_import__('react');\n__vite_ssr_exportAll__(__vite_ssr_import_1__);\n", + "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__('react'); +__vite_ssr_exportAll__(__vite_ssr_import_1__);", ); } @@ -753,7 +822,14 @@ mod test { fn export_star_as_from() { test_same( "export * as foo from 'vue'", - "const __vite_ssr_import_0__ = await __vite_ssr_import__('vue');\nObject.defineProperty(__vite_ssr_exports__, 'foo', {\n\tenumerable: true,\n\tconfigurable: true,\n\tget() {\n\t\treturn __vite_ssr_import_0__;\n\t}\n});\n", + "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__; + } +});", ); } @@ -761,12 +837,28 @@ mod test { 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');\nconst __vite_ssr_import_1__ = await __vite_ssr_import__('foo');\nObject.defineProperty(__vite_ssr_exports__, 'foo', {\n\tenumerable: true,\n\tconfigurable: true,\n\tget() {\n\t\treturn __vite_ssr_import_1__;\n\t}\n});\n", + "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__; + } +});", ); test_same( "import { foo } from 'foo'; export { foo } from 'foo'", - "const __vite_ssr_import_0__ = await __vite_ssr_import__('foo', { importedNames: ['foo'] });\nconst __vite_ssr_import_1__ = await __vite_ssr_import__('foo', { importedNames: ['foo'] });\nObject.defineProperty(__vite_ssr_exports__, 'foo', {\n\tenumerable: true,\n\tconfigurable: true,\n\tget() {\n\t\treturn __vite_ssr_import_1__.foo;\n\t}\n});\n", + "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; + } +});", ); } @@ -774,17 +866,38 @@ mod test { fn export_arbitrary_namespace() { test_same( "export * as \"arbitrary string\" from 'vue'", - "const __vite_ssr_import_0__ = await __vite_ssr_import__('vue');\nObject.defineProperty(__vite_ssr_exports__, 'arbitrary string', {\n\tenumerable: true,\n\tconfigurable: true,\n\tget() {\n\t\treturn __vite_ssr_import_0__;\n\t}\n});\n", + "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__; + } +});", ); test_same( "const something = \"Something\"; export { something as \"arbitrary string\" }", - "const something = 'Something';\nObject.defineProperty(__vite_ssr_exports__, 'arbitrary string', {\n\tenumerable: true,\n\tconfigurable: true,\n\tget() {\n\t\treturn something;\n\t}\n});\n", + "const something = 'Something'; +Object.defineProperty(__vite_ssr_exports__, 'arbitrary string', { + enumerable: true, + configurable: true, + get() { + return something; + } +});", ); test_same( "export { \"arbitrary string2\" as \"arbitrary string\" } from 'vue'", - "const __vite_ssr_import_0__ = await __vite_ssr_import__('vue', { importedNames: ['arbitrary string2'] });\nObject.defineProperty(__vite_ssr_exports__, 'arbitrary string', {\n\tenumerable: true,\n\tconfigurable: true,\n\tget() {\n\t\treturn __vite_ssr_import_0__['arbitrary string2'];\n\t}\n});\n", + "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']; + } +});", ); } @@ -792,7 +905,13 @@ mod test { fn export_default() { test_same( "export default {}", - "Object.defineProperty(__vite_ssr_exports__, 'default', {\n\tenumerable: true,\n\tconfigurable: true,\n\tget() {\n\t\treturn {};\n\t}\n});\n", + "Object.defineProperty(__vite_ssr_exports__, 'default', { + enumerable: true, + configurable: true, + get() { + return {}; + } +});", ); } @@ -800,7 +919,9 @@ mod test { fn export_then_import_minified() { test_same( "export * from 'vue';import {createApp} from 'vue'", - "const __vite_ssr_import_0__ = await __vite_ssr_import__('vue');\n__vite_ssr_exportAll__(__vite_ssr_import_0__);\nconst __vite_ssr_import_1__ = await __vite_ssr_import__('vue', { importedNames: ['createApp'] });\n", + "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'] });", ); } @@ -808,20 +929,28 @@ mod test { fn hoist_import_to_top() { test_same( "path.resolve('server.js');import path from 'node:path';", - "const __vite_ssr_import_0__ = await __vite_ssr_import__('node:path', { importedNames: ['default'] });\n__vite_ssr_import_0__.default.resolve('server.js');\n", + "const __vite_ssr_import_0__ = await __vite_ssr_import__('node:path', { importedNames: ['default'] }); +__vite_ssr_import_0__.default.resolve('server.js');", ); } #[test] fn import_meta() { - test_same("console.log(import.meta.url)", "console.log(__vite_ssr_import_meta__.url);\n"); + test_same("console.log(import.meta.url)", "console.log(__vite_ssr_import_meta__.url);"); } #[test] fn dynamic_import() { test_same( "export const i = () => import('./foo')", - "const i = () => __vite_ssr_dynamic_import__('./foo');\nObject.defineProperty(__vite_ssr_exports__, 'i', {\n\tenumerable: true,\n\tconfigurable: true,\n\tget() {\n\t\treturn i;\n\t}\n});\n", + "const i = () => __vite_ssr_dynamic_import__('./foo'); +Object.defineProperty(__vite_ssr_exports__, 'i', { + enumerable: true, + configurable: true, + get() { + return i; + } +});", ); } @@ -829,7 +958,12 @@ mod test { fn do_not_rewrite_method_definition() { test_same( "import { fn } from 'vue';class A { fn() { fn() } }", - "const __vite_ssr_import_0__ = await __vite_ssr_import__('vue', { importedNames: ['fn'] });\nclass A {\n\tfn() {\n\t\t__vite_ssr_import_0__.fn();\n\t}\n}\n", + "const __vite_ssr_import_0__ = await __vite_ssr_import__('vue', { importedNames: ['fn'] }); +class A { + fn() { + (0, __vite_ssr_import_0__.fn)(); + } +}", ); } @@ -837,7 +971,11 @@ mod test { fn do_not_rewrite_when_variable_in_scope() { test_same( "import { fn } from 'vue';function A(){ const fn = () => {}; return { fn }; }", - "const __vite_ssr_import_0__ = await __vite_ssr_import__('vue', { importedNames: ['fn'] });\nfunction A() {\n\tconst fn = () => {};\n\treturn { fn };\n}\n", + "const __vite_ssr_import_0__ = await __vite_ssr_import__('vue', { importedNames: ['fn'] }); +function A() { + const fn = () => {}; + return { fn }; +}", ); } @@ -845,7 +983,14 @@ mod test { fn do_not_rewrite_destructuring_object() { test_same( "import { fn } from 'vue';function A(){ let {fn, test} = {fn: 'foo', test: 'bar'}; return { fn }; }", - "const __vite_ssr_import_0__ = await __vite_ssr_import__('vue', { importedNames: ['fn'] });\nfunction A() {\n\tlet { fn, test } = {\n\t\tfn: 'foo',\n\t\ttest: 'bar'\n\t};\n\treturn { fn };\n}\n", + "const __vite_ssr_import_0__ = await __vite_ssr_import__('vue', { importedNames: ['fn'] }); +function A() { + let { fn, test } = { + fn: 'foo', + test: 'bar' + }; + return { fn }; +}", ); } @@ -853,7 +998,11 @@ mod test { fn do_not_rewrite_destructuring_array() { test_same( "import { fn } from 'vue';function A(){ let [fn, test] = ['foo', 'bar']; return { fn }; }", - "const __vite_ssr_import_0__ = await __vite_ssr_import__('vue', { importedNames: ['fn'] });\nfunction A() {\n\tlet [fn, test] = ['foo', 'bar'];\n\treturn { fn };\n}\n", + "const __vite_ssr_import_0__ = await __vite_ssr_import__('vue', { importedNames: ['fn'] }); +function A() { + let [fn, test] = ['foo', 'bar']; + return { fn }; +}", ); } @@ -861,7 +1010,8 @@ mod test { fn do_not_rewrite_catch_clause() { test_same( "import {error} from './dependency';try {} catch(error) {}", - "const __vite_ssr_import_0__ = await __vite_ssr_import__('./dependency', { importedNames: ['error'] });\ntry {} catch (error) {}\n", + "const __vite_ssr_import_0__ = await __vite_ssr_import__('./dependency', { importedNames: ['error'] }); +try {} catch (error) {}", ); } @@ -869,7 +1019,8 @@ mod test { fn should_declare_imported_super_class() { test_same( "import { Foo } from './dependency'; class A extends Foo {}", - "const __vite_ssr_import_0__ = await __vite_ssr_import__('./dependency', { importedNames: ['Foo'] });\nclass A extends __vite_ssr_import_0__.Foo {}\n", + "const __vite_ssr_import_0__ = await __vite_ssr_import__('./dependency', { importedNames: ['Foo'] }); +class A extends __vite_ssr_import_0__.Foo {}", ); } @@ -877,7 +1028,23 @@ mod test { fn should_declare_exported_super_class() { test_same( "import { Foo } from './dependency'; export default class A extends Foo {} export class B extends Foo {}", - "const __vite_ssr_import_0__ = await __vite_ssr_import__('./dependency', { importedNames: ['Foo'] });\nclass A extends __vite_ssr_import_0__.Foo {}\nObject.defineProperty(__vite_ssr_exports__, 'default', {\n\tenumerable: true,\n\tconfigurable: true,\n\tget() {\n\t\treturn A;\n\t}\n});\nclass B extends __vite_ssr_import_0__.Foo {}\nObject.defineProperty(__vite_ssr_exports__, 'B', {\n\tenumerable: true,\n\tconfigurable: true,\n\tget() {\n\t\treturn B;\n\t}\n});\n", + "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 A; + } +}); +class B extends __vite_ssr_import_0__.Foo {} +Object.defineProperty(__vite_ssr_exports__, 'B', { + enumerable: true, + configurable: true, + get() { + return B; + } +});", ); } @@ -887,27 +1054,60 @@ mod test { // default anonymous functions test_same( "export default function() {}", - "Object.defineProperty(__vite_ssr_exports__, 'default', {\n\tenumerable: true,\n\tconfigurable: true,\n\tget() {\n\t\treturn function() {};\n\t}\n});\n", + "Object.defineProperty(__vite_ssr_exports__, 'default', { + enumerable: true, + configurable: true, + get() { + return function() {}; + } +});", ); // default anonymous class test_same( "export default class {}", - "Object.defineProperty(__vite_ssr_exports__, 'default', {\n\tenumerable: true,\n\tconfigurable: true,\n\tget() {\n\t\treturn class {};\n\t}\n});\n", + "Object.defineProperty(__vite_ssr_exports__, 'default', { + enumerable: true, + configurable: true, + get() { + return class {}; + } +});", ); // default named functions test_same( "export default function foo() {}\nfoo.prototype = Object.prototype;", - // "function foo() {}\nfoo.prototype = Object.prototype;\nObject.defineProperty(__vite_ssr_exports__, 'default', {\n\tenumerable: true,\n\tconfigurable: true,\n\tget() {\n\t\treturn foo;\n\t}\n});\n", - "function foo() {}\nObject.defineProperty(__vite_ssr_exports__, 'default', {\n\tenumerable: true,\n\tconfigurable: true,\n\tget() {\n\t\treturn foo;\n\t}\n});\nfoo.prototype = Object.prototype;\n", + "function foo() {} +Object.defineProperty(__vite_ssr_exports__, 'default', { + enumerable: true, + configurable: true, + get() { + return foo; + } +}); +foo.prototype = Object.prototype;", ); // default named classes test_same( "export default class A {}\nexport class B extends A {}", - // "class A {}\nclass B extends A {}\nObject.defineProperty(__vite_ssr_exports__, 'B', {\n\tenumerable: true,\n\tconfigurable: true,\n\tget() {\n\t\treturn B;\n\t}\n});\nObject.defineProperty(__vite_ssr_exports__, 'default', {\n\tenumerable: true,\n\tconfigurable: true,\n\tget() {\n\t\treturn A;\n\t}\n});\n", - "class A {}\nObject.defineProperty(__vite_ssr_exports__, 'default', {\n\tenumerable: true,\n\tconfigurable: true,\n\tget() {\n\t\treturn A;\n\t}\n});\nclass B extends A {}\nObject.defineProperty(__vite_ssr_exports__, 'B', {\n\tenumerable: true,\n\tconfigurable: true,\n\tget() {\n\t\treturn B;\n\t}\n});\n", + "class A {} +Object.defineProperty(__vite_ssr_exports__, 'default', { + enumerable: true, + configurable: true, + get() { + return A; + } +}); +class B extends A {} +Object.defineProperty(__vite_ssr_exports__, 'B', { + enumerable: true, + configurable: true, + get() { + return B; + } +});", ); } @@ -922,13 +1122,32 @@ mod test { function f() { console.log(inject) } function e() { const { inject } = { inject: true } } function g() { const f = () => { const inject = true }; console.log(inject) }", - "const __vite_ssr_import_0__ = await __vite_ssr_import__('vue', { importedNames: ['inject'] });\nconst a = { inject: __vite_ssr_import_0__.inject };\nconst b = { test: __vite_ssr_import_0__.inject };\nfunction c() {\n\tconst { test: inject } = { test: true };\n\tconsole.log(inject);\n}\nconst d = __vite_ssr_import_0__.inject;\nfunction f() {\n\tconsole.log(__vite_ssr_import_0__.inject);\n}\nfunction e() {\n\tconst { inject } = { inject: true };\n}\nfunction g() {\n\tconst f = () => {\n\t\tconst inject = true;\n\t};\n\tconsole.log(__vite_ssr_import_0__.inject);\n}\n", + "const __vite_ssr_import_0__ = await __vite_ssr_import__('vue', { importedNames: ['inject'] }); +const a = { inject: __vite_ssr_import_0__.inject }; +const b = { test: __vite_ssr_import_0__.inject }; +function c() { + const { test: inject } = { test: true }; + console.log(inject); +} +const d = __vite_ssr_import_0__.inject; +function f() { + console.log(__vite_ssr_import_0__.inject); +} +function e() { + const { inject } = { inject: true }; +} +function g() { + const f = () => { + const inject = true; + }; + console.log(__vite_ssr_import_0__.inject); +}", ); } #[test] fn empty_array_pattern() { - test_same("const [, LHS, RHS] = inMatch;", "const [, LHS, RHS] = inMatch;\n"); + test_same("const [, LHS, RHS] = inMatch;", "const [, LHS, RHS] = inMatch;"); } #[test] @@ -940,9 +1159,9 @@ mod test { function b({ _ = bar() }) {} function c({ _ = bar() + foo() }) {}", "const __vite_ssr_import_0__ = await __vite_ssr_import__('foo', { importedNames: ['foo', 'bar'] }); -const a = ({ _ = __vite_ssr_import_0__.foo() }) => {}; -function b({ _ = __vite_ssr_import_0__.bar() }) {} -function c({ _ = __vite_ssr_import_0__.bar() + __vite_ssr_import_0__.foo() }) {}\n", +const a = ({ _ = (0, __vite_ssr_import_0__.foo)() }) => {}; +function b({ _ = (0, __vite_ssr_import_0__.bar)() }) {} +function c({ _ = (0, __vite_ssr_import_0__.bar)() + (0, __vite_ssr_import_0__.foo)() }) {}", ); } @@ -957,9 +1176,9 @@ function c({ _ = __vite_ssr_import_0__.bar() + __vite_ssr_import_0__.foo() }) {} }", "const __vite_ssr_import_0__ = await __vite_ssr_import__('foo', { importedNames: ['n'] }); const a = () => { -\tconst { type: n = 'bar' } = {}; -\tconsole.log(n); -};\n", + const { type: n = 'bar' } = {}; + console.log(n); +};", ); // https://github.com/vitejs/vite/issues/9585 @@ -970,11 +1189,11 @@ const a = () => { { const { [n]: m } = foo }", - "const __vite_ssr_import_0__ = await __vite_ssr_import__('foo', { importedNames: ['n', 'm'] }); + "const __vite_ssr_import_0__ = await __vite_ssr_import__('foo', { importedNames: ['n', 'm'] }); const foo = {}; { -\tconst { [__vite_ssr_import_0__.n]: m } = foo; -}\n", + const { [__vite_ssr_import_0__.n]: m } = foo; +}", ); } @@ -1007,28 +1226,29 @@ const foo = {}; objRest()", "const __vite_ssr_import_0__ = await __vite_ssr_import__('vue', { importedNames: ['remove', 'add', 'get', 'set', 'rest', 'objRest'] }); - function a() { - const { - o: { remove }, - a: { b: { c: [ add ] }}, - d: [{ get }, set, ...rest], - ...objRest - } = foo; - - remove(); - add(); - get(); - set(); - rest(); - objRest(); - } - - __vite_ssr_import_0__.remove(); - __vite_ssr_import_0__.add(); - __vite_ssr_import_0__.get(); - __vite_ssr_import_0__.set(); - __vite_ssr_import_0__.rest(); - __vite_ssr_import_0__.objRest();", +function a() { + const { + o: { remove }, + a: { b: { c: [ add ] }}, + d: [{ get }, set, ...rest], + ...objRest + } = foo; + + remove(); + add(); + get(); + set(); + rest(); + objRest(); +} + +(0, __vite_ssr_import_0__.remove)(); +(0, __vite_ssr_import_0__.add)(); +(0, __vite_ssr_import_0__.get)(); +(0, __vite_ssr_import_0__.set)(); +(0, __vite_ssr_import_0__.rest)(); +(0, __vite_ssr_import_0__.objRest)(); +", ); } @@ -1050,17 +1270,17 @@ const foo = {}; }", "const __vite_ssr_import_0__ = await __vite_ssr_import__('foo', { importedNames: ['default'] }); - const bar = 'bar'; - - const obj = { - foo() {}, - [__vite_ssr_import_0__.default]() {}, - [bar]() {}, - foo: () => {}, - [__vite_ssr_import_0__.default]: () => {}, - [bar]: () => {}, - bar(foo) {} - };", +const bar = 'bar'; + +const obj = { + foo() {}, + [__vite_ssr_import_0__.default]() {}, + [bar]() {}, + foo: () => {}, + [__vite_ssr_import_0__.default]: () => {}, + [bar]: () => {}, + bar(foo) {} +};", ); } @@ -1076,12 +1296,12 @@ const foo = {}; }", "const __vite_ssr_import_0__ = await __vite_ssr_import__('vue', { importedNames: ['remove', 'add'] }); - // const add = __vite_ssr_import_0__.add; - // const remove = __vite_ssr_import_0__.remove; - class A { - remove = 1; - add = null; - }", +// const add = __vite_ssr_import_0__.add; +// const remove = __vite_ssr_import_0__.remove; +class A { + remove = 1; + add = null; +}", ); } @@ -1101,15 +1321,15 @@ const foo = {}; }", "const __vite_ssr_import_0__ = await __vite_ssr_import__('foo', { importedNames: ['default'] }); - const bar = 'bar'; +const bar = 'bar'; - class A { - foo() {} - [__vite_ssr_import_0__.default]() {} - [bar]() {} - #foo() {} - bar(foo) {} - }", +class A { + foo() {} + [__vite_ssr_import_0__.default]() {} + [bar]() {} + #foo() {} + bar(foo) {} +}", ); } @@ -1138,28 +1358,29 @@ const foo = {}; aaa() bbb()", - r"const __vite_ssr_import_0__ = await __vite_ssr_import__('vue', { importedNames: ['aaa', 'bbb', 'ccc', 'ddd'] }); - - function foobar() { - ddd(); - - const aaa = () => { - bbb(ccc); - ddd(); - }; - const bbb = () => { - console.log('hi'); - }; - const ccc = 1; - function ddd() {} - - aaa(); - bbb(); - ccc(); - } + "const __vite_ssr_import_0__ = await __vite_ssr_import__('vue', { importedNames: ['aaa', 'bbb', 'ccc', 'ddd'] }); + +function foobar() { + ddd(); + + const aaa = () => { + bbb(ccc); + ddd(); + }; + const bbb = () => { + console.log('hi'); + }; + const ccc = 1; + function ddd() {} + + aaa(); + bbb(); + ccc(); +} - __vite_ssr_import_0__.aaa(); - __vite_ssr_import_0__.bbb();", +(0, __vite_ssr_import_0__.aaa)(); +(0, __vite_ssr_import_0__.bbb)(); +", ); } @@ -1177,10 +1398,10 @@ const foo = {}; ) }", "const __vite_ssr_import_0__ = await __vite_ssr_import__('react', { importedNames: ['default'] }); - const __vite_ssr_import_1__ = await __vite_ssr_import__('foo', { importedNames: ['Foo', 'Slot'] }); - function Bar({ Slot = __vite_ssr_import_0__.default.createElement(__vite_ssr_import_1__.Foo, null) }) { - \treturn __vite_ssr_import_0__.default.createElement(__vite_ssr_import_0__.default.Fragment, null, __vite_ssr_import_0__.default.createElement(Slot, null)); - }\n", +const __vite_ssr_import_1__ = await __vite_ssr_import__('foo', { importedNames: ['Foo', 'Slot'] }); +function Bar({ Slot = __vite_ssr_import_0__.default.createElement(__vite_ssr_import_1__.Foo, null) }) { + return __vite_ssr_import_0__.default.createElement(__vite_ssr_import_0__.default.Fragment, null, __vite_ssr_import_0__.default.createElement(Slot, null)); +}", ); } @@ -1191,22 +1412,23 @@ const foo = {}; }export function fn2() { }", "function fn1() { - } - Object.defineProperty(__vite_ssr_exports__, 'fn1', { - \tenumerable: true, - \tconfigurable: true, - \tget() { - \t\treturn fn1; - \t} - });\nfunction fn2() { - } - Object.defineProperty(__vite_ssr_exports__, 'fn2', { - \tenumerable: true, - \tconfigurable: true, - \tget() { - \t\treturn fn2; - \t} - });\n", +} +Object.defineProperty(__vite_ssr_exports__, 'fn1', { + enumerable: true, + configurable: true, + get() { + return fn1; + } +}); +function fn2() { +} +Object.defineProperty(__vite_ssr_exports__, 'fn2', { + enumerable: true, + configurable: true, + get() { + return fn2; + } +});", ); } @@ -1222,25 +1444,25 @@ const foo = {}; return Math.random(); });", "Object.defineProperty(__vite_ssr_exports__, 'default', { - \tenumerable: true, - \tconfigurable: true, - \tget() { - \t\treturn function getRandom() { - \t\t\treturn Math.random(); - \t\t}; - \t} - });\n", + enumerable: true, + configurable: true, + get() { + return function getRandom() { + return Math.random(); + }; + } +});", ); test_same( "export default (class A {});", "Object.defineProperty(__vite_ssr_exports__, 'default', { - \tenumerable: true, - \tconfigurable: true, - \tget() { - \t\treturn class A {}; - \t} - });\n", + enumerable: true, + configurable: true, + get() { + return class A {}; + } +});", ); } @@ -1251,7 +1473,7 @@ const foo = {}; "#!/usr/bin/env node console.log(\"it can parse the hashbang\")", "#!/usr/bin/env node - console.log(\"it can parse the hashbang\");\n", +console.log(\"it can parse the hashbang\");", ); } @@ -1260,9 +1482,11 @@ const foo = {}; test_same( "#!/usr/bin/env node console.log(foo); - import foo from \"foo\"", + import foo from 'foo'", "#!/usr/bin/env node - const __vite_ssr_import_0__ = await __vite_ssr_import__('foo', { importedNames: ['default'] });\nconsole.log(__vite_ssr_import_0__.default);\n", + const __vite_ssr_import_0__ = await __vite_ssr_import__('foo', { importedNames: ['default'] }); + console.log(__vite_ssr_import_0__.default); + ", ); } @@ -1270,10 +1494,12 @@ const foo = {}; fn identity_function_helper_injected_after_hashbang() { test_same( "#!/usr/bin/env node - import { foo } from \"foo\" - foo()", + import { foo } from 'foo'; + foo();", "#!/usr/bin/env node - const __vite_ssr_import_0__ = await __vite_ssr_import__('foo', { importedNames: ['foo'] });\n__vite_ssr_import_0__.foo();\n", + const __vite_ssr_import_0__ = await __vite_ssr_import__('foo', { importedNames: ['foo'] }); + (0, __vite_ssr_import_0__.foo)(); + ", ); } @@ -1306,7 +1532,38 @@ const foo = {}; } } }", - "const __vite_ssr_import_0__ = await __vite_ssr_import__('foobar', { importedNames: ['foo', 'bar'] });\nif (false) {\n\tconst foo = 'foo';\n\tconsole.log(foo);\n} else if (false) {\n\tconst [bar] = ['bar'];\n\tconsole.log(bar);\n} else {\n\tconsole.log(__vite_ssr_import_0__.foo);\n\tconsole.log(__vite_ssr_import_0__.bar);\n}\nclass Test {\n\tconstructor() {\n\t\tif (false) {\n\t\t\tconst foo = 'foo';\n\t\t\tconsole.log(foo);\n\t\t} else if (false) {\n\t\t\tconst [bar] = ['bar'];\n\t\t\tconsole.log(bar);\n\t\t} else {\n\t\t\tconsole.log(__vite_ssr_import_0__.foo);\n\t\t\tconsole.log(__vite_ssr_import_0__.bar);\n\t\t}\n\t}\n}\nObject.defineProperty(__vite_ssr_exports__, 'Test', {\n\tenumerable: true,\n\tconfigurable: true,\n\tget() {\n\t\treturn Test;\n\t}\n});\n", + "const __vite_ssr_import_0__ = await __vite_ssr_import__('foobar', { importedNames: ['foo', 'bar'] }); +if (false) { + const foo = 'foo'; + console.log(foo); +} else if (false) { + const [bar] = ['bar']; + console.log(bar); +} else { + console.log(__vite_ssr_import_0__.foo); + console.log(__vite_ssr_import_0__.bar); +} +class Test { + constructor() { + if (false) { + const foo = 'foo'; + console.log(foo); + } else if (false) { + const [bar] = ['bar']; + console.log(bar); + } else { + console.log(__vite_ssr_import_0__.foo); + console.log(__vite_ssr_import_0__.bar); + } + } +} +Object.defineProperty(__vite_ssr_exports__, 'Test', { + enumerable: true, + configurable: true, + get() { + return Test; + } +});", ); } @@ -1321,7 +1578,13 @@ const foo = {}; } return [foo, bar] }", - "const __vite_ssr_import_0__ = await __vite_ssr_import__('foobar', { importedNames: ['foo', 'bar'] });\nfunction test() {\n\tif (true) {\n\t\tvar foo = () => { var why = 'would'; }, bar = 'someone';\n\t}\n\treturn [foo, bar];\n}\n", + "const __vite_ssr_import_0__ = await __vite_ssr_import__('foobar', { importedNames: ['foo', 'bar'] }); +function test() { + if (true) { + var foo = () => { var why = 'would'; }, bar = 'someone'; + } + return [foo, bar]; +}", ); } @@ -1339,7 +1602,16 @@ const foo = {}; try {} catch (baz){ baz } return bar; }", - "const __vite_ssr_import_0__ = await __vite_ssr_import__('foobar', { importedNames: ['foo', 'bar', 'baz'] });\nfunction test() {\n\t[__vite_ssr_import_0__.foo];\n\t{\n\t\tlet foo = 10;\n\t\tlet bar = 10;\n\t}\n\ttry {} catch (baz) { baz; }\n\treturn __vite_ssr_import_0__.bar;\n}\n", + "const __vite_ssr_import_0__ = await __vite_ssr_import__('foobar', { importedNames: ['foo', 'bar', 'baz'] }); +function test() { + [__vite_ssr_import_0__.foo]; + { + let foo = 10; + let bar = 10; + } + try {} catch (baz) { baz; } + return __vite_ssr_import_0__.bar; +}", ); } @@ -1359,7 +1631,16 @@ const foo = {}; for (const test in tests) { console.log(test) }", - "const __vite_ssr_import_0__ = await __vite_ssr_import__('./test.js', { importedNames: ['test'] });\nfor (const test of tests) {\n\tconsole.log(test);\n}\nfor (let test = 0; test < 10; test++) {\n\tconsole.log(test);\n}\nfor (const test in tests) {\n\tconsole.log(test);\n}\n", + "const __vite_ssr_import_0__ = await __vite_ssr_import__('./test.js', { importedNames: ['test'] }); +for (const test of tests) { + console.log(test); +} +for (let test = 0; test < 10; test++) { + console.log(test); +} +for (const test in tests) { + console.log(test); +}", ); } @@ -1374,7 +1655,13 @@ const foo = {}; bar: class Bar {} } const Baz = class extends Foo {}", - "const __vite_ssr_import_0__ = await __vite_ssr_import__('./foo', { importedNames: ['default', 'Bar'] });\nconsole.log(__vite_ssr_import_0__.default, __vite_ssr_import_0__.Bar);\nconst obj = {\n\tfoo: class Foo {},\n\tbar: class Bar {}\n};\nconst Baz = class extends __vite_ssr_import_0__.default {};\n", + "const __vite_ssr_import_0__ = await __vite_ssr_import__('./foo', { importedNames: ['default', 'Bar'] }); +console.log(__vite_ssr_import_0__.default, __vite_ssr_import_0__.Bar); +const obj = { + foo: class Foo {}, + bar: class Bar {} +}; +const Baz = class extends __vite_ssr_import_0__.default {};", ); } @@ -1383,7 +1670,8 @@ const foo = {}; test_same( "import * as foo from './foo.json' with { type: 'json' }; import('./bar.json', { with: { type: 'json' } });", - "const __vite_ssr_import_0__ = await __vite_ssr_import__('./foo.json');\n__vite_ssr_dynamic_import__('./bar.json', { with: { type: 'json' } });\n", + "const __vite_ssr_import_0__ = await __vite_ssr_import__('./foo.json'); +__vite_ssr_dynamic_import__('./bar.json', { with: { type: 'json' } });", ); } @@ -1398,13 +1686,11 @@ const foo = {}; import { foo } from './foo' export * from './b' console.log(foo + 2)", - " - 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__); - console.log(__vite_ssr_import_1__.foo + 2) - ", + "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__); +console.log(__vite_ssr_import_1__.foo + 2);", ); } @@ -1415,7 +1701,23 @@ const foo = {}; export default foo() export * as bar from './bar' console.log(bar)", - "const __vite_ssr_import_0__ = await __vite_ssr_import__('./foo', { importedNames: ['foo'] });\nObject.defineProperty(__vite_ssr_exports__, 'default', {\n\tenumerable: true,\n\tconfigurable: true,\n\tget() {\n\t\treturn __vite_ssr_import_0__.foo();\n\t}\n});\nconst __vite_ssr_import_1__ = await __vite_ssr_import__('./bar');\nObject.defineProperty(__vite_ssr_exports__, 'bar', {\n\tenumerable: true,\n\tconfigurable: true,\n\tget() {\n\t\treturn __vite_ssr_import_1__;\n\t}\n});\nconsole.log(bar);\n", + "const __vite_ssr_import_0__ = await __vite_ssr_import__('./foo', { importedNames: ['foo'] }); +Object.defineProperty(__vite_ssr_exports__, 'default', { + enumerable: true, + configurable: true, + get() { + return (0, __vite_ssr_import_0__.foo)(); + } +}); +const __vite_ssr_import_1__ = await __vite_ssr_import__('./bar'); +Object.defineProperty(__vite_ssr_exports__, 'bar', { + enumerable: true, + configurable: true, + get() { + return __vite_ssr_import_1__; + } +}); +console.log(bar);", ); } @@ -1570,7 +1872,13 @@ const foo = {}; const c = () => { if(true){return}O(1,{}) }", - "const __vite_ssr_import_0__ = await __vite_ssr_import__('a', { importedNames: ['default'] });\nconst c = () => {\n\tif (true) {\n\t\treturn;\n\t}\n\t__vite_ssr_import_0__.default(1, {});\n};\n", + "const __vite_ssr_import_0__ = await __vite_ssr_import__('a', { importedNames: ['default'] }); +const c = () => { + if (true) { + return; + } + (0, __vite_ssr_import_0__.default)(1, {}); +};", ); } } From f8984b033868b88f99def4bb166984b812147e36 Mon Sep 17 00:00:00 2001 From: Dunqing Date: Thu, 27 Feb 2025 18:24:05 +0800 Subject: [PATCH 15/30] Polish and add comments --- .../src/vite/module_runner_transform.rs | 270 +++++++++++++----- 1 file changed, 198 insertions(+), 72 deletions(-) diff --git a/crates/oxc_transformer/src/vite/module_runner_transform.rs b/crates/oxc_transformer/src/vite/module_runner_transform.rs index 93765149ac796..7b20bb875e7f2 100644 --- a/crates/oxc_transformer/src/vite/module_runner_transform.rs +++ b/crates/oxc_transformer/src/vite/module_runner_transform.rs @@ -29,7 +29,11 @@ use crate::utils::ast_builder::{ }; pub struct ModuleRunnerTransform<'a> { + /// Uid for generating import binding names. import_uid: u32, + /// Import bindings used to determine which identifiers should be transformed. + /// The key is a symbol id that belongs to the import binding. + /// The value is a tuple of (Binding, Property). import_bindings: FxHashMap, Option>)>, } @@ -44,6 +48,7 @@ const SSR_IMPORT_KEY: Atom<'static> = Atom::new_const("__vite_ssr_import__"); const SSR_DYNAMIC_IMPORT_KEY: Atom<'static> = Atom::new_const("__vite_ssr_dynamic_import__"); const SSR_EXPORT_ALL_KEY: Atom<'static> = Atom::new_const("__vite_ssr_exportAll__"); const SSR_IMPORT_META_KEY: Atom<'static> = Atom::new_const("__vite_ssr_import_meta__"); +const DEFAULT: Atom<'static> = Atom::new_const("default"); impl<'a> Traverse<'a> for ModuleRunnerTransform<'a> { #[inline] @@ -56,13 +61,14 @@ impl<'a> Traverse<'a> for ModuleRunnerTransform<'a> { match expr { Expression::Identifier(_) => self.transform_identifier(expr, ctx), Expression::MetaProperty(_) => self.transform_meta_property(expr, ctx), - Expression::ImportExpression(_) => self.transform_import_expression(expr, ctx), + Expression::ImportExpression(_) => self.transform_dynamic_import(expr, ctx), _ => {} } } } impl<'a> ModuleRunnerTransform<'a> { + /// Transform import and export declarations. fn transform_imports_and_exports( &mut self, program: &mut Program<'a>, @@ -72,10 +78,10 @@ impl<'a> ModuleRunnerTransform<'a> { if !should_transform { return; } + // Reserve enough space for new statements let mut new_stmts: ArenaVec<'a, Statement<'a>> = ctx.ast.vec_with_capacity(program.body.len() * 2); - let mut hoist_index = None; for stmt in program.body.drain(..) { @@ -83,7 +89,7 @@ impl<'a> ModuleRunnerTransform<'a> { Statement::ImportDeclaration(import) => { let ImportDeclaration { span, source, specifiers, .. } = import.unbox(); let import_statement = self.transform_import(span, source, specifiers, ctx); - // Need to hoist import statements to the above of the other statements + // Needs to hoist import statements to the above of the other statements if let Some(index) = hoist_index { new_stmts.insert(index, import_statement); hoist_index.replace(index + 1); @@ -92,21 +98,22 @@ impl<'a> ModuleRunnerTransform<'a> { } continue; } - Statement::ExportNamedDeclaration(export) => { - self.transform_export_named_declaration(&mut new_stmts, export, ctx); - } Statement::ExportAllDeclaration(export) => { self.transform_export_all_declaration(&mut new_stmts, export, ctx); continue; } + Statement::ExportNamedDeclaration(export) => { + self.transform_export_named_declaration(&mut new_stmts, export, ctx); + } Statement::ExportDefaultDeclaration(export) => { self.transform_export_default_declaration(&mut new_stmts, export, ctx); - continue; } _ => { new_stmts.push(stmt); } } + + // Found that non-import statements, we can start hoisting import statements after this if hoist_index.is_none() { hoist_index.replace(new_stmts.len() - 1); } @@ -115,48 +122,55 @@ impl<'a> ModuleRunnerTransform<'a> { program.body = new_stmts; } - #[inline] - fn transform_import_expression( - &mut self, - expr: &mut Expression<'a>, - ctx: &mut TraverseCtx<'a>, - ) { - let Expression::ImportExpression(import_expr) = ctx.ast.move_expression(expr) else { - unreachable!(); - }; - - let ImportExpression { span, source, arguments, .. } = import_expr.unbox(); - let flags = ReferenceFlags::Read; - let callee = ctx.create_unbound_ident_expr(SPAN, SSR_DYNAMIC_IMPORT_KEY, flags); - let arguments = arguments.into_iter().map(Argument::from); - let arguments = ctx.ast.vec_from_iter(iter::once(Argument::from(source)).chain(arguments)); - *expr = ctx.ast.expression_call(span, callee, NONE, arguments, false); - } - - #[inline] + /// Transform `identifier` to point correctly imported binding. + /// + /// - Import without renaming + /// ```js + /// import { foo } from 'vue'; + /// foo; + /// // to + /// __vite_ssr_import_0__.foo; + /// ``` + /// + /// - Import with renaming + /// ```js + /// import { "arbitrary string" as bar } from 'vue'; + /// bar; + /// // to + /// __vite_ssr_import_0__["arbitrary string"]; + /// ``` + /// + /// - The identifier is a callee of a call expression + /// ```js + /// import { foo } from 'vue'; + /// foo(); + /// // to + /// (0, __vite_ssr_import_0__.foo)(); + /// ``` fn transform_identifier(&self, expr: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>) { let Expression::Identifier(ident) = expr else { unreachable!(); }; - let Some(reference_id) = ident.reference_id.get() else { - return; - }; - let Some(symbol_id) = ctx.symbols().get_reference(reference_id).symbol_id() else { + let Some((binding, property)) = ident + .reference_id + .get() + .and_then(|id| ctx.symbols().get_reference(id).symbol_id()) + .and_then(|id| self.import_bindings.get(&id)) + else { return; }; - if let Some((binding, property)) = self.import_bindings.get(&symbol_id) { - let object = binding.create_read_expression(ctx); - let object = if let Some(property) = property { - // TODO(improvement): It looks like here could always return a computed member expression, - // so that we don't need to check if it's an identifier name. - if is_identifier_name(property) { - create_property_access(ident.span, object, property, ctx) - } else { - create_compute_property_access(ident.span, object, property, ctx) - } + + let object = binding.create_read_expression(ctx); + *expr = if let Some(property) = property { + // TODO(improvement): It looks like here could always return a computed member expression, + // so that we don't need to check if it's an identifier name. + // __vite_ssr_import_0__.foo + let expr = if is_identifier_name(property) { + create_property_access(ident.span, object, property, ctx) } else { - object + // __vite_ssr_import_0__['arbitrary string'] + create_compute_property_access(ident.span, object, property, ctx) }; if matches!(ctx.parent(), Ancestor::CallExpressionCallee(_)) { @@ -164,14 +178,32 @@ impl<'a> ModuleRunnerTransform<'a> { // let zero = ctx.ast.expression_numeric_literal(SPAN, 0f64, None, NumberBase::Decimal); - let expressions = ctx.ast.vec_from_array([zero, object]); - *expr = ctx.ast.expression_sequence(ident.span, expressions); + let expressions = ctx.ast.vec_from_array([zero, expr]); + ctx.ast.expression_sequence(ident.span, expressions) } else { - *expr = object; + expr } - } + } else { + object + }; + } + + /// Transform `import(source, ...arguments)` to `__vite_ssr_dynamic_import__(source, ...arguments)`. + #[inline] + fn transform_dynamic_import(&mut self, expr: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>) { + let Expression::ImportExpression(import_expr) = ctx.ast.move_expression(expr) else { + unreachable!(); + }; + + let ImportExpression { span, source, arguments, .. } = import_expr.unbox(); + let flags = ReferenceFlags::Read; + let callee = ctx.create_unbound_ident_expr(SPAN, SSR_DYNAMIC_IMPORT_KEY, flags); + let arguments = arguments.into_iter().map(Argument::from); + let arguments = ctx.ast.vec_from_iter(iter::once(Argument::from(source)).chain(arguments)); + *expr = ctx.ast.expression_call(span, callee, NONE, arguments, false); } + /// Transform `import.meta` to `__vite_ssr_import_meta__`. #[inline] fn transform_meta_property(&mut self, expr: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>) { let Expression::MetaProperty(meta) = expr else { @@ -181,6 +213,28 @@ impl<'a> ModuleRunnerTransform<'a> { *expr = ctx.create_unbound_ident_expr(meta.span, SSR_IMPORT_META_KEY, ReferenceFlags::Read); } + /// Transform import declaration (`import { foo } from 'vue'`). + /// + /// - Import specifier + /// ```js + /// import { foo, bar } from 'vue'; + /// // to + /// const __vite_ssr_import_0__ = await __vite_ssr_import__('vue', { importedNames: ['foo', 'bar'] }); + /// ``` + /// + /// - Import default specifier + /// ```js + /// import vue from 'vue'; + /// // to + /// const __vite_ssr_import_0__ = await __vite_ssr_import__('vue', { importedNames: ['default'] }); + /// ``` + /// + /// - Import namespace specifier + /// ```js + /// import * as vue from 'vue'; + /// // to + /// const __vite_ssr_import_0__ = await __vite_ssr_import__('vue'); + /// ``` fn transform_import( &mut self, span: Span, @@ -210,11 +264,11 @@ impl<'a> ModuleRunnerTransform<'a> { ctx.symbols_mut().set_name(binding.symbol_id, &binding.name); self.import_bindings.insert(binding.symbol_id, (binding, None)); - let binding_pattern_kind = BindingPatternKind::BindingIdentifier(ctx.alloc(local)); - ctx.ast.binding_pattern(binding_pattern_kind, NONE, false) + let kind = BindingPatternKind::BindingIdentifier(ctx.alloc(local)); + ctx.ast.binding_pattern(kind, NONE, false) } else { let binding = self.generate_import_binding(ctx); - arguments.push(self.transform_import_metadata(&binding, specifiers, ctx)); + arguments.push(self.transform_import_specifiers(&binding, specifiers, ctx)); binding.create_binding_pattern(ctx) } } else { @@ -225,6 +279,39 @@ impl<'a> ModuleRunnerTransform<'a> { Self::create_import(span, pattern, arguments, ctx) } + /// Transform named export declaration (`export function foo() {}`). + /// + /// - Export a declaration + /// ```js + /// export function foo() {} + /// // to + /// function foo() {} + /// Object.defineProperty(__vite_ssr_exports__, 'foo', { enumerable: true, configurable: true, get() { return foo; }}); + /// ``` + /// + /// - Export specifiers + /// ```js + /// export { foo, bar }; + /// // to + /// Object.defineProperty(__vite_ssr_exports__, 'foo', { enumerable: true, configurable: true, get() { return foo; }}); + /// Object.defineProperty(__vite_ssr_exports__, 'bar', { enumerable: true, configurable: true, get() { return bar; }}); + /// ``` + /// + /// - Export specifiers from module + /// ```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; }}); + /// ``` + /// + /// - Export specifiers with renaming + /// ```js + /// export { foo as bar }; + /// // to + /// Object.defineProperty(__vite_ssr_exports__, 'bar', { enumerable: true, configurable: true, get() { return foo; }}); + /// ``` fn transform_export_named_declaration( &mut self, new_stmts: &mut ArenaVec<'a, Statement<'a>>, @@ -235,6 +322,7 @@ impl<'a> ModuleRunnerTransform<'a> { if let Some(declaration) = declaration { let export_expression = match &declaration { + // `export const [foo, bar] = [1, 2];` Declaration::VariableDeclaration(variable) => { let new_stmts_index = new_stmts.len(); variable.bound_names(&mut |ident| { @@ -246,11 +334,13 @@ impl<'a> ModuleRunnerTransform<'a> { new_stmts.insert(new_stmts_index, Statement::from(declaration)); return; } + // `export function foo() {}` Declaration::FunctionDeclaration(func) => { let binding = BoundIdentifier::from_binding_ident(func.id.as_ref().unwrap()); let ident = binding.create_read_expression(ctx); Self::create_export(span, ident, binding.name, ctx) } + // `export class Foo {}` Declaration::ClassDeclaration(class) => { let binding = BoundIdentifier::from_binding_ident(class.id.as_ref().unwrap()); let ident = binding.create_read_expression(ctx); @@ -264,13 +354,7 @@ impl<'a> ModuleRunnerTransform<'a> { }; new_stmts.extend([Statement::from(declaration), export_expression]); } else { - // ```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 } }); - // ``` + // If the source is Some, then we need to import the module first and then export them. let import_binding = source.map(|source| { let binding = self.generate_import_binding(ctx); let pattern = binding.create_binding_pattern(ctx); @@ -310,6 +394,23 @@ impl<'a> ModuleRunnerTransform<'a> { } } + /// Transform export all declaration (`export * from 'vue'`). + /// + /// - Without renamed export: + /// ```js + /// export * from 'vue'; + /// // to + /// const __vite_ssr_import_0__ = await __vite_ssr_import__('vue'); + /// Object.defineProperty(__vite_ssr_exports__, 'default', { enumerable: true, configurable: true, get(){ return __vite_ssr_import_0__ } }); + /// ``` + /// + /// - Renamed export: + /// ```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__ } }); + /// ``` fn transform_export_all_declaration( &mut self, new_stmts: &mut ArenaVec<'a, Statement<'a>>, @@ -321,7 +422,7 @@ impl<'a> ModuleRunnerTransform<'a> { let pattern = binding.create_binding_pattern(ctx); let arguments = ctx.ast.vec1(Argument::from(Expression::StringLiteral(ctx.ast.alloc(source)))); - new_stmts.push(Self::create_import(span, pattern, arguments, ctx)); + let import = Self::create_import(span, pattern, arguments, ctx); let ident = binding.create_read_expression(ctx); @@ -336,59 +437,82 @@ impl<'a> ModuleRunnerTransform<'a> { let call = ctx.ast.expression_call(SPAN, callee, NONE, arguments, false); ctx.ast.statement_expression(span, call) }; - new_stmts.push(export); - } - + new_stmts.extend([import, export]); + } + + /// Transform export default declaration (`export default function foo() {}`). + /// + /// - Named function declaration + /// ```js + /// export default function foo() {} + /// // to + /// function foo() {} + /// Object.defineProperty(__vite_ssr_exports__, 'default', { enumerable: true, configurable: true, get(){ return foo } }); + /// ``` + /// + /// - Named class declaration + /// ```js + /// export default class Foo {} + /// // to + /// class Foo {} + /// Object.defineProperty(__vite_ssr_exports__, 'default', { enumerable: true, configurable: true, get(){ return Foo } }); + /// ``` + /// + /// - Without named declaration and expression + /// ```js + /// export default function () {} + /// export default {} + /// // to + /// Object.defineProperty(__vite_ssr_exports__, 'default', { enumerable: true, configurable: true, get(){ return expr } }); + /// Object.defineProperty(__vite_ssr_exports__, 'default', { enumerable: true, configurable: true, get(){ return {} } }); + /// ``` fn transform_export_default_declaration( &self, new_stmts: &mut ArenaVec<'a, Statement<'a>>, export: ArenaBox<'a, ExportDefaultDeclaration<'a>>, ctx: &mut TraverseCtx<'a>, ) { - let default = Atom::new_const("default"); let ExportDefaultDeclaration { span, declaration, .. } = export.unbox(); let expr = match declaration { ExportDefaultDeclarationKind::FunctionDeclaration(mut func) => { - // `export default function foo() {}` -> - // `function foo() {}; Object.defineProperty(__vite_ssr_exports__, 'default', { enumerable: true, configurable: true, get(){ return foo } });` if let Some(id) = &func.id { let ident = BoundIdentifier::from_binding_ident(id).create_read_expression(ctx); new_stmts.extend([ Statement::FunctionDeclaration(func), - Self::create_export(span, ident, default, ctx), + Self::create_export(span, ident, DEFAULT, ctx), ]); return; } - + // Without name, treat it as an expression func.r#type = FunctionType::FunctionExpression; Expression::FunctionExpression(func) } ExportDefaultDeclarationKind::ClassDeclaration(mut class) => { - // `export default class Foo {}` -> - // `class Foo {}; Object.defineProperty(__vite_ssr_exports__, 'default', { enumerable: true, configurable: true, get(){ return Foo } });` if let Some(id) = &class.id { let ident = BoundIdentifier::from_binding_ident(id).create_read_expression(ctx); new_stmts.extend([ Statement::ClassDeclaration(class), - Self::create_export(span, ident, default, ctx), + Self::create_export(span, ident, DEFAULT, ctx), ]); return; } + // Without name, treat it as an expression class.r#type = ClassType::ClassExpression; Expression::ClassExpression(class) } ExportDefaultDeclarationKind::TSInterfaceDeclaration(_) => { - // Do nothing for `interface Foo {}` + // Do nothing for `export default interface Foo {}` return; } expr @ match_expression!(ExportDefaultDeclarationKind) => expr.into_expression(), }; - // `export default expr` -> `Object.defineProperty(__vite_ssr_exports__, 'default', { enumerable: true, configurable: true, get(){ return expr } });` - new_stmts.push(Self::create_export(span, expr, default, ctx)); + + new_stmts.push(Self::create_export(span, expr, DEFAULT, ctx)); } - fn transform_import_metadata( + /// Transform import specifiers, and return an imported names object. + fn transform_import_specifiers( &mut self, binding: &BoundIdentifier<'a>, specifiers: ArenaVec<'a, ImportDeclarationSpecifier<'a>>, @@ -402,7 +526,7 @@ impl<'a> ModuleRunnerTransform<'a> { } ImportDeclarationSpecifier::ImportDefaultSpecifier(specifier) => { let ImportDefaultSpecifier { span, local } = specifier.unbox(); - self.insert_import_binding(span, binding, local, Atom::from("default"), ctx) + self.insert_import_binding(span, binding, local, DEFAULT, ctx) } ImportDeclarationSpecifier::ImportNamespaceSpecifier(_) => { unreachable!() @@ -427,6 +551,7 @@ impl<'a> ModuleRunnerTransform<'a> { scopes.remove_binding(scopes.root_scope_id(), &name); let symbol_id = symbol_id.get().unwrap(); + // Do not need to insert if there no identifiers that point to this symbol if !ctx.symbols().get_resolved_reference_ids(symbol_id).is_empty() { self.import_bindings.insert(symbol_id, (binding.clone(), Some(key))); } @@ -454,6 +579,7 @@ impl<'a> ModuleRunnerTransform<'a> { string.push_str("__vite_ssr_import_"); string.push_str(&uid); string.push_str("__"); + debug_assert_eq!(string.len(), capacity); Atom::from(string) } From c5ea4727e2463db31bfdacb6ff8ca75bc9522088 Mon Sep 17 00:00:00 2001 From: Dunqing Date: Thu, 27 Feb 2025 18:25:05 +0800 Subject: [PATCH 16/30] Fix clippy --- .../src/vite/module_runner_transform.rs | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/crates/oxc_transformer/src/vite/module_runner_transform.rs b/crates/oxc_transformer/src/vite/module_runner_transform.rs index 7b20bb875e7f2..747025aeac6e3 100644 --- a/crates/oxc_transformer/src/vite/module_runner_transform.rs +++ b/crates/oxc_transformer/src/vite/module_runner_transform.rs @@ -60,8 +60,8 @@ impl<'a> Traverse<'a> for ModuleRunnerTransform<'a> { fn enter_expression(&mut self, expr: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>) { match expr { Expression::Identifier(_) => self.transform_identifier(expr, ctx), - Expression::MetaProperty(_) => self.transform_meta_property(expr, ctx), - Expression::ImportExpression(_) => self.transform_dynamic_import(expr, ctx), + Expression::MetaProperty(_) => Self::transform_meta_property(expr, ctx), + Expression::ImportExpression(_) => Self::transform_dynamic_import(expr, ctx), _ => {} } } @@ -106,7 +106,7 @@ impl<'a> ModuleRunnerTransform<'a> { self.transform_export_named_declaration(&mut new_stmts, export, ctx); } Statement::ExportDefaultDeclaration(export) => { - self.transform_export_default_declaration(&mut new_stmts, export, ctx); + Self::transform_export_default_declaration(&mut new_stmts, export, ctx); } _ => { new_stmts.push(stmt); @@ -190,7 +190,7 @@ impl<'a> ModuleRunnerTransform<'a> { /// Transform `import(source, ...arguments)` to `__vite_ssr_dynamic_import__(source, ...arguments)`. #[inline] - fn transform_dynamic_import(&mut self, expr: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>) { + fn transform_dynamic_import(expr: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>) { let Expression::ImportExpression(import_expr) = ctx.ast.move_expression(expr) else { unreachable!(); }; @@ -205,7 +205,7 @@ impl<'a> ModuleRunnerTransform<'a> { /// Transform `import.meta` to `__vite_ssr_import_meta__`. #[inline] - fn transform_meta_property(&mut self, expr: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>) { + fn transform_meta_property(expr: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>) { let Expression::MetaProperty(meta) = expr else { unreachable!(); }; @@ -467,7 +467,6 @@ impl<'a> ModuleRunnerTransform<'a> { /// Object.defineProperty(__vite_ssr_exports__, 'default', { enumerable: true, configurable: true, get(){ return {} } }); /// ``` fn transform_export_default_declaration( - &self, new_stmts: &mut ArenaVec<'a, Statement<'a>>, export: ArenaBox<'a, ExportDefaultDeclaration<'a>>, ctx: &mut TraverseCtx<'a>, From 9f1c1b484980d036384b33367a14f025d3676868 Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Thu, 27 Feb 2025 10:26:18 +0000 Subject: [PATCH 17/30] [autofix.ci] apply automated fixes --- crates/oxc_transformer/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/oxc_transformer/Cargo.toml b/crates/oxc_transformer/Cargo.toml index 43d546ca51372..b8d970098329d 100644 --- a/crates/oxc_transformer/Cargo.toml +++ b/crates/oxc_transformer/Cargo.toml @@ -47,12 +47,12 @@ sha1 = { workspace = true } [dev-dependencies] insta = { workspace = true } -similar = { workspace = true } oxc_codegen = { workspace = true } oxc_parser = { workspace = true } oxc_sourcemap = { workspace = true } oxc_tasks_common = { workspace = true } pico-args = { workspace = true } +similar = { workspace = true } [features] default = [] From 3af4a8d4769f08ba1671a94fa01d1e585c151c41 Mon Sep 17 00:00:00 2001 From: Dunqing Date: Thu, 27 Feb 2025 19:00:08 +0800 Subject: [PATCH 18/30] Remove FIXME --- .../src/vite/module_runner_transform.rs | 25 ++++++++----------- 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/crates/oxc_transformer/src/vite/module_runner_transform.rs b/crates/oxc_transformer/src/vite/module_runner_transform.rs index 747025aeac6e3..e9c7f3aadc793 100644 --- a/crates/oxc_transformer/src/vite/module_runner_transform.rs +++ b/crates/oxc_transformer/src/vite/module_runner_transform.rs @@ -1409,24 +1409,21 @@ const obj = { ); } - // FIXME: The output has adjusted, need to confirm if it's correct #[test] fn class_props() { test_same( "import { remove, add } from 'vue' - - class A { - remove = 1 - add = null - }", - "const __vite_ssr_import_0__ = await __vite_ssr_import__('vue', { importedNames: ['remove', 'add'] }); - -// const add = __vite_ssr_import_0__.add; -// const remove = __vite_ssr_import_0__.remove; -class A { - remove = 1; - add = null; -}", + class A { + remove = 1 + add = null + }", + " + const __vite_ssr_import_0__ = await __vite_ssr_import__('vue', { importedNames: ['remove', 'add'] }); + class A { + remove = 1; + add = null; + } + ", ); } From c638e492afada2953832c9d3b6d1d43ee233ffd1 Mon Sep 17 00:00:00 2001 From: Dunqing Date: Thu, 27 Feb 2025 20:06:44 +0800 Subject: [PATCH 19/30] Fix unused lint --- crates/oxc_transformer/src/vite/module_runner_transform.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/oxc_transformer/src/vite/module_runner_transform.rs b/crates/oxc_transformer/src/vite/module_runner_transform.rs index e9c7f3aadc793..16e3b39e44ba4 100644 --- a/crates/oxc_transformer/src/vite/module_runner_transform.rs +++ b/crates/oxc_transformer/src/vite/module_runner_transform.rs @@ -38,6 +38,7 @@ pub struct ModuleRunnerTransform<'a> { } impl ModuleRunnerTransform<'_> { + #[expect(unused)] pub fn new() -> Self { Self { import_uid: 0, import_bindings: FxHashMap::default() } } From c8fbb8bc28871469ad92d59215a7b1257c46864c Mon Sep 17 00:00:00 2001 From: Dunqing Date: Thu, 27 Feb 2025 20:25:08 +0800 Subject: [PATCH 20/30] Add documentation at top of file --- .../src/vite/module_runner_transform.rs | 46 ++++++++++++++++--- 1 file changed, 39 insertions(+), 7 deletions(-) diff --git a/crates/oxc_transformer/src/vite/module_runner_transform.rs b/crates/oxc_transformer/src/vite/module_runner_transform.rs index 16e3b39e44ba4..f32d39ba4f849 100644 --- a/crates/oxc_transformer/src/vite/module_runner_transform.rs +++ b/crates/oxc_transformer/src/vite/module_runner_transform.rs @@ -1,17 +1,49 @@ /// Module runner transform /// +/// This plugin is used to transform import statement to import by `__vite_ssr_import__` +/// and export statement to export by `__vite_ssr_exports__`, these functions will be +/// injected by Vite node. +/// +/// ## Example +/// +/// Input: +/// ```js +/// import { foo } from 'vue'; +/// import vue from 'vue'; +/// import * as vue from 'vue'; +/// +/// foo(); +/// console.log(vue.version); +/// console.log(vue.zoo()); +/// ``` +/// +/// Output: +/// ```js +/// const __vite_ssr_import_0__ = await __vite_ssr_import__('vue', { importedNames: ['foo'] }); +/// const __vite_ssr_import_1__ = await __vite_ssr_import__('vue', { importedNames: ['default'] }); +/// const __vite_ssr_import_2__ = await __vite_ssr_import__('vue'); +/// (0, __vite_ssr_import_0__.foo)(); +/// console.log(__vite_ssr_import_2__.version); +/// console.log(__vite_ssr_import_2__.zoo()); +/// ``` +/// /// ## Implementation /// -/// Based on https://github.com/vitejs/vite/blob/00deea4ff88e30e299cb40a801b5dc0205ac913d/packages/vite/src/node/ssr/ssrTransform.ts +/// Based on [Vite](https://github.com/vitejs/vite/blob/00deea4ff88e30e299cb40a801b5dc0205ac913d/packages/vite/src/node/ssr/ssrTransform.ts)'s ssrTransform. +/// +/// All tests are copy-pasted from (ssrTransform.spec.ts)[https://github.com/vitejs/vite/blob/00deea4ff88e30e299cb40a801b5dc0205ac913d/packages/vite/src/node/ssr/__tests__/ssrTransform.spec.ts] /// +/// ## Integrate into main `Transformer` in future +/// +/// There are few problems to integrate this transform into the main transformer: /// -/// ## Complicated parts if we want to support this in main transformer: /// 1. In Vite, it will collect import deps and dynamic import deps during the transform process, and return them -/// at the end of function. We can do this, but how to return it? -/// 2. In case other plugins will insert imports/exports, we must transform them in exit_program, but it will pose -/// another problem: how to transform identifiers which refers to imports? We must collect some information from imports, -/// but it already at the end of the visitor. To solve this, we may introduce a new visitor to transform identifier, -/// dynamic import and meta property. +/// at the end of function. We can do this, but how to pass them into the js side? +/// +/// 2. In case other plugins will insert imports/exports, we must transform them in `exit_program`, but it will pose +/// another problem: how to transform identifiers which refer to imports? We must collect some information from imports, +/// but it is already at the end of the visitor. To solve this, we may introduce a new visitor to transform identifiers, +/// dynamic imports, and import meta. use compact_str::ToCompactString; use rustc_hash::FxHashMap; use std::iter; From ed98fd4a437b5b609ded27aea583f665257c6a67 Mon Sep 17 00:00:00 2001 From: Dunqing Date: Thu, 27 Feb 2025 20:32:11 +0800 Subject: [PATCH 21/30] Support collect `deps` and `dynamicDeps` --- .../module_runner_transform.rs | 23 +++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) rename crates/oxc_transformer/src/{vite => plugins}/module_runner_transform.rs (98%) diff --git a/crates/oxc_transformer/src/vite/module_runner_transform.rs b/crates/oxc_transformer/src/plugins/module_runner_transform.rs similarity index 98% rename from crates/oxc_transformer/src/vite/module_runner_transform.rs rename to crates/oxc_transformer/src/plugins/module_runner_transform.rs index f32d39ba4f849..327ba3e23b3ae 100644 --- a/crates/oxc_transformer/src/vite/module_runner_transform.rs +++ b/crates/oxc_transformer/src/plugins/module_runner_transform.rs @@ -67,12 +67,20 @@ pub struct ModuleRunnerTransform<'a> { /// The key is a symbol id that belongs to the import binding. /// The value is a tuple of (Binding, Property). import_bindings: FxHashMap, Option>)>, + + // Collect deps and dynamic deps for Vite + deps: Vec, + dynamic_deps: Vec, } impl ModuleRunnerTransform<'_> { - #[expect(unused)] pub fn new() -> Self { - Self { import_uid: 0, import_bindings: FxHashMap::default() } + Self { + import_uid: 0, + import_bindings: FxHashMap::default(), + deps: Vec::default(), + dynamic_deps: Vec::default(), + } } } @@ -94,7 +102,7 @@ impl<'a> Traverse<'a> for ModuleRunnerTransform<'a> { match expr { Expression::Identifier(_) => self.transform_identifier(expr, ctx), Expression::MetaProperty(_) => Self::transform_meta_property(expr, ctx), - Expression::ImportExpression(_) => Self::transform_dynamic_import(expr, ctx), + Expression::ImportExpression(_) => self.transform_dynamic_import(expr, ctx), _ => {} } } @@ -223,12 +231,17 @@ impl<'a> ModuleRunnerTransform<'a> { /// Transform `import(source, ...arguments)` to `__vite_ssr_dynamic_import__(source, ...arguments)`. #[inline] - fn transform_dynamic_import(expr: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>) { + fn transform_dynamic_import(&mut self, expr: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>) { let Expression::ImportExpression(import_expr) = ctx.ast.move_expression(expr) else { unreachable!(); }; let ImportExpression { span, source, arguments, .. } = import_expr.unbox(); + + if let Expression::StringLiteral(source) = &source { + self.dynamic_deps.push(source.value.to_string()); + } + let flags = ReferenceFlags::Read; let callee = ctx.create_unbound_ident_expr(SPAN, SSR_DYNAMIC_IMPORT_KEY, flags); let arguments = arguments.into_iter().map(Argument::from); @@ -275,6 +288,8 @@ impl<'a> ModuleRunnerTransform<'a> { specifiers: Option>>, ctx: &mut TraverseCtx<'a>, ) -> Statement<'a> { + self.deps.push(source.value.to_string()); + // ['vue', { importedNames: ['foo'] }]` let mut arguments = ctx.ast.vec_with_capacity(1 + usize::from(specifiers.is_some())); arguments.push(Argument::from(Expression::StringLiteral(ctx.ast.alloc(source)))); From 5e270f85dd108135697ce5457261620f46dd3575 Mon Sep 17 00:00:00 2001 From: Dunqing Date: Thu, 27 Feb 2025 20:32:34 +0800 Subject: [PATCH 22/30] move to plugins module --- crates/oxc_transformer/src/lib.rs | 1 - crates/oxc_transformer/src/plugins/mod.rs | 2 ++ crates/oxc_transformer/src/vite/mod.rs | 1 - 3 files changed, 2 insertions(+), 2 deletions(-) delete mode 100644 crates/oxc_transformer/src/vite/mod.rs diff --git a/crates/oxc_transformer/src/lib.rs b/crates/oxc_transformer/src/lib.rs index 6c9f804a45b23..0a0a4f3c565b6 100644 --- a/crates/oxc_transformer/src/lib.rs +++ b/crates/oxc_transformer/src/lib.rs @@ -36,7 +36,6 @@ mod typescript; mod decorator; mod plugins; -mod vite; use common::Common; use context::TransformCtx; diff --git a/crates/oxc_transformer/src/plugins/mod.rs b/crates/oxc_transformer/src/plugins/mod.rs index fdf6d786e022c..d47d1e1e9108e 100644 --- a/crates/oxc_transformer/src/plugins/mod.rs +++ b/crates/oxc_transformer/src/plugins/mod.rs @@ -1,5 +1,7 @@ mod inject_global_variables; +mod module_runner_transform; mod replace_global_defines; pub use inject_global_variables::*; +pub use module_runner_transform::*; pub use replace_global_defines::*; diff --git a/crates/oxc_transformer/src/vite/mod.rs b/crates/oxc_transformer/src/vite/mod.rs deleted file mode 100644 index ae3ae6c38beaf..0000000000000 --- a/crates/oxc_transformer/src/vite/mod.rs +++ /dev/null @@ -1 +0,0 @@ -pub mod module_runner_transform; From a18bb3ed2cc7f4c769c96ed38ffaac299534f873 Mon Sep 17 00:00:00 2001 From: Dunqing Date: Thu, 27 Feb 2025 20:33:44 +0800 Subject: [PATCH 23/30] derive Debug and Default --- crates/oxc_transformer/src/plugins/module_runner_transform.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/crates/oxc_transformer/src/plugins/module_runner_transform.rs b/crates/oxc_transformer/src/plugins/module_runner_transform.rs index 327ba3e23b3ae..6b2b84cb0ca66 100644 --- a/crates/oxc_transformer/src/plugins/module_runner_transform.rs +++ b/crates/oxc_transformer/src/plugins/module_runner_transform.rs @@ -60,6 +60,7 @@ use crate::utils::ast_builder::{ create_compute_property_access, create_member_callee, create_property_access, }; +#[derive(Debug, Default)] pub struct ModuleRunnerTransform<'a> { /// Uid for generating import binding names. import_uid: u32, @@ -798,7 +799,7 @@ mod test { (symbols, scopes) = traverse_mut(&mut jsx, &allocator, &mut program, symbols, scopes); } - let mut module_runner_transform = ModuleRunnerTransform::new(); + let mut module_runner_transform = ModuleRunnerTransform::default(); traverse_mut(&mut module_runner_transform, &allocator, &mut program, symbols, scopes); if !ret.errors.is_empty() { From 39a07ae799b20d245cb0c92a14021c8264058271 Mon Sep 17 00:00:00 2001 From: Dunqing Date: Fri, 28 Feb 2025 13:31:17 +0800 Subject: [PATCH 24/30] Optimize imports hoisting --- .../src/plugins/module_runner_transform.rs | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/crates/oxc_transformer/src/plugins/module_runner_transform.rs b/crates/oxc_transformer/src/plugins/module_runner_transform.rs index 6b2b84cb0ca66..4716a09a6f955 100644 --- a/crates/oxc_transformer/src/plugins/module_runner_transform.rs +++ b/crates/oxc_transformer/src/plugins/module_runner_transform.rs @@ -124,7 +124,9 @@ impl<'a> ModuleRunnerTransform<'a> { // Reserve enough space for new statements 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()); for stmt in program.body.drain(..) { match stmt { @@ -132,9 +134,8 @@ impl<'a> ModuleRunnerTransform<'a> { 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 let Some(index) = hoist_index { - new_stmts.insert(index, import_statement); - hoist_index.replace(index + 1); + if hoist_index.is_some() { + hoist_imports.push(import_statement); } else { new_stmts.push(import_statement); } @@ -161,6 +162,12 @@ impl<'a> ModuleRunnerTransform<'a> { } } + if let Some(index) = hoist_index { + if !hoist_imports.is_empty() { + new_stmts.splice(index..index, hoist_imports); + } + } + program.body = new_stmts; } From 74df54a82282fcefcc86ef12f08a61acd4987c60 Mon Sep 17 00:00:00 2001 From: Dunqing Date: Fri, 28 Feb 2025 13:42:40 +0800 Subject: [PATCH 25/30] Fix incorrect transform for `export default` anonymous function/class --- .../src/plugins/module_runner_transform.rs | 57 +++++++++---------- 1 file changed, 28 insertions(+), 29 deletions(-) diff --git a/crates/oxc_transformer/src/plugins/module_runner_transform.rs b/crates/oxc_transformer/src/plugins/module_runner_transform.rs index 4716a09a6f955..10cd907b0d8a3 100644 --- a/crates/oxc_transformer/src/plugins/module_runner_transform.rs +++ b/crates/oxc_transformer/src/plugins/module_runner_transform.rs @@ -536,11 +536,12 @@ impl<'a> ModuleRunnerTransform<'a> { Statement::FunctionDeclaration(func), Self::create_export(span, ident, DEFAULT, ctx), ]); - return; + } else { + func.r#type = FunctionType::FunctionExpression; + let right = Expression::FunctionExpression(func); + new_stmts.push(Self::create_export_default_assignment(span, right, ctx)); } - // Without name, treat it as an expression - func.r#type = FunctionType::FunctionExpression; - Expression::FunctionExpression(func) + return; } ExportDefaultDeclarationKind::ClassDeclaration(mut class) => { if let Some(id) = &class.id { @@ -549,12 +550,12 @@ impl<'a> ModuleRunnerTransform<'a> { Statement::ClassDeclaration(class), Self::create_export(span, ident, DEFAULT, ctx), ]); - return; + } else { + class.r#type = ClassType::ClassExpression; + let right = Expression::ClassExpression(class); + new_stmts.push(Self::create_export_default_assignment(span, right, ctx)); } - - // Without name, treat it as an expression - class.r#type = ClassType::ClassExpression; - Expression::ClassExpression(class) + return; } ExportDefaultDeclarationKind::TSInterfaceDeclaration(_) => { // Do nothing for `export default interface Foo {}` @@ -768,6 +769,22 @@ impl<'a> ModuleRunnerTransform<'a> { ctx.ast.statement_expression(span, Self::create_define_property(arguments, ctx)) } + + // __vite_ssr_exports__.default = right; + fn create_export_default_assignment( + span: Span, + right: Expression<'a>, + ctx: &mut TraverseCtx<'a>, + ) -> Statement<'a> { + let object = + ctx.create_unbound_ident_expr(SPAN, SSR_MODULE_EXPORTS_KEY, ReferenceFlags::Read); + let property = ctx.ast.identifier_name(SPAN, DEFAULT); + let member = ctx.ast.member_expression_static(SPAN, object, property, false); + let target = AssignmentTarget::from(member); + let operator = AssignmentOperator::Assign; + let assignment = ctx.ast.expression_assignment(SPAN, operator, target, right); + ctx.ast.statement_expression(span, assignment) + } } #[cfg(test)] @@ -1233,28 +1250,10 @@ Object.defineProperty(__vite_ssr_exports__, 'B', { #[test] fn should_handle_default_export_variants() { // default anonymous functions - test_same( - "export default function() {}", - "Object.defineProperty(__vite_ssr_exports__, 'default', { - enumerable: true, - configurable: true, - get() { - return function() {}; - } -});", - ); + test_same("export default function() {}", "__vite_ssr_exports__.default = function() {};"); // default anonymous class - test_same( - "export default class {}", - "Object.defineProperty(__vite_ssr_exports__, 'default', { - enumerable: true, - configurable: true, - get() { - return class {}; - } -});", - ); + test_same("export default class {}", "__vite_ssr_exports__.default = class {};"); // default named functions test_same( From 1779afc7a06b9ad1d743afe92b99f130640617b7 Mon Sep 17 00:00:00 2001 From: Dunqing Date: Fri, 28 Feb 2025 13:44:16 +0800 Subject: [PATCH 26/30] Format a test --- .../src/plugins/module_runner_transform.rs | 42 +++++++++++-------- 1 file changed, 24 insertions(+), 18 deletions(-) diff --git a/crates/oxc_transformer/src/plugins/module_runner_transform.rs b/crates/oxc_transformer/src/plugins/module_runner_transform.rs index 10cd907b0d8a3..1ceb4ce77790b 100644 --- a/crates/oxc_transformer/src/plugins/module_runner_transform.rs +++ b/crates/oxc_transformer/src/plugins/module_runner_transform.rs @@ -1225,24 +1225,30 @@ class A extends __vite_ssr_import_0__.Foo {}", #[test] fn should_declare_exported_super_class() { test_same( - "import { Foo } from './dependency'; export default class A extends 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', { - enumerable: true, - configurable: true, - get() { - return A; - } -}); -class B extends __vite_ssr_import_0__.Foo {} -Object.defineProperty(__vite_ssr_exports__, 'B', { - enumerable: true, - configurable: true, - get() { - return B; - } -});", + " + import { Foo } from './dependency'; + export default class A extends 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', { + enumerable: true, + configurable: true, + get() { + return A; + } + }); + class B extends __vite_ssr_import_0__.Foo {} + Object.defineProperty(__vite_ssr_exports__, 'B', { + enumerable: true, + configurable: true, + get() { + return B; + } + }); + ", ); } From 84051f7b682c97e73262149b5a0b578ee61b4285 Mon Sep 17 00:00:00 2001 From: Dunqing Date: Fri, 28 Feb 2025 13:47:00 +0800 Subject: [PATCH 27/30] Add a test "named exports of imported binding" --- .../src/plugins/module_runner_transform.rs | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/crates/oxc_transformer/src/plugins/module_runner_transform.rs b/crates/oxc_transformer/src/plugins/module_runner_transform.rs index 1ceb4ce77790b..54e8375abdde9 100644 --- a/crates/oxc_transformer/src/plugins/module_runner_transform.rs +++ b/crates/oxc_transformer/src/plugins/module_runner_transform.rs @@ -1005,6 +1005,26 @@ Object.defineProperty(__vite_ssr_exports__, 'c', { ); } + #[test] + fn export_named_imported_binding() { + test_same( + " + import { createApp } from 'vue'; + export { createApp } + ", + " + const __vite_ssr_import_0__ = await __vite_ssr_import__('vue', { importedNames: ['createApp'] }); + Object.defineProperty(__vite_ssr_exports__, 'createApp', { + enumerable: true, + configurable: true, + get() { + return __vite_ssr_import_0__.createApp; + } + }); + ", + ); + } + #[test] fn export_star_from() { test_same( From d9396539fa2c2e0625ee0295986255b5a67548be Mon Sep 17 00:00:00 2001 From: Dunqing Date: Fri, 28 Feb 2025 14:00:44 +0800 Subject: [PATCH 28/30] Update crates/oxc_transformer/src/plugins/module_runner_transform.rs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: 翠 / green --- crates/oxc_transformer/src/plugins/module_runner_transform.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/oxc_transformer/src/plugins/module_runner_transform.rs b/crates/oxc_transformer/src/plugins/module_runner_transform.rs index 54e8375abdde9..be0e02c70efe5 100644 --- a/crates/oxc_transformer/src/plugins/module_runner_transform.rs +++ b/crates/oxc_transformer/src/plugins/module_runner_transform.rs @@ -484,7 +484,7 @@ impl<'a> ModuleRunnerTransform<'a> { let export = if let Some(exported) = exported { // `export * as foo from 'vue'` -> - // `defineProperty(__vite_ssr_exports__, 'foo', { enumerable: true, configurable: true, get(){ return __vite_ssr_import_0__ } });` + // `Object.defineProperty(__vite_ssr_exports__, 'foo', { enumerable: true, configurable: true, get(){ return __vite_ssr_import_0__ } });` Self::create_export(span, ident, exported.name(), ctx) } else { let callee = ctx.ast.expression_identifier(SPAN, SSR_EXPORT_ALL_KEY); From d4c10b9130231abd5b627de2eb0faa37e28b5b35 Mon Sep 17 00:00:00 2001 From: Dunqing Date: Fri, 28 Feb 2025 14:04:00 +0800 Subject: [PATCH 29/30] Update crates/oxc_transformer/src/plugins/module_runner_transform.rs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: 翠 / green --- crates/oxc_transformer/src/plugins/module_runner_transform.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/oxc_transformer/src/plugins/module_runner_transform.rs b/crates/oxc_transformer/src/plugins/module_runner_transform.rs index be0e02c70efe5..b7e99eac43eaa 100644 --- a/crates/oxc_transformer/src/plugins/module_runner_transform.rs +++ b/crates/oxc_transformer/src/plugins/module_runner_transform.rs @@ -156,7 +156,7 @@ impl<'a> ModuleRunnerTransform<'a> { } } - // Found that non-import statements, we can start hoisting import statements after this + // 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); } From 99f2527789dba850f87a194738a0de6041361f4f Mon Sep 17 00:00:00 2001 From: Dunqing Date: Fri, 28 Feb 2025 14:06:42 +0800 Subject: [PATCH 30/30] Remove redundant semicolon --- crates/oxc_transformer/src/plugins/module_runner_transform.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/oxc_transformer/src/plugins/module_runner_transform.rs b/crates/oxc_transformer/src/plugins/module_runner_transform.rs index b7e99eac43eaa..47a37248143ca 100644 --- a/crates/oxc_transformer/src/plugins/module_runner_transform.rs +++ b/crates/oxc_transformer/src/plugins/module_runner_transform.rs @@ -1247,7 +1247,7 @@ class A extends __vite_ssr_import_0__.Foo {}", test_same( " import { Foo } from './dependency'; - export default class A extends Foo {}; + export default class A extends Foo {} export class B extends Foo {} ", "