use farmfe_core::{
config::{
config_regex::ConfigRegex,
preset_env::{PresetEnvConfig, PresetEnvConfigObj},
AliasItem, Config, Mode as FarmMode, StringOrRegex,
},
plugin::Plugin,
serde_json,
swc_common::{comments::SingleThreadedComments, Mark},
swc_ecma_ast::{EsVersion, Module, ModuleItem, Program, Script},
};
use farmfe_toolkit::{
constant::RUNTIME_SUFFIX,
script::swc_try_with::{resolve_module_mark, try_with},
swc_ecma_preset_env::{self, transform_from_env, transform_from_es_version, EnvConfig, Mode},
swc_ecma_transforms::{
fixer,
hygiene::{hygiene_with_config, Config as HygieneConfig},
Assumptions,
},
swc_ecma_transforms_base::helpers::inject_helpers,
swc_ecma_visit::VisitMutWith,
};
pub const SWC_HELPERS_PACKAGE: &str = "@swc/helpers";
pub struct FarmPluginPolyfill {
config: swc_ecma_preset_env::Config,
include: Vec<ConfigRegex>,
exclude: Vec<ConfigRegex>,
enforce_exclude: Vec<ConfigRegex>,
assumptions: Assumptions,
farm_runtime_regex: ConfigRegex,
}
impl FarmPluginPolyfill {
pub fn new(config: &Config) -> Self {
let (config, include, exclude, assumptions) = match &*config.preset_env {
PresetEnvConfig::Bool(_) => {
let PresetEnvConfigObj {
include,
exclude,
options: _,
assumptions: _,
} = PresetEnvConfigObj::default();
(
swc_ecma_preset_env::Config {
mode: Some(Mode::Usage),
..Default::default()
},
include,
exclude,
Default::default(),
)
}
PresetEnvConfig::Obj(obj) => {
let options = &obj.options;
let mut user_config: swc_ecma_preset_env::Config =
serde_json::from_value(*options.clone()).unwrap();
user_config.mode = user_config.mode.or(Some(Mode::Usage));
let user_assumption: Assumptions =
serde_json::from_value(*obj.assumptions.clone()).unwrap();
(
user_config,
obj.include.clone(),
obj.exclude.clone(),
user_assumption,
)
}
};
Self {
config,
include,
exclude,
assumptions,
enforce_exclude: vec![ConfigRegex::new("node_modules/core-js")],
farm_runtime_regex: ConfigRegex::new(format!("\\{RUNTIME_SUFFIX}$").as_str()),
}
}
}
impl Plugin for FarmPluginPolyfill {
fn name(&self) -> &str {
"FarmPluginPolyfill"
}
fn priority(&self) -> i32 {
i32::MIN
}
fn config(&self, config: &mut Config) -> farmfe_core::error::Result<Option<()>> {
let swc_helpers_find = StringOrRegex::String(SWC_HELPERS_PACKAGE.to_string());
if !config.runtime.swc_helpers_path.is_empty()
&& !config.resolve.alias.iter().any(|a| {
if let StringOrRegex::String(str) = &a.find {
str == SWC_HELPERS_PACKAGE
} else {
false
}
})
{
config.resolve.alias.push(AliasItem {
find: swc_helpers_find,
replacement: config.runtime.swc_helpers_path.clone(),
});
}
Ok(Some(()))
}
fn process_module(
&self,
param: &mut farmfe_core::plugin::PluginProcessModuleHookParam,
context: &std::sync::Arc<farmfe_core::context::CompilationContext>,
) -> farmfe_core::error::Result<Option<()>> {
if !param.module_type.is_script() {
return Ok(None);
}
let relative_path = param.module_id.relative_path();
if !self.include.iter().any(|r| r.is_match(relative_path))
&& self.exclude.iter().any(|r| r.is_match(relative_path))
{
return Ok(None);
}
if self
.enforce_exclude
.iter()
.any(|r| r.is_match(relative_path))
{
return Ok(None);
}
let cm = context.meta.get_module_source_map(param.module_id);
let globals = context.meta.get_globals(¶m.module_id);
try_with(cm, globals.value(), || {
let unresolved_mark = Mark::from_u32(param.meta.as_script().unresolved_mark);
let top_level_mark = Mark::from_u32(param.meta.as_script().top_level_mark);
let ast = param.meta.as_script_mut().take_ast();
let is_runtime_module = self.farm_runtime_regex.is_match(relative_path);
let mut final_ast = if ast
.body
.iter()
.all(|item| !matches!(item, farmfe_core::swc_ecma_ast::ModuleItem::ModuleDecl(_)))
&& !is_runtime_module
{
Program::Script(Script {
span: ast.span,
body: ast
.body
.into_iter()
.map(|item| item.expect_stmt())
.collect(),
shebang: ast.shebang,
})
} else {
Program::Module(ast)
};
let comments: SingleThreadedComments = param.meta.as_script().comments.clone().into();
if is_runtime_module && matches!(context.config.mode, FarmMode::Production) {
final_ast.mutate(&mut transform_from_es_version(
unresolved_mark,
Some(&comments),
EsVersion::Es5,
self.assumptions,
false,
));
} else if !is_runtime_module {
final_ast.mutate(&mut transform_from_env(
unresolved_mark,
Some(&comments),
EnvConfig::from(self.config.clone()),
self.assumptions,
));
}
final_ast.visit_mut_with(&mut inject_helpers(unresolved_mark));
final_ast.visit_mut_with(&mut hygiene_with_config(HygieneConfig {
top_level_mark,
..Default::default()
}));
final_ast.visit_mut_with(&mut fixer(Some(&comments)));
let module_ast = match final_ast {
Program::Script(script_ast) => Module {
span: script_ast.span,
body: script_ast
.body
.into_iter()
.map(|item| ModuleItem::Stmt(item))
.collect(),
shebang: script_ast.shebang,
},
Program::Module(module_ast) => module_ast,
};
param.meta.as_script_mut().set_ast(module_ast);
})?;
let (unresolved_mark, top_level_mark) = resolve_module_mark(
&mut param.meta.as_script_mut().ast,
param.module_type.is_typescript(),
globals.value(),
);
param.meta.as_script_mut().unresolved_mark = unresolved_mark.as_u32();
param.meta.as_script_mut().top_level_mark = top_level_mark.as_u32();
Ok(Some(()))
}
}