mirror of
https://github.com/danbulant/oxc
synced 2026-05-25 04:42:10 +00:00
feat(transformer): add import helpers to manage module imports (#2996)
closes #2971
This commit is contained in:
parent
df2036eea0
commit
e43c245388
7 changed files with 172 additions and 71 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
|
@ -1678,6 +1678,7 @@ dependencies = [
|
|||
name = "oxc_transformer"
|
||||
version = "0.12.3"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"oxc_allocator",
|
||||
"oxc_ast",
|
||||
"oxc_codegen",
|
||||
|
|
|
|||
|
|
@ -27,8 +27,8 @@ oxc_diagnostics = { workspace = true }
|
|||
oxc_syntax = { workspace = true }
|
||||
|
||||
rustc-hash = { workspace = true }
|
||||
|
||||
serde = { workspace = true, features = ["derive"] }
|
||||
indexmap = { workspace = true }
|
||||
serde = { workspace = true, features = ["derive"] }
|
||||
|
||||
[dev-dependencies]
|
||||
oxc_parser = { workspace = true }
|
||||
|
|
|
|||
|
|
@ -5,6 +5,8 @@ use oxc_ast::AstBuilder;
|
|||
use oxc_diagnostics::Error;
|
||||
use oxc_semantic::Semantic;
|
||||
|
||||
use crate::helpers::module_imports::ModuleImports;
|
||||
|
||||
pub type Ctx<'a> = Rc<TransformCtx<'a>>;
|
||||
|
||||
pub struct TransformCtx<'a> {
|
||||
|
|
@ -16,6 +18,10 @@ pub struct TransformCtx<'a> {
|
|||
filename: String,
|
||||
|
||||
errors: RefCell<Vec<Error>>,
|
||||
|
||||
// Helpers
|
||||
/// Manage import statement globally
|
||||
pub module_imports: ModuleImports<'a>,
|
||||
}
|
||||
|
||||
impl<'a> TransformCtx<'a> {
|
||||
|
|
@ -25,7 +31,8 @@ impl<'a> TransformCtx<'a> {
|
|||
.file_stem() // omit file extension
|
||||
.map_or_else(|| String::from("unknown"), |name| name.to_string_lossy().to_string());
|
||||
let errors = RefCell::new(vec![]);
|
||||
Self { ast, semantic, filename, errors }
|
||||
let module_imports = ModuleImports::new(allocator);
|
||||
Self { ast, semantic, filename, errors, module_imports }
|
||||
}
|
||||
|
||||
pub fn take_errors(&self) -> Vec<Error> {
|
||||
|
|
|
|||
136
crates/oxc_transformer/src/helpers/module_imports.rs
Normal file
136
crates/oxc_transformer/src/helpers/module_imports.rs
Normal file
|
|
@ -0,0 +1,136 @@
|
|||
use indexmap::IndexMap;
|
||||
use std::cell::RefCell;
|
||||
|
||||
use oxc_allocator::{Allocator, Vec};
|
||||
use oxc_ast::{ast::*, AstBuilder};
|
||||
use oxc_span::{CompactStr, SPAN};
|
||||
|
||||
pub struct NamedImport {
|
||||
imported: CompactStr,
|
||||
local: Option<CompactStr>, // Not used in `require`
|
||||
}
|
||||
|
||||
impl NamedImport {
|
||||
pub fn new(imported: CompactStr, local: Option<CompactStr>) -> NamedImport {
|
||||
Self { imported, local }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Hash, Eq, PartialEq)]
|
||||
pub enum ImportKind {
|
||||
Import,
|
||||
Require,
|
||||
}
|
||||
|
||||
#[derive(Hash, Eq, PartialEq)]
|
||||
pub struct ImportType {
|
||||
kind: ImportKind,
|
||||
source: CompactStr,
|
||||
}
|
||||
|
||||
impl ImportType {
|
||||
fn new(kind: ImportKind, source: CompactStr) -> Self {
|
||||
Self { kind, source }
|
||||
}
|
||||
}
|
||||
|
||||
/// Manage import statement globally
|
||||
/// <https://github.com/nicolo-ribaudo/babel/tree/main/packages/babel-helper-module-imports>
|
||||
pub struct ModuleImports<'a> {
|
||||
ast: AstBuilder<'a>,
|
||||
|
||||
imports: RefCell<IndexMap<ImportType, std::vec::Vec<NamedImport>>>,
|
||||
}
|
||||
|
||||
impl<'a> ModuleImports<'a> {
|
||||
pub fn new(allocator: &'a Allocator) -> ModuleImports<'a> {
|
||||
let ast = AstBuilder::new(allocator);
|
||||
Self { ast, imports: RefCell::new(IndexMap::default()) }
|
||||
}
|
||||
|
||||
/// Add `import { named_import } from 'source'`
|
||||
pub fn add_import(&self, source: CompactStr, import: NamedImport) {
|
||||
self.imports
|
||||
.borrow_mut()
|
||||
.entry(ImportType::new(ImportKind::Import, source))
|
||||
.or_default()
|
||||
.push(import);
|
||||
}
|
||||
|
||||
/// Add `var named_import from 'source'`
|
||||
pub fn add_require(&self, source: CompactStr, import: NamedImport, front: bool) {
|
||||
let len = self.imports.borrow().len();
|
||||
self.imports
|
||||
.borrow_mut()
|
||||
.entry(ImportType::new(ImportKind::Require, source))
|
||||
.or_default()
|
||||
.push(import);
|
||||
if front {
|
||||
self.imports.borrow_mut().move_index(len, 0);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_import_statements(&self) -> Vec<'a, Statement<'a>> {
|
||||
self.ast.new_vec_from_iter(self.imports.borrow_mut().drain(..).map(
|
||||
|(import_type, names)| match import_type.kind {
|
||||
ImportKind::Import => self.get_named_import(&import_type.source, names),
|
||||
ImportKind::Require => self.get_require(&import_type.source, names),
|
||||
},
|
||||
))
|
||||
}
|
||||
|
||||
fn get_named_import(
|
||||
&self,
|
||||
source: &CompactStr,
|
||||
names: std::vec::Vec<NamedImport>,
|
||||
) -> Statement<'a> {
|
||||
let specifiers = self.ast.new_vec_from_iter(names.into_iter().map(|name| {
|
||||
ImportDeclarationSpecifier::ImportSpecifier(ImportSpecifier {
|
||||
span: SPAN,
|
||||
imported: ModuleExportName::Identifier(IdentifierName::new(
|
||||
SPAN,
|
||||
self.ast.new_atom(name.imported.as_str()),
|
||||
)),
|
||||
local: BindingIdentifier::new(
|
||||
SPAN,
|
||||
self.ast.new_atom(name.local.unwrap_or(name.imported).as_str()),
|
||||
),
|
||||
import_kind: ImportOrExportKind::Value,
|
||||
})
|
||||
}));
|
||||
let import_stmt = self.ast.import_declaration(
|
||||
SPAN,
|
||||
Some(specifiers),
|
||||
self.ast.string_literal(SPAN, source.as_str()),
|
||||
None,
|
||||
ImportOrExportKind::Value,
|
||||
);
|
||||
self.ast.module_declaration(ModuleDeclaration::ImportDeclaration(import_stmt))
|
||||
}
|
||||
|
||||
fn get_require(&self, source: &CompactStr, names: std::vec::Vec<NamedImport>) -> Statement<'a> {
|
||||
let var_kind = VariableDeclarationKind::Var;
|
||||
let callee = {
|
||||
let ident = IdentifierReference::new(SPAN, "require".into());
|
||||
self.ast.identifier_reference_expression(ident)
|
||||
};
|
||||
let args = {
|
||||
let string = self.ast.string_literal(SPAN, source.as_str());
|
||||
let arg = Argument::Expression(self.ast.literal_string_expression(string));
|
||||
self.ast.new_vec_single(arg)
|
||||
};
|
||||
let name = names.into_iter().next().unwrap();
|
||||
let id = {
|
||||
let ident = BindingIdentifier::new(SPAN, self.ast.new_atom(&name.imported));
|
||||
self.ast.binding_pattern(self.ast.binding_pattern_identifier(ident), None, false)
|
||||
};
|
||||
let decl = {
|
||||
let init = self.ast.call_expression(SPAN, callee, args, false, None);
|
||||
let decl = self.ast.variable_declarator(SPAN, var_kind, id, Some(init), false);
|
||||
self.ast.new_vec_single(decl)
|
||||
};
|
||||
let variable_declaration =
|
||||
self.ast.variable_declaration(SPAN, var_kind, decl, Modifiers::empty());
|
||||
Statement::Declaration(Declaration::VariableDeclaration(variable_declaration))
|
||||
}
|
||||
}
|
||||
|
|
@ -15,6 +15,10 @@ mod options;
|
|||
mod react;
|
||||
mod typescript;
|
||||
|
||||
mod helpers {
|
||||
pub mod module_imports;
|
||||
}
|
||||
|
||||
use std::{path::Path, rc::Rc};
|
||||
|
||||
use oxc_allocator::{Allocator, Vec};
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ use oxc_syntax::{
|
|||
xml_entities::XML_ENTITIES,
|
||||
};
|
||||
|
||||
use crate::context::Ctx;
|
||||
use crate::{context::Ctx, helpers::module_imports::NamedImport};
|
||||
|
||||
pub use super::{
|
||||
jsx_self::ReactJsxSelf,
|
||||
|
|
@ -42,7 +42,6 @@ pub struct ReactJsx<'a> {
|
|||
jsx_source: ReactJsxSource<'a>,
|
||||
|
||||
// States
|
||||
imports: std::vec::Vec<Statement<'a>>,
|
||||
require_jsx_runtime: bool,
|
||||
jsx_runtime_importer: CompactStr,
|
||||
default_runtime: ReactJsxRuntime,
|
||||
|
|
@ -69,7 +68,6 @@ impl<'a> ReactJsx<'a> {
|
|||
ctx: Rc::clone(ctx),
|
||||
jsx_self: ReactJsxSelf::new(ctx),
|
||||
jsx_source: ReactJsxSource::new(ctx),
|
||||
imports: vec![],
|
||||
require_jsx_runtime: false,
|
||||
jsx_runtime_importer,
|
||||
import_jsx: false,
|
||||
|
|
@ -118,7 +116,7 @@ impl<'a> ReactJsx<'a> {
|
|||
return;
|
||||
}
|
||||
|
||||
let imports = self.ctx.ast.new_vec_from_iter(self.imports.drain(..));
|
||||
let imports = self.ctx.module_imports.get_import_statements();
|
||||
let index = program
|
||||
.body
|
||||
.iter()
|
||||
|
|
@ -154,21 +152,20 @@ impl<'a> ReactJsx<'a> {
|
|||
fn add_require_jsx_runtime(&mut self) {
|
||||
if !self.require_jsx_runtime {
|
||||
self.require_jsx_runtime = true;
|
||||
let source = self.ast().string_literal(SPAN, self.jsx_runtime_importer.as_str());
|
||||
self.add_require_statement("_reactJsxRuntime", source, false);
|
||||
self.add_require_statement(
|
||||
"_reactJsxRuntime",
|
||||
self.jsx_runtime_importer.clone(),
|
||||
false,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn get_jsx_runtime_source(&self) -> StringLiteral<'a> {
|
||||
self.ast().string_literal(SPAN, self.jsx_runtime_importer.as_str())
|
||||
}
|
||||
|
||||
fn add_import_jsx(&mut self) {
|
||||
if self.is_script() {
|
||||
self.add_require_jsx_runtime();
|
||||
} else if !self.import_jsx {
|
||||
self.import_jsx = true;
|
||||
self.add_import_statement("jsx", "_jsx", self.get_jsx_runtime_source());
|
||||
self.add_import_statement("jsx", "_jsx", self.jsx_runtime_importer.clone());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -177,7 +174,7 @@ impl<'a> ReactJsx<'a> {
|
|||
self.add_require_jsx_runtime();
|
||||
} else if !self.import_jsxs {
|
||||
self.import_jsxs = true;
|
||||
self.add_import_statement("jsxs", "_jsxs", self.get_jsx_runtime_source());
|
||||
self.add_import_statement("jsxs", "_jsxs", self.jsx_runtime_importer.clone());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -186,7 +183,7 @@ impl<'a> ReactJsx<'a> {
|
|||
self.add_require_jsx_runtime();
|
||||
} else if !self.import_fragment {
|
||||
self.import_fragment = true;
|
||||
self.add_import_statement("Fragment", "_Fragment", self.get_jsx_runtime_source());
|
||||
self.add_import_statement("Fragment", "_Fragment", self.jsx_runtime_importer.clone());
|
||||
self.add_import_jsx();
|
||||
}
|
||||
}
|
||||
|
|
@ -194,65 +191,23 @@ impl<'a> ReactJsx<'a> {
|
|||
fn add_import_create_element(&mut self) {
|
||||
if !self.import_create_element {
|
||||
self.import_create_element = true;
|
||||
let source = self.ast().string_literal(SPAN, self.options.import_source.as_ref());
|
||||
let source = self.options.import_source.as_ref();
|
||||
if self.is_script() {
|
||||
self.add_require_statement("_react", source, true);
|
||||
self.add_require_statement("_react", source.into(), true);
|
||||
} else {
|
||||
self.add_import_statement("createElement", "_createElement", source);
|
||||
self.add_import_statement("createElement", "_createElement", source.into());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn add_import_statement(&mut self, imported: &str, local: &str, source: StringLiteral<'a>) {
|
||||
let mut specifiers = self.ast().new_vec_with_capacity(1);
|
||||
let imported =
|
||||
ModuleExportName::Identifier(IdentifierName::new(SPAN, self.ast().new_atom(imported)));
|
||||
specifiers.push(ImportDeclarationSpecifier::ImportSpecifier(ImportSpecifier {
|
||||
span: SPAN,
|
||||
imported,
|
||||
local: BindingIdentifier::new(SPAN, self.ast().new_atom(local)),
|
||||
import_kind: ImportOrExportKind::Value,
|
||||
}));
|
||||
let value = ImportOrExportKind::Value;
|
||||
let import_stmt =
|
||||
self.ast().import_declaration(SPAN, Some(specifiers), source, None, value);
|
||||
let decl = self.ast().module_declaration(ModuleDeclaration::ImportDeclaration(import_stmt));
|
||||
self.imports.push(decl);
|
||||
fn add_import_statement(&mut self, imported: &str, local: &str, source: CompactStr) {
|
||||
let import = NamedImport::new(imported.into(), Some(local.into()));
|
||||
self.ctx.module_imports.add_import(source, import);
|
||||
}
|
||||
|
||||
/// `var variable_name = require(source);`
|
||||
fn add_require_statement(
|
||||
&mut self,
|
||||
variable_name: &str,
|
||||
source: StringLiteral<'a>,
|
||||
front: bool,
|
||||
) {
|
||||
let var_kind = VariableDeclarationKind::Var;
|
||||
let callee = {
|
||||
let ident = IdentifierReference::new(SPAN, "require".into());
|
||||
self.ctx.ast.identifier_reference_expression(ident)
|
||||
};
|
||||
let args = {
|
||||
let arg = Argument::Expression(self.ast().literal_string_expression(source));
|
||||
self.ctx.ast.new_vec_single(arg)
|
||||
};
|
||||
let id = {
|
||||
let ident = BindingIdentifier::new(SPAN, self.ast().new_atom(variable_name));
|
||||
self.ast().binding_pattern(self.ast().binding_pattern_identifier(ident), None, false)
|
||||
};
|
||||
let decl = {
|
||||
let init = self.ast().call_expression(SPAN, callee, args, false, None);
|
||||
let decl = self.ast().variable_declarator(SPAN, var_kind, id, Some(init), false);
|
||||
self.ast().new_vec_single(decl)
|
||||
};
|
||||
let variable_declaration =
|
||||
self.ast().variable_declaration(SPAN, var_kind, decl, Modifiers::empty());
|
||||
let stmt = Statement::Declaration(Declaration::VariableDeclaration(variable_declaration));
|
||||
if front {
|
||||
self.imports.insert(0, stmt);
|
||||
} else {
|
||||
self.imports.push(stmt);
|
||||
}
|
||||
fn add_require_statement(&mut self, variable_name: &str, source: CompactStr, front: bool) {
|
||||
let import = NamedImport::new(variable_name.into(), None);
|
||||
self.ctx.module_imports.add_require(source, import, front);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
Passed: 123/227
|
||||
Passed: 125/227
|
||||
|
||||
# All Passed:
|
||||
* babel-plugin-transform-react-jsx-source
|
||||
|
|
@ -107,10 +107,8 @@ Passed: 123/227
|
|||
* regression/another-preset-with-custom-jsx-keep-source-self/input.mjs
|
||||
* regression/runtime-classic-allow-multiple-source-self/input.mjs
|
||||
|
||||
# babel-plugin-transform-react-jsx (33/36)
|
||||
* autoImport/auto-import-react-source-type-module/input.js
|
||||
# babel-plugin-transform-react-jsx (35/36)
|
||||
* autoImport/complicated-scope-module/input.js
|
||||
* autoImport/react-defined/input.js
|
||||
|
||||
# babel-plugin-transform-react-display-name (3/4)
|
||||
* display-name/nested/input.js
|
||||
|
|
|
|||
Loading…
Reference in a new issue