use swc_atoms::js_word;
use swc_common::{
collections::AHashSet, comments::Comments, util::take::Take, FileName, Mark, Span, Spanned,
DUMMY_SP,
};
use swc_ecma_ast::*;
use swc_ecma_transforms_base::{feature::FeatureFlag, helper_expr};
use swc_ecma_utils::{
member_expr, private_ident, quote_ident, ExprFactory, FunctionFactory, IsDirective,
};
use swc_ecma_visit::{as_folder, noop_visit_mut_type, Fold, VisitMut, VisitMutWith};
pub use super::util::Config;
use crate::{
module_decl_strip::{Export, Link, LinkFlag, LinkItem, LinkSpecifierReducer, ModuleDeclStrip},
module_ref_rewriter::{ImportMap, ModuleRefRewriter},
path::{ImportResolver, Resolver},
util::{
clone_first_use_strict, define_es_module, emit_export_stmts, local_name_for_src, prop_name,
use_strict, ImportInterop, ObjPropKeyIdent,
},
};
pub fn common_js<C>(
unresolved_mark: Mark,
config: Config,
available_features: FeatureFlag,
comments: Option<C>,
) -> impl Fold + VisitMut
where
C: Comments,
{
as_folder(Cjs {
config,
resolver: Resolver::Default,
unresolved_mark,
available_features,
comments,
support_arrow: caniuse!(available_features.ArrowFunctions),
const_var_kind: if caniuse!(available_features.BlockScoping) {
VarDeclKind::Const
} else {
VarDeclKind::Var
},
})
}
pub fn common_js_with_resolver<C>(
resolver: Box<dyn ImportResolver>,
base: FileName,
unresolved_mark: Mark,
config: Config,
available_features: FeatureFlag,
comments: Option<C>,
) -> impl Fold + VisitMut
where
C: Comments,
{
as_folder(Cjs {
config,
resolver: Resolver::Real { base, resolver },
unresolved_mark,
available_features,
comments,
support_arrow: caniuse!(available_features.ArrowFunctions),
const_var_kind: if caniuse!(available_features.BlockScoping) {
VarDeclKind::Const
} else {
VarDeclKind::Var
},
})
}
pub struct Cjs<C>
where
C: Comments,
{
config: Config,
resolver: Resolver,
unresolved_mark: Mark,
available_features: FeatureFlag,
comments: Option<C>,
support_arrow: bool,
const_var_kind: VarDeclKind,
}
impl<C> VisitMut for Cjs<C>
where
C: Comments,
{
noop_visit_mut_type!();
fn visit_mut_module_items(&mut self, n: &mut Vec<ModuleItem>) {
let import_interop = self.config.import_interop();
let mut module_map = Default::default();
let mut has_ts_import_equals = false;
n.iter_mut().for_each(|item| {
if let ModuleItem::ModuleDecl(module_decl) = item {
*item = self.handle_ts_import_equals(
module_decl.take(),
&mut module_map,
&mut has_ts_import_equals,
);
}
});
let mut strip = ModuleDeclStrip::new(self.const_var_kind);
n.visit_mut_with(&mut strip);
let mut stmts: Vec<ModuleItem> = Vec::with_capacity(n.len() + 4);
if self.config.strict_mode {
stmts.push(clone_first_use_strict(n).unwrap_or_else(use_strict).into());
}
let ModuleDeclStrip {
link,
export,
export_assign,
has_module_decl,
..
} = strip;
let has_module_decl = has_module_decl || has_ts_import_equals;
let is_export_assign = export_assign.is_some();
if has_module_decl && !import_interop.is_none() && !is_export_assign {
stmts.push(define_es_module(self.exports()).into())
}
let mut lazy_record = Default::default();
stmts.extend(
self.handle_import_export(
&mut module_map,
&mut lazy_record,
link,
export,
is_export_assign,
)
.map(From::from),
);
stmts.extend(n.take().into_iter().filter(|item| match item {
ModuleItem::Stmt(stmt) => !stmt.is_use_strict(),
_ => false,
}));
if let Some(export_assign) = export_assign {
stmts.push(
export_assign
.make_assign_to(
op!("="),
member_expr!(DUMMY_SP.apply_mark(self.unresolved_mark), module.exports)
.into(),
)
.into_stmt()
.into(),
)
}
if !self.config.ignore_dynamic || !self.config.preserve_import_meta {
stmts.visit_mut_children_with(self);
}
stmts.visit_mut_children_with(&mut ModuleRefRewriter {
import_map: module_map,
lazy_record,
allow_top_level_this: self.config.allow_top_level_this,
is_global_this: true,
});
*n = stmts;
}
fn visit_mut_expr(&mut self, n: &mut Expr) {
match n {
Expr::Call(CallExpr {
span,
callee: Callee::Import(Import { span: import_span }),
args,
..
}) if !self.config.ignore_dynamic => {
args.visit_mut_with(self);
let mut is_lit_path = false;
args.get_mut(0).into_iter().for_each(|x| {
if let ExprOrSpread { spread: None, expr } = x {
if let Expr::Lit(Lit::Str(Str { value, raw, .. })) = &mut **expr {
is_lit_path = true;
*value = self.resolver.resolve(value.clone());
*raw = None;
}
}
});
let require_span = import_span.apply_mark(self.unresolved_mark);
*n = cjs_dynamic_import(
*span,
self.pure_span(),
args.take(),
quote_ident!(require_span, "require"),
self.config.import_interop(),
self.support_arrow,
is_lit_path,
);
}
Expr::Member(MemberExpr {
span,
obj,
prop:
MemberProp::Ident(Ident {
sym: js_word!("url"),
..
}),
}) if !self.config.preserve_import_meta
&& obj
.as_meta_prop()
.map(|p| p.kind == MetaPropKind::ImportMeta)
.unwrap_or_default() =>
{
obj.visit_mut_with(self);
let require = quote_ident!(DUMMY_SP.apply_mark(self.unresolved_mark), "require");
*n = cjs_import_meta_url(*span, require, self.unresolved_mark);
}
_ => n.visit_mut_children_with(self),
}
}
}
impl<C> Cjs<C>
where
C: Comments,
{
fn handle_import_export(
&mut self,
import_map: &mut ImportMap,
lazy_record: &mut AHashSet<Id>,
link: Link,
export: Export,
is_export_assign: bool,
) -> impl Iterator<Item = Stmt> {
let import_interop = self.config.import_interop();
let is_node = import_interop.is_node();
let mut stmts = Vec::with_capacity(link.len());
let mut export_obj_prop_list = export.into_iter().map(From::from).collect();
let lexer_reexport = if is_node {
self.emit_lexer_ts_reexport(&link)
} else {
None
};
link.into_iter().for_each(
|(src, LinkItem(src_span, link_specifier_set, mut link_flag))| {
let is_swc_default_helper =
!link_flag.has_named() && src.starts_with("@swc/helpers/");
let is_node_default = !link_flag.has_named() && is_node;
if import_interop.is_none() || is_swc_default_helper {
link_flag -= LinkFlag::NAMESPACE;
}
let mod_ident = private_ident!(local_name_for_src(&src));
let mut decl_mod_ident = false;
link_specifier_set.reduce(
import_map,
&mut export_obj_prop_list,
&mod_ident,
&None,
&mut decl_mod_ident,
is_swc_default_helper || is_node_default,
);
let is_lazy =
decl_mod_ident && !link_flag.export_star() && self.config.lazy.is_lazy(&src);
if is_lazy {
lazy_record.insert(mod_ident.to_id());
}
let import_expr =
self.resolver
.make_require_call(self.unresolved_mark, src, src_span);
let import_expr = if is_swc_default_helper {
import_expr.make_member(quote_ident!("default"))
} else {
import_expr
};
let import_expr = if link_flag.export_star() {
helper_expr!(export_star, "exportStar").as_call(
DUMMY_SP,
vec![import_expr.as_arg(), self.exports().as_arg()],
)
} else {
import_expr
};
let import_expr = {
match import_interop {
ImportInterop::Swc if link_flag.interop() => if link_flag.namespace() {
helper_expr!(interop_require_wildcard, "interopRequireWildcard")
} else {
helper_expr!(interop_require_default, "interopRequireDefault")
}
.as_call(self.pure_span(), vec![import_expr.as_arg()]),
ImportInterop::Node if link_flag.namespace() => {
helper_expr!(interop_require_wildcard, "interopRequireWildcard")
.as_call(
self.pure_span(),
vec![import_expr.as_arg(), true.as_arg()],
)
}
_ => import_expr,
}
};
if decl_mod_ident {
let stmt = if is_lazy {
Stmt::Decl(Decl::Fn(lazy_require(
import_expr,
mod_ident,
self.const_var_kind,
)))
} else {
Stmt::Decl(
import_expr
.into_var_decl(self.const_var_kind, mod_ident.into())
.into(),
)
};
stmts.push(stmt);
} else {
stmts.push(import_expr.into_stmt());
}
},
);
let mut export_stmts: Vec<Stmt> = Default::default();
if !export_obj_prop_list.is_empty() && !is_export_assign {
export_obj_prop_list.sort_by_key(|prop| prop.span());
let mut features = self.available_features;
let exports = self.exports();
if is_node {
if export_obj_prop_list.len() > 1 {
export_stmts.extend(self.emit_lexer_exports_init(&export_obj_prop_list));
} else {
features -= FeatureFlag::ArrowFunctions;
}
}
export_stmts.extend(emit_export_stmts(features, exports, export_obj_prop_list));
}
export_stmts.extend(lexer_reexport);
export_stmts.into_iter().chain(stmts)
}
fn handle_ts_import_equals(
&self,
module_decl: ModuleDecl,
module_map: &mut ImportMap,
has_ts_import_equals: &mut bool,
) -> ModuleItem {
match module_decl {
ModuleDecl::TsImportEquals(v)
if matches!(
&*v,
TsImportEqualsDecl {
declare: false,
is_type_only: false,
module_ref: TsModuleRef::TsExternalModuleRef(TsExternalModuleRef { .. }),
..
}
) =>
{
let TsImportEqualsDecl {
span,
is_export,
id,
module_ref,
..
} = *v;
let Str {
span: src_span,
value: src,
..
} = module_ref.expect_ts_external_module_ref().expr;
*has_ts_import_equals = true;
let require = self
.resolver
.make_require_call(self.unresolved_mark, src, src_span);
if is_export {
module_map.insert(id.to_id(), (self.exports(), Some(id.sym.clone())));
let assign_expr = AssignExpr {
span,
op: op!("="),
left: id.as_pat_or_expr(),
right: Box::new(require),
};
assign_expr.into_stmt()
} else {
let mut var_decl = require.into_var_decl(self.const_var_kind, id.into());
var_decl.span = span;
Stmt::Decl(var_decl.into())
}
.into()
}
_ => module_decl.into(),
}
}
fn exports(&self) -> Ident {
quote_ident!(DUMMY_SP.apply_mark(self.unresolved_mark), "exports")
}
fn emit_lexer_exports_init(&mut self, export_id_list: &[ObjPropKeyIdent]) -> Option<Stmt> {
match export_id_list.len() {
0 => None,
1 => {
let expr: Expr = 0.into();
let key_value = &export_id_list[0];
let prop = prop_name(key_value.key(), DUMMY_SP).into();
let export_binding = MemberExpr {
obj: Box::new(self.exports().into()),
span: key_value.span(),
prop,
};
let expr = expr.make_assign_to(op!("="), export_binding.as_pat_or_expr());
let expr = BinExpr {
span: DUMMY_SP,
op: op!("&&"),
left: 0.into(),
right: Box::new(expr),
};
Some(expr.into_stmt())
}
_ => {
let props = export_id_list
.iter()
.map(|key_value| prop_name(key_value.key(), DUMMY_SP))
.map(|key| KeyValueProp {
key: key.into(),
value: quote_ident!("_").into(),
})
.map(Prop::KeyValue)
.map(Box::new)
.map(PropOrSpread::Prop)
.collect();
let module_exports_assign = ObjectLit {
span: DUMMY_SP,
props,
}
.make_assign_to(
op!("="),
member_expr!(DUMMY_SP.apply_mark(self.unresolved_mark), module.exports).into(),
);
let expr = BinExpr {
span: DUMMY_SP,
op: op!("&&"),
left: 0.into(),
right: Box::new(module_exports_assign),
};
Some(expr.into_stmt())
}
}
}
fn emit_lexer_ts_reexport(&self, link: &Link) -> Option<Stmt> {
let mut seq_list = vec![];
link.iter().for_each(|(src, LinkItem(_, _, link_flag))| {
if link_flag.export_star() {
let import_expr =
self.resolver
.make_require_call(self.unresolved_mark, src.clone(), DUMMY_SP);
let export = Expr::Ident(quote_ident!("__export"))
.as_call(DUMMY_SP, vec![import_expr.as_arg()]);
seq_list.push(Box::new(export));
}
});
if seq_list.is_empty() {
None
} else {
let seq_expr = SeqExpr {
span: DUMMY_SP,
exprs: seq_list,
}
.into();
let expr = BinExpr {
span: DUMMY_SP,
op: op!("&&"),
left: 0.into(),
right: seq_expr,
};
Some(expr.into_stmt())
}
}
fn pure_span(&self) -> Span {
let mut span = DUMMY_SP;
if self.config.import_interop().is_none() {
return span;
}
if let Some(comments) = &self.comments {
span = Span::dummy_with_cmt();
comments.add_pure_comment(span.lo);
}
span
}
}
pub(crate) fn cjs_dynamic_import(
span: Span,
pure_span: Span,
args: Vec<ExprOrSpread>,
require: Ident,
import_interop: ImportInterop,
support_arrow: bool,
is_lit_path: bool,
) -> Expr {
let p = private_ident!("p");
let (resolve_args, callback_params, require_args) = if is_lit_path {
(vec![], vec![], args)
} else {
(args, vec![p.clone().into()], vec![p.as_arg()])
};
let then = member_expr!(DUMMY_SP, Promise.resolve)
.as_call(DUMMY_SP, resolve_args)
.make_member(quote_ident!("then"));
let import_expr = {
let require = require.as_call(DUMMY_SP, require_args);
match import_interop {
ImportInterop::None => require,
ImportInterop::Swc => helper_expr!(interop_require_wildcard, "interopRequireWildcard")
.as_call(pure_span, vec![require.as_arg()]),
ImportInterop::Node => helper_expr!(interop_require_wildcard, "interopRequireWildcard")
.as_call(pure_span, vec![require.as_arg(), true.as_arg()]),
}
};
then.as_call(
span,
vec![import_expr
.into_lazy_auto(callback_params, support_arrow)
.as_arg()],
)
}
fn cjs_import_meta_url(span: Span, require: Ident, unresolved_mark: Mark) -> Expr {
require
.as_call(DUMMY_SP, vec!["url".as_arg()])
.make_member(quote_ident!("pathToFileURL"))
.as_call(
DUMMY_SP,
vec![quote_ident!(DUMMY_SP.apply_mark(unresolved_mark), "__filename").as_arg()],
)
.make_member(quote_ident!("toString"))
.as_call(span, Default::default())
}
pub fn lazy_require(expr: Expr, mod_ident: Ident, var_kind: VarDeclKind) -> FnDecl {
let data = private_ident!("data");
let data_decl = expr.into_var_decl(var_kind, data.clone().into());
let data_stmt = data_decl.into();
let overwrite_stmt = data
.clone()
.into_lazy_fn(Default::default())
.into_fn_expr(None)
.make_assign_to(op!("="), mod_ident.clone().as_pat_or_expr())
.into_stmt();
let return_stmt = data.into_return_stmt().into();
FnDecl {
ident: mod_ident,
declare: false,
function: Function {
params: Default::default(),
decorators: Default::default(),
span: DUMMY_SP,
body: Some(BlockStmt {
span: DUMMY_SP,
stmts: vec![data_stmt, overwrite_stmt, return_stmt],
}),
is_generator: false,
is_async: false,
type_params: None,
return_type: None,
}
.into(),
}
}