use farmfe_core::{
config::{Mode, FARM_DYNAMIC_REQUIRE, FARM_REQUIRE},
module::{meta_data::script::FARM_RUNTIME_MODULE_HELPER_ID, module_graph::ModuleGraph, ModuleId},
plugin::ResolveKind,
swc_common::{util::take::Take, Mark, SyntaxContext, DUMMY_SP},
swc_ecma_ast::{CallExpr, Callee, Expr, ExprOrSpread, Ident, Lit, MemberExpr, MemberProp, Str},
HashMap,
};
use farmfe_toolkit::{
script::{is_commonjs_require, is_dynamic_import},
swc_ecma_visit::{VisitMut, VisitMutWith},
};
pub struct SourceReplacer<'a> {
unresolved_mark: Mark,
top_level_mark: Mark,
module_graph: &'a ModuleGraph,
module_id: ModuleId,
mode: Mode,
hoisted_external_modules: HashMap<(String, farmfe_core::plugin::ResolveKind), ModuleId>,
pub external_modules: Vec<ModuleId>,
}
pub struct SourceReplacerOptions<'a> {
pub unresolved_mark: Mark,
pub top_level_mark: Mark,
pub module_graph: &'a ModuleGraph,
pub module_id: ModuleId,
pub mode: Mode,
pub hoisted_external_modules: HashMap<(String, farmfe_core::plugin::ResolveKind), ModuleId>,
}
impl<'a> SourceReplacer<'a> {
pub fn new(options: SourceReplacerOptions<'a>) -> Self {
let SourceReplacerOptions {
unresolved_mark,
top_level_mark,
module_graph,
module_id,
mode,
hoisted_external_modules,
} = options;
Self {
unresolved_mark,
top_level_mark,
module_graph,
module_id,
mode,
hoisted_external_modules,
external_modules: vec![],
}
}
}
impl<'a> VisitMut for SourceReplacer<'a> {
fn visit_mut_expr(&mut self, expr: &mut Expr) {
if let Expr::Call(call_expr) = expr {
if let SourceReplaceResult::NotScriptModule = self.replace_source_with_id(call_expr) {
*expr = Expr::Lit(Lit::Str(Str {
span: DUMMY_SP,
value: "".into(),
raw: None,
}));
}
} else {
expr.visit_mut_children_with(self);
}
}
}
enum SourceReplaceResult {
NotReplaced,
Replaced,
NotScriptModule,
}
impl SourceReplacer<'_> {
fn find_real_module_meta_by_source(
&self,
source: &str,
kinds: Vec<ResolveKind>,
) -> Option<ModuleId> {
let mut id = None;
for kind in kinds {
if let Some(dep_id) = self
.module_graph
.get_dep_by_source_optional(&self.module_id, &source, Some(kind.clone()))
.or(
self
.hoisted_external_modules
.get(&(source.to_string(), kind.clone()))
.cloned(),
)
{
id = Some(dep_id);
break;
}
}
id
}
fn replace_source_with_id(&mut self, call_expr: &mut CallExpr) -> SourceReplaceResult {
if call_expr.args.len() != 1 {
call_expr.visit_mut_children_with(self);
return SourceReplaceResult::NotReplaced;
}
if is_commonjs_require(self.unresolved_mark, self.top_level_mark, &*call_expr) {
if let ExprOrSpread {
spread: None,
expr: box Expr::Lit(Lit::Str(str)),
} = &mut call_expr.args[0]
{
let source = str.value.to_string();
call_expr.callee = Callee::Expr(Box::new(Expr::Ident(Ident {
span: DUMMY_SP,
sym: FARM_REQUIRE.into(),
optional: false,
ctxt: SyntaxContext::empty(),
})));
if [FARM_RUNTIME_MODULE_HELPER_ID].contains(&source.as_str()) {
str.value = source.as_str().into();
str.span = DUMMY_SP;
str.raw = None;
return SourceReplaceResult::Replaced;
}
let Some(id) = self.find_real_module_meta_by_source(
&source,
vec![
ResolveKind::Import,
ResolveKind::ExportFrom,
ResolveKind::Require,
],
) else {
panic!(
"Cannot find module id for source {:?} from {:?}.",
source, self.module_id
)
};
let dep_module = self.module_graph.module(&id).unwrap();
if dep_module.external {
self.external_modules.push(id.clone());
return SourceReplaceResult::NotReplaced;
}
if dep_module.module_type.is_script() {
str.value = id.id(self.mode.clone()).into();
str.span = DUMMY_SP;
str.raw = None;
return SourceReplaceResult::Replaced;
} else {
return SourceReplaceResult::NotScriptModule;
}
}
} else if is_dynamic_import(&*call_expr) {
if let ExprOrSpread {
spread: None,
expr: box Expr::Lit(Lit::Str(str)),
} = &mut call_expr.args[0]
{
let source = str.value.to_string();
if let Some(id) =
self.find_real_module_meta_by_source(&source, vec![ResolveKind::DynamicImport])
{
let dep_module = self.module_graph.module(&id).unwrap();
if dep_module.external {
self.external_modules.push(id);
return SourceReplaceResult::NotReplaced;
}
str.value = id.id(self.mode.clone()).into();
str.span = DUMMY_SP;
str.raw = None;
} else {
panic!(
"cannot found {} of DynamicImport from {}",
source,
self.module_id.to_string()
);
}
call_expr.callee = Callee::Expr(Box::new(Expr::Ident(Ident {
span: DUMMY_SP,
sym: FARM_DYNAMIC_REQUIRE.into(),
optional: false,
ctxt: SyntaxContext::empty(),
})));
return SourceReplaceResult::Replaced;
}
}
call_expr.visit_mut_children_with(self);
SourceReplaceResult::NotReplaced
}
}
pub struct ExistingCommonJsRequireVisitor<'a> {
unresolved_mark: Mark,
top_level_mark: Mark,
module_graph: &'a ModuleGraph,
module_id: ModuleId,
}
impl<'a> ExistingCommonJsRequireVisitor<'a> {
pub fn new(
unresolved_mark: Mark,
top_level_mark: Mark,
module_graph: &'a ModuleGraph,
module_id: ModuleId,
) -> Self {
Self {
unresolved_mark,
top_level_mark,
module_graph,
module_id,
}
}
}
impl<'a> VisitMut for ExistingCommonJsRequireVisitor<'a> {
fn visit_mut_call_expr(&mut self, call_expr: &mut CallExpr) {
if call_expr.args.len() != 1 {
call_expr.visit_mut_children_with(self);
return;
}
if is_commonjs_require(self.unresolved_mark, self.top_level_mark, &*call_expr) {
if let ExprOrSpread {
spread: None,
expr: box Expr::Lit(Lit::Str(str)),
} = &mut call_expr.args[0]
{
let source = str.value.to_string();
if let Some(id) =
self
.module_graph
.get_dep_by_source_optional(&self.module_id, &source, None)
{
let source_module = self.module_graph.module(&id).unwrap();
if source_module.module_type.is_script() && source_module.external {
let expr_take = call_expr.take();
*call_expr = CallExpr {
span: DUMMY_SP,
callee: Callee::Expr(Box::new(Expr::Member(MemberExpr {
span: DUMMY_SP,
obj: Box::new(Expr::Ident(Ident {
span: DUMMY_SP,
sym: FARM_REQUIRE.into(),
optional: false,
ctxt: SyntaxContext::empty(),
})),
prop: MemberProp::Ident("i".into()),
}))),
args: vec![ExprOrSpread {
spread: None,
expr: Box::new(Expr::Call(expr_take)),
}],
type_args: None,
ctxt: SyntaxContext::empty(),
};
}
}
}
}
}
}