feat(config): Make all configuration overridable (#4575)

This commit is contained in:
Donny/강동윤 2022-05-09 14:38:27 +09:00 committed by GitHub
parent 29811e28ee
commit 7fc9bbccd9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
47 changed files with 842 additions and 724 deletions

View File

@ -93,10 +93,14 @@ jobs:
os: ubuntu-latest os: ubuntu-latest
- crate: better_scoped_tls - crate: better_scoped_tls
os: ubuntu-latest os: ubuntu-latest
- crate: binding_commons
os: ubuntu-latest
- crate: binding_core_node - crate: binding_core_node
os: ubuntu-latest os: ubuntu-latest
- crate: binding_core_wasm - crate: binding_core_wasm
os: ubuntu-latest os: ubuntu-latest
- crate: binding_css_node
os: ubuntu-latest
- crate: enum_kind - crate: enum_kind
os: ubuntu-latest os: ubuntu-latest
- crate: from_variant - crate: from_variant
@ -136,6 +140,10 @@ jobs:
cargo hack check --feature-powerset --no-dev-deps cargo hack check --feature-powerset --no-dev-deps
- crate: swc_common - crate: swc_common
os: windows-latest os: windows-latest
- crate: swc_config
os: ubuntu-latest
- crate: swc_config_macro
os: ubuntu-latest
- crate: swc_css - crate: swc_css
os: ubuntu-latest os: ubuntu-latest
- crate: swc_css_ast - crate: swc_css_ast

45
Cargo.lock generated
View File

@ -60,9 +60,9 @@ dependencies = [
[[package]] [[package]]
name = "anyhow" name = "anyhow"
version = "1.0.56" version = "1.0.57"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4361135be9122e0870de935d7c439aef945b9f9ddd4199a553b5270b49c82a27" checksum = "08f9b8508dccb7687a1d6c4ce66b2b0ecef467c94667de27d8d7fe1f8d2a9cdc"
[[package]] [[package]]
name = "arbitrary" name = "arbitrary"
@ -2553,9 +2553,9 @@ checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3"
[[package]] [[package]]
name = "serde" name = "serde"
version = "1.0.136" version = "1.0.137"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ce31e24b01e1e524df96f1c2fdd054405f8d7376249a5110886fb4b658484789" checksum = "61ea8d54c77f8315140a05f4c7237403bf38b72704d031543aa1d16abbf517d1"
dependencies = [ dependencies = [
"serde_derive", "serde_derive",
] ]
@ -2593,9 +2593,9 @@ dependencies = [
[[package]] [[package]]
name = "serde_derive" name = "serde_derive"
version = "1.0.136" version = "1.0.137"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08597e7152fcd306f41838ed3e37be9eaeed2b61c42e2117266a554fab4662f9" checksum = "1f26faba0c3959972377d3b2d306ee9f71faee9714294e41bb777f83f88578be"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -2892,12 +2892,14 @@ dependencies = [
"pathdiff", "pathdiff",
"rayon", "rayon",
"regex", "regex",
"rustc-hash",
"serde", "serde",
"serde_json", "serde_json",
"sourcemap", "sourcemap",
"swc_atoms", "swc_atoms",
"swc_cached", "swc_cached",
"swc_common", "swc_common",
"swc_config",
"swc_ecma_ast", "swc_ecma_ast",
"swc_ecma_codegen", "swc_ecma_codegen",
"swc_ecma_ext_transforms", "swc_ecma_ext_transforms",
@ -3051,6 +3053,28 @@ dependencies = [
"url", "url",
] ]
[[package]]
name = "swc_config"
version = "0.1.0"
dependencies = [
"anyhow",
"indexmap",
"serde",
"serde_json",
"swc_config_macro",
]
[[package]]
name = "swc_config_macro"
version = "0.1.0"
dependencies = [
"pmutil",
"proc-macro2",
"quote",
"swc_macros_common",
"syn",
]
[[package]] [[package]]
name = "swc_css" name = "swc_css"
version = "0.104.2" version = "0.104.2"
@ -3271,6 +3295,7 @@ dependencies = [
"serde", "serde",
"swc_atoms", "swc_atoms",
"swc_common", "swc_common",
"swc_config",
"swc_ecma_ast", "swc_ecma_ast",
"swc_ecma_codegen", "swc_ecma_codegen",
"swc_ecma_parser", "swc_ecma_parser",
@ -3324,6 +3349,7 @@ dependencies = [
"swc_atoms", "swc_atoms",
"swc_cached", "swc_cached",
"swc_common", "swc_common",
"swc_config",
"swc_ecma_ast", "swc_ecma_ast",
"swc_ecma_codegen", "swc_ecma_codegen",
"swc_ecma_parser", "swc_ecma_parser",
@ -3555,6 +3581,7 @@ dependencies = [
"indexmap", "indexmap",
"once_cell", "once_cell",
"rayon", "rayon",
"rustc-hash",
"serde_json", "serde_json",
"swc_atoms", "swc_atoms",
"swc_common", "swc_common",
@ -3878,7 +3905,7 @@ dependencies = [
[[package]] [[package]]
name = "swc_macros_common" name = "swc_macros_common"
version = "0.3.4" version = "0.3.5"
dependencies = [ dependencies = [
"pmutil", "pmutil",
"proc-macro2", "proc-macro2",
@ -4053,9 +4080,9 @@ dependencies = [
[[package]] [[package]]
name = "syn" name = "syn"
version = "1.0.86" version = "1.0.92"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a65b3f4ffa0092e9887669db0eae07941f023991ab58ea44da8fe8e2d511c6b" checksum = "7ff7c592601f11445996a06f8ad0c27f094a58857c2f89e97974ab9235b92c52"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",

View File

@ -19,7 +19,7 @@ napi = { version = "2", default-features = false, features = [
"serde-json", "serde-json",
] } ] }
serde = { version = "1", features = ["derive"] } serde = { version = "1", features = ["derive"] }
serde_json = "1" serde_json = { version = "1", features = ["unbounded_depth"] }
swc_node_base = { path = "../swc_node_base" } swc_node_base = { path = "../swc_node_base" }
tracing = { version = "0.1.32", features = ["release_max_level_info"] } tracing = { version = "0.1.32", features = ["release_max_level_info"] }
tracing-subscriber = { version = "0.3.9", features = ["env-filter"] } tracing-subscriber = { version = "0.3.9", features = ["env-filter"] }

View File

@ -115,7 +115,7 @@ impl Task for BundleTask {
.config .config
.options .options
.as_ref() .as_ref()
.map(|v| v.config.minify) .map(|v| v.config.minify.into_bool())
.unwrap_or(false); .unwrap_or(false);
let output = self.swc.print( let output = self.swc.print(

View File

@ -43,7 +43,7 @@ impl Task for PrintTask {
.unwrap_or(SourceMapsConfig::Bool(false)), .unwrap_or(SourceMapsConfig::Bool(false)),
&Default::default(), &Default::default(),
None, None,
options.config.minify, options.config.minify.into_bool(),
None, None,
) )
.convert_err() .convert_err()
@ -100,7 +100,7 @@ pub fn print_sync(program: String, options: Buffer) -> napi::Result<TransformOut
.unwrap_or(SourceMapsConfig::Bool(false)), .unwrap_or(SourceMapsConfig::Bool(false)),
&Default::default(), &Default::default(),
None, None,
options.config.minify, options.config.minify.into_bool(),
None, None,
) )
.convert_err() .convert_err()

View File

@ -48,7 +48,7 @@ impl Task for TransformTask {
try_with( try_with(
self.c.cm.clone(), self.c.cm.clone(),
!options.config.error.filename, !options.config.error.filename.into_bool(),
|handler| { |handler| {
self.c.run(|| match &self.input { self.c.run(|| match &self.input {
Input::Program(ref s) => { Input::Program(ref s) => {
@ -130,25 +130,29 @@ pub fn transform_sync(s: String, is_module: bool, opts: Buffer) -> napi::Result<
options.config.adjust(Path::new(&options.filename)); options.config.adjust(Path::new(&options.filename));
} }
try_with(c.cm.clone(), !options.config.error.filename, |handler| { try_with(
c.run(|| { c.cm.clone(),
if is_module { !options.config.error.filename.into_bool(),
let program: Program = |handler| {
deserialize_json(s.as_str()).context("failed to deserialize Program")?; c.run(|| {
c.process_js(handler, program, &options) if is_module {
} else { let program: Program =
let fm = c.cm.new_source_file( deserialize_json(s.as_str()).context("failed to deserialize Program")?;
if options.filename.is_empty() { c.process_js(handler, program, &options)
FileName::Anon } else {
} else { let fm = c.cm.new_source_file(
FileName::Real(options.filename.clone().into()) if options.filename.is_empty() {
}, FileName::Anon
s, } else {
); FileName::Real(options.filename.clone().into())
c.process_js_file(fm, handler, &options) },
} s,
}) );
}) c.process_js_file(fm, handler, &options)
}
})
},
)
.convert_err() .convert_err()
} }
@ -189,17 +193,21 @@ pub fn transform_file_sync(
options.config.adjust(Path::new(&options.filename)); options.config.adjust(Path::new(&options.filename));
} }
try_with(c.cm.clone(), !options.config.error.filename, |handler| { try_with(
c.run(|| { c.cm.clone(),
if is_module { !options.config.error.filename.into_bool(),
let program: Program = |handler| {
deserialize_json(s.as_str()).context("failed to deserialize Program")?; c.run(|| {
c.process_js(handler, program, &options) if is_module {
} else { let program: Program =
let fm = c.cm.load_file(Path::new(&s)).expect("failed to load file"); deserialize_json(s.as_str()).context("failed to deserialize Program")?;
c.process_js_file(fm, handler, &options) c.process_js(handler, program, &options)
} } else {
}) let fm = c.cm.load_file(Path::new(&s)).expect("failed to load file");
}) c.process_js_file(fm, handler, &options)
}
})
},
)
.convert_err() .convert_err()
} }

View File

@ -114,7 +114,7 @@ pub fn print_sync(s: JsValue, opts: JsValue) -> Result<JsValue, JsValue> {
.unwrap_or(SourceMapsConfig::Bool(false)), .unwrap_or(SourceMapsConfig::Bool(false)),
&Default::default(), &Default::default(),
None, None,
opts.config.minify, opts.config.minify.into(),
None, None,
) )
.context("failed to print code")?; .context("failed to print code")?;

View File

@ -30,7 +30,7 @@ napi-derive = { version = "2", default-features = false, features = [
node_macro_deps = { path = "../node_macro_deps" } node_macro_deps = { path = "../node_macro_deps" }
serde = { version = "1", features = ["derive"] } serde = { version = "1", features = ["derive"] }
serde_json = "1" serde_json = "1"
swc_common = { path = "../swc_common", features = ["perf"] } swc_common = { path = "../swc_common", features = ["perf", "sourcemap"] }
swc_css_ast = { path = "../swc_css_ast" } swc_css_ast = { path = "../swc_css_ast" }
swc_css_codegen = { path = "../swc_css_codegen" } swc_css_codegen = { path = "../swc_css_codegen" }
swc_css_minifier = { path = "../swc_css_minifier" } swc_css_minifier = { path = "../swc_css_minifier" }

View File

@ -30,56 +30,58 @@ anyhow = "1"
base64 = "0.13.0" base64 = "0.13.0"
dashmap = "5.1.0" dashmap = "5.1.0"
either = "1" either = "1"
indexmap = {version = "1", features = ["serde"]} indexmap = { version = "1", features = ["serde"] }
json_comments = "0.2.0" json_comments = "0.2.0"
lru = "0.7.1" lru = "0.7.1"
once_cell = "1.10.0" once_cell = "1.10.0"
parking_lot = "0.12.0" parking_lot = "0.12.0"
pathdiff = "0.2.0" pathdiff = "0.2.0"
regex = "1" regex = "1"
serde = {version = "1", features = ["derive"]} serde = { version = "1", features = ["derive"] }
serde_json = "1" serde_json = "1"
sourcemap = "6" sourcemap = "6"
swc_atoms = {version = "0.2", path = "../swc_atoms"} swc_atoms = { version = "0.2", path = "../swc_atoms" }
swc_cached = {version = "0.1.0", path = "../swc_cached"} swc_cached = { version = "0.1.0", path = "../swc_cached" }
swc_common = {version = "0.17.23", path = "../swc_common", features = [ swc_common = { version = "0.17.23", path = "../swc_common", features = [
"sourcemap", "sourcemap",
"concurrent", "concurrent",
"parking_lot", "parking_lot",
]} ] }
swc_ecma_ast = {version = "0.77.0", path = "../swc_ecma_ast"} swc_config = { version = "0.1.0", path = "../swc_config" }
swc_ecma_codegen = {version = "0.106.0", path = "../swc_ecma_codegen"} swc_ecma_ast = { version = "0.77.0", path = "../swc_ecma_ast" }
swc_ecma_ext_transforms = {version = "0.69.0", path = "../swc_ecma_ext_transforms"} swc_ecma_codegen = { version = "0.106.0", path = "../swc_ecma_codegen" }
swc_ecma_lints = {version = "0.38.0", path = "../swc_ecma_lints"} swc_ecma_ext_transforms = { version = "0.69.0", path = "../swc_ecma_ext_transforms" }
swc_ecma_loader = {version = "0.29.0", path = "../swc_ecma_loader", features = [ swc_ecma_lints = { version = "0.38.0", path = "../swc_ecma_lints" }
swc_ecma_loader = { version = "0.29.0", path = "../swc_ecma_loader", features = [
"cache", "cache",
"node", "node",
"tsc", "tsc",
]} ] }
swc_ecma_minifier = {version = "0.110.0", path = "../swc_ecma_minifier"} swc_ecma_minifier = { version = "0.110.0", path = "../swc_ecma_minifier" }
swc_ecma_parser = {version = "0.103.0", path = "../swc_ecma_parser"} swc_ecma_parser = { version = "0.103.0", path = "../swc_ecma_parser" }
swc_ecma_preset_env = {version = "0.125.0", path = "../swc_ecma_preset_env"} swc_ecma_preset_env = { version = "0.125.0", path = "../swc_ecma_preset_env" }
swc_ecma_transforms = {version = "0.150.0", path = "../swc_ecma_transforms", features = [ swc_ecma_transforms = { version = "0.150.0", path = "../swc_ecma_transforms", features = [
"compat", "compat",
"module", "module",
"optimization", "optimization",
"proposal", "proposal",
"react", "react",
"typescript", "typescript",
]} ] }
swc_ecma_transforms_base = {version = "0.82.0", path = "../swc_ecma_transforms_base"} swc_ecma_transforms_base = { version = "0.82.0", path = "../swc_ecma_transforms_base" }
swc_ecma_transforms_compat = {version = "0.96.0", path = "../swc_ecma_transforms_compat"} swc_ecma_transforms_compat = { version = "0.96.0", path = "../swc_ecma_transforms_compat" }
swc_ecma_transforms_optimization = {version = "0.120.0", path = "../swc_ecma_transforms_optimization"} swc_ecma_transforms_optimization = { version = "0.120.0", path = "../swc_ecma_transforms_optimization" }
swc_ecma_utils = {version = "0.83.0", path = "../swc_ecma_utils"} swc_ecma_utils = { version = "0.83.0", path = "../swc_ecma_utils" }
swc_ecma_visit = {version = "0.63.0", path = "../swc_ecma_visit"} swc_ecma_visit = { version = "0.63.0", path = "../swc_ecma_visit" }
swc_ecmascript = {version = "0.153.0", path = "../swc_ecmascript"} swc_ecmascript = { version = "0.153.0", path = "../swc_ecmascript" }
swc_error_reporters = {version = "0.1.2", path = "../swc_error_reporters"} swc_error_reporters = { version = "0.1.2", path = "../swc_error_reporters" }
swc_node_comments = {version = "0.4.0", path = "../swc_node_comments"} swc_node_comments = { version = "0.4.0", path = "../swc_node_comments" }
swc_plugin_proxy = {version = "0.2.1", path = "../swc_plugin_proxy", optional = true} swc_plugin_proxy = { version = "0.2.1", path = "../swc_plugin_proxy", optional = true }
swc_plugin_runner = {version = "0.54.0", path = "../swc_plugin_runner", optional = true, default-features = false} swc_plugin_runner = { version = "0.54.0", path = "../swc_plugin_runner", optional = true, default-features = false }
swc_timer = {version = "0.5.0", path = "../swc_timer"} swc_timer = { version = "0.5.0", path = "../swc_timer" }
swc_visit = {version = "0.3.0", path = "../swc_visit"} swc_visit = { version = "0.3.0", path = "../swc_visit" }
tracing = "0.1.32" tracing = "0.1.32"
rustc-hash = "1.1.0"
[dependencies.napi-derive] [dependencies.napi-derive]
default-features = false default-features = false
@ -97,11 +99,11 @@ version = "2.0.0"
ansi_term = "0.12" ansi_term = "0.12"
criterion = "0.3" criterion = "0.3"
rayon = "1.5.1" rayon = "1.5.1"
swc_ecma_lints = {version = "0.38.0", path = "../swc_ecma_lints", features = [ swc_ecma_lints = { version = "0.38.0", path = "../swc_ecma_lints", features = [
"non_critical_lints", "non_critical_lints",
]} ] }
swc_node_base = {version = "0.5.0", path = "../swc_node_base"} swc_node_base = { version = "0.5.0", path = "../swc_node_base" }
testing = {version = "0.19.0", path = "../testing"} testing = { version = "0.19.0", path = "../testing" }
walkdir = "2" walkdir = "2"
[[example]] [[example]]

View File

@ -4,7 +4,7 @@ extern crate swc_node_base;
use std::{path::PathBuf, sync::Arc}; use std::{path::PathBuf, sync::Arc};
use criterion::{black_box, criterion_group, criterion_main, Bencher, Criterion}; use criterion::{black_box, criterion_group, criterion_main, Bencher, Criterion};
use swc::{config::JsMinifyOptions, try_with_handler}; use swc::{config::JsMinifyOptions, try_with_handler, BoolOrDataConfig};
use swc_common::{FilePathMapping, SourceMap}; use swc_common::{FilePathMapping, SourceMap};
fn mk() -> swc::Compiler { fn mk() -> swc::Compiler {
@ -35,8 +35,8 @@ fn bench_minify(b: &mut Bencher, filename: &str) {
fm, fm,
handler, handler,
&JsMinifyOptions { &JsMinifyOptions {
compress: true.into(), compress: BoolOrDataConfig::from_bool(true),
mangle: true.into(), mangle: BoolOrDataConfig::from_bool(true),
format: Default::default(), format: Default::default(),
ecma: Default::default(), ecma: Default::default(),
keep_classnames: Default::default(), keep_classnames: Default::default(),

View File

@ -1,7 +1,7 @@
use std::{path::Path, sync::Arc}; use std::{path::Path, sync::Arc};
use anyhow::Context; use anyhow::Context;
use swc::{self, config::JsMinifyOptions, try_with_handler, HandlerOpts}; use swc::{self, config::JsMinifyOptions, try_with_handler, BoolOrDataConfig, HandlerOpts};
use swc_common::SourceMap; use swc_common::SourceMap;
fn main() { fn main() {
@ -23,8 +23,8 @@ fn main() {
fm, fm,
handler, handler,
&JsMinifyOptions { &JsMinifyOptions {
compress: true.into(), compress: BoolOrDataConfig::from_bool(true),
mangle: true.into(), mangle: BoolOrDataConfig::from_bool(true),
format: Default::default(), format: Default::default(),
ecma: Default::default(), ecma: Default::default(),
keep_classnames: Default::default(), keep_classnames: Default::default(),

View File

@ -1,7 +1,8 @@
use std::{cell::RefCell, collections::HashMap, path::PathBuf, rc::Rc, sync::Arc}; use std::{cell::RefCell, path::PathBuf, rc::Rc, sync::Arc};
use compat::{es2015::regenerator, es2020::export_namespace_from}; use compat::{es2015::regenerator, es2020::export_namespace_from};
use either::Either; use either::Either;
use rustc_hash::FxHashMap;
use swc_atoms::JsWord; use swc_atoms::JsWord;
use swc_common::{ use swc_common::{
chain, chain,
@ -21,9 +22,7 @@ use swc_ecma_transforms::{
}; };
use swc_ecma_visit::{as_folder, noop_visit_mut_type, VisitMut}; use swc_ecma_visit::{as_folder, noop_visit_mut_type, VisitMut};
use crate::config::{ use crate::config::{CompiledPaths, GlobalPassOption, JsMinifyOptions, ModuleConfig};
util::BoolOrObject, CompiledPaths, GlobalPassOption, JsMinifyOptions, ModuleConfig,
};
/// Builder is used to create a high performance `Compiler`. /// Builder is used to create a high performance `Compiler`.
pub struct PassBuilder<'a, 'b, P: swc_ecma_visit::Fold> { pub struct PassBuilder<'a, 'b, P: swc_ecma_visit::Fold> {
@ -124,7 +123,7 @@ impl<'a, 'b, P: swc_ecma_visit::Fold> PassBuilder<'a, 'b, P> {
pub fn const_modules( pub fn const_modules(
self, self,
globals: HashMap<JsWord, HashMap<JsWord, String>>, globals: FxHashMap<JsWord, FxHashMap<JsWord, String>>,
) -> PassBuilder<'a, 'b, impl swc_ecma_visit::Fold> { ) -> PassBuilder<'a, 'b, impl swc_ecma_visit::Fold> {
let cm = self.cm.clone(); let cm = self.cm.clone();
self.then(const_modules(cm, globals)) self.then(const_modules(cm, globals))
@ -296,7 +295,7 @@ impl<'a, 'b, P: swc_ecma_visit::Fold> PassBuilder<'a, 'b, P> {
let is_mangler_enabled = self let is_mangler_enabled = self
.minify .minify
.as_ref() .as_ref()
.map(|v| matches!(v.mangle, BoolOrObject::Bool(true) | BoolOrObject::Obj(_))) .map(|v| v.mangle.is_obj() || v.mangle.is_true())
.unwrap_or(false); .unwrap_or(false);
let module_scope = Rc::new(RefCell::new(Scope::default())); let module_scope = Rc::new(RefCell::new(Scope::default()));
@ -351,17 +350,30 @@ impl VisitMut for MinifierPass {
fn visit_mut_module(&mut self, m: &mut Module) { fn visit_mut_module(&mut self, m: &mut Module) {
if let Some(options) = &self.options { if let Some(options) = &self.options {
let opts = MinifyOptions { let opts = MinifyOptions {
compress: options.compress.clone().into_obj().map(|mut v| { compress: options
if v.const_to_let.is_none() { .compress
v.const_to_let = Some(true); .clone()
} .unwrap_as_option(|default| match default {
if v.toplevel.is_none() { Some(true) => Some(Default::default()),
v.toplevel = Some(TerserTopLevelOptions::Bool(true)); _ => None,
} })
.map(|mut v| {
if v.const_to_let.is_none() {
v.const_to_let = Some(true);
}
if v.toplevel.is_none() {
v.toplevel = Some(TerserTopLevelOptions::Bool(true));
}
v.into_config(self.cm.clone()) v.into_config(self.cm.clone())
}), }),
mangle: options.mangle.clone().into_obj(), mangle: options
.mangle
.clone()
.unwrap_as_option(|default| match default {
Some(true) => Some(Default::default()),
_ => None,
}),
..Default::default() ..Default::default()
}; };

View File

@ -3,7 +3,6 @@ use std::{
cell::RefCell, cell::RefCell,
collections::{HashMap, HashSet}, collections::{HashMap, HashSet},
env, fmt, env, fmt,
hash::BuildHasher,
path::{Path, PathBuf}, path::{Path, PathBuf},
rc::Rc as RustRc, rc::Rc as RustRc,
sync::Arc, sync::Arc,
@ -15,6 +14,7 @@ use dashmap::DashMap;
use either::Either; use either::Either;
use indexmap::IndexMap; use indexmap::IndexMap;
use once_cell::sync::Lazy; use once_cell::sync::Lazy;
use rustc_hash::FxHashMap;
use serde::{ use serde::{
de::{Unexpected, Visitor}, de::{Unexpected, Visitor},
Deserialize, Deserializer, Serialize, Serializer, Deserialize, Deserializer, Serialize, Serializer,
@ -28,6 +28,10 @@ use swc_common::{
errors::Handler, errors::Handler,
FileName, Mark, SourceMap, SyntaxContext, FileName, Mark, SourceMap, SyntaxContext,
}; };
use swc_config::{
config_types::{BoolConfig, BoolOr, BoolOrDataConfig},
merge::Merge,
};
use swc_ecma_ast::{EsVersion, Expr, Program}; use swc_ecma_ast::{EsVersion, Expr, Program};
use swc_ecma_ext_transforms::jest; use swc_ecma_ext_transforms::jest;
use swc_ecma_lints::{ use swc_ecma_lints::{
@ -40,7 +44,7 @@ use swc_ecma_loader::{
}; };
use swc_ecma_minifier::option::{ use swc_ecma_minifier::option::{
terser::{TerserCompressorOptions, TerserEcmaVersion, TerserTopLevelOptions}, terser::{TerserCompressorOptions, TerserEcmaVersion, TerserTopLevelOptions},
MangleOptions, ManglePropertiesOptions, MangleOptions,
}; };
#[allow(deprecated)] #[allow(deprecated)]
pub use swc_ecma_parser::JscTarget; pub use swc_ecma_parser::JscTarget;
@ -61,7 +65,6 @@ use swc_ecma_transforms_compat::es2015::regenerator;
use swc_ecma_transforms_optimization::{inline_globals2, GlobalExprMap}; use swc_ecma_transforms_optimization::{inline_globals2, GlobalExprMap};
use swc_ecma_visit::{Fold, VisitMutWith}; use swc_ecma_visit::{Fold, VisitMutWith};
use self::util::BoolOrObject;
use crate::{ use crate::{
builder::PassBuilder, builder::PassBuilder,
dropped_comments_preserver::dropped_comments_preserver, dropped_comments_preserver::dropped_comments_preserver,
@ -71,7 +74,6 @@ use crate::{
#[cfg(test)] #[cfg(test)]
mod tests; mod tests;
pub mod util;
#[derive(Clone, Debug, Copy)] #[derive(Clone, Debug, Copy)]
pub enum IsModule { pub enum IsModule {
@ -269,7 +271,6 @@ impl Options {
output_path: Option<&Path>, output_path: Option<&Path>,
source_file_name: Option<String>, source_file_name: Option<String>,
handler: &Handler, handler: &Handler,
is_module: IsModule,
config: Option<Config>, config: Option<Config>,
comments: Option<&'a SingleThreadedComments>, comments: Option<&'a SingleThreadedComments>,
custom_before_pass: impl FnOnce(&Program) -> P, custom_before_pass: impl FnOnce(&Program) -> P,
@ -277,11 +278,12 @@ impl Options {
where where
P: 'a + swc_ecma_visit::Fold, P: 'a + swc_ecma_visit::Fold,
{ {
let mut config = config.unwrap_or_default(); let mut cfg = self.config.clone();
config.merge(&self.config); cfg.merge(config.unwrap_or_default());
let is_module = self.is_module;
let mut source_maps = self.source_maps.clone(); let mut source_maps = self.source_maps.clone();
source_maps.merge(&config.source_maps); source_maps.merge(cfg.source_maps.clone());
let JscConfig { let JscConfig {
assumptions, assumptions,
@ -298,7 +300,11 @@ impl Options {
lints, lints,
preserve_all_comments, preserve_all_comments,
.. ..
} = config.jsc; } = cfg.jsc;
let loose = loose.into_bool();
let preserve_all_comments = preserve_all_comments.into_bool();
let keep_class_names = keep_class_names.into_bool();
let external_helpers = external_helpers.into_bool();
let mut assumptions = assumptions.unwrap_or_else(|| { let mut assumptions = assumptions.unwrap_or_else(|| {
if loose { if loose {
@ -338,7 +344,10 @@ impl Options {
js_minify = js_minify.map(|c| { js_minify = js_minify.map(|c| {
let compress = c let compress = c
.compress .compress
.into_obj() .unwrap_as_option(|default| match default {
Some(true) => Some(Default::default()),
_ => None,
})
.map(|mut c| { .map(|mut c| {
if c.toplevel.is_none() { if c.toplevel.is_none() {
c.toplevel = Some(TerserTopLevelOptions::Bool(true)); c.toplevel = Some(TerserTopLevelOptions::Bool(true));
@ -346,19 +355,22 @@ impl Options {
c c
}) })
.map(BoolOrObject::Obj) .map(BoolOrDataConfig::from_obj)
.unwrap_or(BoolOrObject::Bool(false)); .unwrap_or_else(|| BoolOrDataConfig::from_bool(false));
let mangle = c let mangle = c
.mangle .mangle
.into_obj() .unwrap_as_option(|default| match default {
Some(true) => Some(Default::default()),
_ => None,
})
.map(|mut c| { .map(|mut c| {
c.top_level = true; c.top_level = true;
c c
}) })
.map(BoolOrObject::Obj) .map(BoolOrDataConfig::from_obj)
.unwrap_or(BoolOrObject::Bool(false)); .unwrap_or_else(|| BoolOrDataConfig::from_bool(false));
JsMinifyOptions { JsMinifyOptions {
compress, compress,
@ -371,9 +383,21 @@ impl Options {
let regenerator = transform.regenerator.clone(); let regenerator = transform.regenerator.clone();
let preserve_comments = if preserve_all_comments { let preserve_comments = if preserve_all_comments {
Some(BoolOrObject::from(true)) BoolOr::Bool(true)
} else { } else {
js_minify.as_ref().map(|v| v.format.comments.clone()) js_minify
.as_ref()
.map(|v| match v.format.comments.clone().into_inner() {
Some(v) => v,
None => BoolOr::Bool(false),
})
.unwrap_or_else(|| {
BoolOr::Data(if cfg.minify.into_bool() {
JsMinifyCommentOption::PreserveSomeComments
} else {
JsMinifyCommentOption::PreserveAllComments
})
})
}; };
if syntax.typescript() { if syntax.typescript() {
@ -397,7 +421,10 @@ impl Options {
} }
}; };
let enable_simplifier = optimizer.as_ref().map(|v| v.simplify).unwrap_or_default(); let enable_simplifier = optimizer
.as_ref()
.map(|v| v.simplify.into_bool())
.unwrap_or_default();
let optimization = { let optimization = {
if let Some(opts) = optimizer.and_then(|o| o.globals) { if let Some(opts) = optimizer.and_then(|o| o.globals) {
@ -439,18 +466,18 @@ impl Options {
Some(hygiene::Config { keep_class_names }) Some(hygiene::Config { keep_class_names })
}) })
.fixer(!self.disable_fixer) .fixer(!self.disable_fixer)
.preset_env(config.env) .preset_env(cfg.env)
.regenerator(regenerator) .regenerator(regenerator)
.finalize( .finalize(
base_url, base_url,
paths.into_iter().collect(), paths.into_iter().collect(),
base, base,
syntax, syntax,
config.module, cfg.module,
comments, comments,
); );
let keep_import_assertions = experimental.keep_import_assertions; let keep_import_assertions = experimental.keep_import_assertions.into_bool();
// Embedded runtime plugin target, based on assumption we have // Embedded runtime plugin target, based on assumption we have
// 1. filesystem access for the cache // 1. filesystem access for the cache
@ -570,15 +597,15 @@ impl Options {
Ok(BuiltInput { Ok(BuiltInput {
program, program,
minify: config.minify, minify: cfg.minify.into_bool(),
pass, pass,
external_helpers, external_helpers,
syntax, syntax,
target: es_version, target: es_version,
is_module, is_module,
source_maps: source_maps.unwrap_or(SourceMapsConfig::Bool(false)), source_maps: source_maps.unwrap_or(SourceMapsConfig::Bool(false)),
inline_sources_content: config.inline_sources_content, inline_sources_content: cfg.inline_sources_content.into_bool(),
input_source_map: config.input_source_map.clone(), input_source_map: cfg.input_source_map.clone().unwrap_or_default(),
output_path: output_path.map(|v| v.to_path_buf()), output_path: output_path.map(|v| v.to_path_buf()),
source_file_name, source_file_name,
comments: comments.cloned(), comments: comments.cloned(),
@ -648,17 +675,8 @@ impl Default for Rc {
exclude: Some(FileMatcher::Regex("\\.tsx?$".into())), exclude: Some(FileMatcher::Regex("\\.tsx?$".into())),
jsc: JscConfig { jsc: JscConfig {
syntax: Some(Default::default()), syntax: Some(Default::default()),
transform: None,
external_helpers: false,
target: Default::default(),
loose: false,
keep_class_names: false,
..Default::default() ..Default::default()
}, },
module: None,
minify: false,
source_maps: None,
input_source_map: InputSourceMap::default(),
..Default::default() ..Default::default()
}, },
Config { Config {
@ -670,17 +688,8 @@ impl Default for Rc {
tsx: true, tsx: true,
..Default::default() ..Default::default()
})), })),
transform: None,
external_helpers: false,
target: Default::default(),
loose: false,
keep_class_names: false,
..Default::default() ..Default::default()
}, },
module: None,
minify: false,
source_maps: None,
input_source_map: InputSourceMap::default(),
..Default::default() ..Default::default()
}, },
Config { Config {
@ -692,17 +701,8 @@ impl Default for Rc {
tsx: false, tsx: false,
..Default::default() ..Default::default()
})), })),
transform: None,
external_helpers: false,
target: Default::default(),
loose: false,
keep_class_names: false,
..Default::default() ..Default::default()
}, },
module: None,
minify: false,
source_maps: None,
input_source_map: InputSourceMap::default(),
..Default::default() ..Default::default()
}, },
]) ])
@ -747,7 +747,7 @@ impl Rc {
} }
/// A single object in the `.swcrc` file /// A single object in the `.swcrc` file
#[derive(Debug, Default, Clone, Deserialize)] #[derive(Debug, Default, Clone, Deserialize, Merge)]
#[serde(deny_unknown_fields, rename_all = "camelCase")] #[serde(deny_unknown_fields, rename_all = "camelCase")]
pub struct Config { pub struct Config {
#[serde(default)] #[serde(default)]
@ -766,17 +766,17 @@ pub struct Config {
pub module: Option<ModuleConfig>, pub module: Option<ModuleConfig>,
#[serde(default)] #[serde(default)]
pub minify: bool, pub minify: BoolConfig<false>,
#[serde(default)] #[serde(default)]
pub input_source_map: InputSourceMap, pub input_source_map: Option<InputSourceMap>,
/// Possible values are: `'inline'`, `true`, `false`. /// Possible values are: `'inline'`, `true`, `false`.
#[serde(default)] #[serde(default)]
pub source_maps: Option<SourceMapsConfig>, pub source_maps: Option<SourceMapsConfig>,
#[serde(default = "true_by_default")] #[serde(default)]
pub inline_sources_content: bool, pub inline_sources_content: BoolConfig<true>,
#[serde(default)] #[serde(default)]
pub error: ErrorConfig, pub error: ErrorConfig,
@ -790,10 +790,10 @@ pub struct Config {
#[serde(deny_unknown_fields, rename_all = "camelCase")] #[serde(deny_unknown_fields, rename_all = "camelCase")]
pub struct JsMinifyOptions { pub struct JsMinifyOptions {
#[serde(default)] #[serde(default)]
pub compress: BoolOrObject<TerserCompressorOptions>, pub compress: BoolOrDataConfig<TerserCompressorOptions>,
#[serde(default)] #[serde(default)]
pub mangle: BoolOrObject<MangleOptions>, pub mangle: BoolOrDataConfig<MangleOptions>,
#[serde(default)] #[serde(default)]
pub format: JsMinifyFormatOptions, pub format: JsMinifyFormatOptions,
@ -817,7 +817,7 @@ pub struct JsMinifyOptions {
pub toplevel: bool, pub toplevel: bool,
#[serde(default)] #[serde(default)]
pub source_map: BoolOrObject<TerserSourceMapOption>, pub source_map: BoolOrDataConfig<TerserSourceMapOption>,
#[serde(default)] #[serde(default)]
pub output_path: Option<String>, pub output_path: Option<String>,
@ -864,7 +864,7 @@ pub struct JsMinifyFormatOptions {
pub braces: bool, pub braces: bool,
#[serde(default)] #[serde(default)]
pub comments: BoolOrObject<JsMinifyCommentOption>, pub comments: BoolOrDataConfig<JsMinifyCommentOption>,
/// Not implemented yet. /// Not implemented yet.
#[serde(default)] #[serde(default)]
@ -872,7 +872,7 @@ pub struct JsMinifyFormatOptions {
/// Not implemented yet. /// Not implemented yet.
#[serde(default, alias = "indent_level")] #[serde(default, alias = "indent_level")]
pub indent_level: usize, pub indent_level: Option<usize>,
/// Not implemented yet. /// Not implemented yet.
#[serde(default, alias = "indent_start")] #[serde(default, alias = "indent_start")]
@ -892,7 +892,7 @@ pub struct JsMinifyFormatOptions {
/// Not implemented yet. /// Not implemented yet.
#[serde(default, alias = "max_line_len")] #[serde(default, alias = "max_line_len")]
pub max_line_len: BoolOrObject<usize>, pub max_line_len: usize,
/// Not implemented yet. /// Not implemented yet.
#[serde(default)] #[serde(default)]
@ -1054,13 +1054,13 @@ pub struct BuiltInput<P: swc_ecma_visit::Fold> {
pub source_file_name: Option<String>, pub source_file_name: Option<String>,
pub comments: Option<SingleThreadedComments>, pub comments: Option<SingleThreadedComments>,
pub preserve_comments: Option<BoolOrObject<JsMinifyCommentOption>>, pub preserve_comments: BoolOr<JsMinifyCommentOption>,
pub inline_sources_content: bool, pub inline_sources_content: bool,
} }
/// `jsc` in `.swcrc`. /// `jsc` in `.swcrc`.
#[derive(Debug, Default, Clone, Serialize, Deserialize)] #[derive(Debug, Default, Clone, Serialize, Deserialize, Merge)]
#[serde(deny_unknown_fields, rename_all = "camelCase")] #[serde(deny_unknown_fields, rename_all = "camelCase")]
pub struct JscConfig { pub struct JscConfig {
#[serde(default)] #[serde(default)]
@ -1073,16 +1073,16 @@ pub struct JscConfig {
pub transform: Option<TransformConfig>, pub transform: Option<TransformConfig>,
#[serde(default)] #[serde(default)]
pub external_helpers: bool, pub external_helpers: BoolConfig<false>,
#[serde(default)] #[serde(default)]
pub target: Option<EsVersion>, pub target: Option<EsVersion>,
#[serde(default)] #[serde(default)]
pub loose: bool, pub loose: BoolConfig<false>,
#[serde(default)] #[serde(default)]
pub keep_class_names: bool, pub keep_class_names: BoolConfig<false>,
#[serde(default)] #[serde(default)]
pub base_url: PathBuf, pub base_url: PathBuf,
@ -1100,11 +1100,11 @@ pub struct JscConfig {
pub lints: LintConfig, pub lints: LintConfig,
#[serde(default)] #[serde(default)]
pub preserve_all_comments: bool, pub preserve_all_comments: BoolConfig<false>,
} }
/// `jsc.experimental` in `.swcrc` /// `jsc.experimental` in `.swcrc`
#[derive(Debug, Default, Clone, Serialize, Deserialize)] #[derive(Debug, Default, Clone, Serialize, Deserialize, Merge)]
#[serde(deny_unknown_fields, rename_all = "camelCase")] #[serde(deny_unknown_fields, rename_all = "camelCase")]
pub struct JscExperimental { pub struct JscExperimental {
/// This requires cargo feature `plugin`. /// This requires cargo feature `plugin`.
@ -1112,7 +1112,7 @@ pub struct JscExperimental {
pub plugins: Option<Vec<PluginConfig>>, pub plugins: Option<Vec<PluginConfig>>,
/// If true, keeps import assertions in the output. /// If true, keeps import assertions in the output.
#[serde(default)] #[serde(default)]
pub keep_import_assertions: bool, pub keep_import_assertions: BoolConfig<false>,
/// Location where swc may stores its intermediate cache. /// Location where swc may stores its intermediate cache.
/// Currently this is only being used for wasm plugin's bytecache. /// Currently this is only being used for wasm plugin's bytecache.
/// Path should be absolute directory, which will be created if not exist. /// Path should be absolute directory, which will be created if not exist.
@ -1122,19 +1122,6 @@ pub struct JscExperimental {
pub cache_root: Option<String>, pub cache_root: Option<String>,
} }
impl Merge for JscExperimental {
fn merge(&mut self, from: &Self) {
if self.plugins.is_none() {
self.plugins = from.plugins.clone();
}
if self.cache_root.is_none() {
self.cache_root = from.cache_root.clone();
}
self.keep_import_assertions |= from.keep_import_assertions;
}
}
/// `paths` section of `tsconfig.json`. /// `paths` section of `tsconfig.json`.
pub type Paths = IndexMap<String, Vec<String>, ahash::RandomState>; pub type Paths = IndexMap<String, Vec<String>, ahash::RandomState>;
pub(crate) type CompiledPaths = Vec<(String, Vec<String>)>; pub(crate) type CompiledPaths = Vec<(String, Vec<String>)>;
@ -1296,21 +1283,21 @@ pub struct HiddenTransformConfig {
pub jest: bool, pub jest: bool,
} }
#[derive(Debug, Default, Clone, Serialize, Deserialize)] #[derive(Debug, Default, Clone, Serialize, Deserialize, Merge)]
#[serde(deny_unknown_fields, rename_all = "camelCase")] #[serde(deny_unknown_fields, rename_all = "camelCase")]
pub struct ConstModulesConfig { pub struct ConstModulesConfig {
#[serde(default)] #[serde(default)]
pub globals: HashMap<JsWord, HashMap<JsWord, String>>, pub globals: FxHashMap<JsWord, FxHashMap<JsWord, String>>,
} }
#[derive(Debug, Default, Clone, Serialize, Deserialize)] #[derive(Debug, Default, Clone, Serialize, Deserialize, Merge)]
#[serde(deny_unknown_fields, rename_all = "camelCase")] #[serde(deny_unknown_fields, rename_all = "camelCase")]
pub struct OptimizerConfig { pub struct OptimizerConfig {
#[serde(default)] #[serde(default)]
pub globals: Option<GlobalPassOption>, pub globals: Option<GlobalPassOption>,
#[serde(default = "true_by_default")] #[serde(default)]
pub simplify: bool, pub simplify: BoolConfig<true>,
#[serde(default)] #[serde(default)]
pub jsonify: Option<JsonifyOption>, pub jsonify: Option<JsonifyOption>,
@ -1327,15 +1314,10 @@ fn default_jsonify_min_cost() -> usize {
1024 1024
} }
#[derive(Debug, Clone, Copy, Serialize, Deserialize)] #[derive(Debug, Default, Clone, Copy, Serialize, Deserialize, Merge)]
#[serde(deny_unknown_fields, rename_all = "camelCase")] #[serde(deny_unknown_fields, rename_all = "camelCase")]
pub struct ErrorConfig { pub struct ErrorConfig {
pub filename: bool, pub filename: BoolConfig<true>,
}
impl Default for ErrorConfig {
fn default() -> Self {
ErrorConfig { filename: true }
}
} }
#[derive(Debug, Default, Clone, Serialize, Deserialize)] #[derive(Debug, Default, Clone, Serialize, Deserialize)]
@ -1538,284 +1520,6 @@ fn default_env_name() -> String {
} }
} }
pub trait Merge {
/// Apply overrides from `from`
fn merge(&mut self, from: &Self);
}
impl<T: Clone> Merge for Option<T>
where
T: Merge,
{
fn merge(&mut self, from: &Option<T>) {
if let Some(ref from) = *from {
match *self {
Some(ref mut v) => v.merge(from),
None => *self = Some(from.clone()),
}
}
}
}
impl Merge for Config {
fn merge(&mut self, from: &Self) {
self.jsc.merge(&from.jsc);
self.module.merge(&from.module);
self.minify.merge(&from.minify);
self.env.merge(&from.env);
self.source_maps.merge(&from.source_maps);
self.input_source_map.merge(&from.input_source_map);
self.inline_sources_content
.merge(&from.inline_sources_content);
}
}
impl Merge for JsMinifyOptions {
fn merge(&mut self, from: &Self) {
self.compress.merge(&from.compress);
self.mangle.merge(&from.mangle);
self.format.merge(&from.format);
self.ecma.merge(&from.ecma);
self.keep_classnames |= from.keep_classnames;
self.keep_fnames |= from.keep_fnames;
self.safari10 |= from.safari10;
self.toplevel |= from.toplevel;
self.inline_sources_content |= from.inline_sources_content;
}
}
impl Merge for TerserCompressorOptions {
fn merge(&mut self, from: &Self) {
self.defaults |= from.defaults;
// TODO
}
}
impl Merge for MangleOptions {
fn merge(&mut self, from: &Self) {
self.props.merge(&from.props);
self.top_level |= from.top_level;
self.keep_class_names |= from.keep_class_names;
self.keep_fn_names |= from.keep_fn_names;
self.keep_private_props |= from.keep_private_props;
self.safari10 |= from.safari10;
}
}
impl Merge for JsMinifyFormatOptions {
fn merge(&mut self, from: &Self) {
self.comments.merge(&from.comments);
}
}
impl Merge for JsMinifyCommentOption {
fn merge(&mut self, _: &Self) {}
}
impl Merge for ManglePropertiesOptions {
fn merge(&mut self, from: &Self) {
self.undeclared |= from.undeclared;
}
}
impl Merge for TerserEcmaVersion {
fn merge(&mut self, from: &Self) {
*self = from.clone();
}
}
impl Merge for SourceMapsConfig {
fn merge(&mut self, _: &Self) {}
}
impl Merge for InputSourceMap {
fn merge(&mut self, r: &Self) {
if let InputSourceMap::Bool(false) = *self {
*self = r.clone();
}
}
}
impl Merge for swc_ecma_preset_env::Config {
fn merge(&mut self, from: &Self) {
*self = from.clone();
}
}
impl Merge for JscConfig {
fn merge(&mut self, from: &Self) {
self.assumptions.merge(&from.assumptions);
self.syntax.merge(&from.syntax);
self.transform.merge(&from.transform);
self.external_helpers.merge(&from.external_helpers);
self.target.merge(&from.target);
self.loose.merge(&from.loose);
self.keep_class_names.merge(&from.keep_class_names);
self.paths.merge(&from.paths);
self.minify.merge(&from.minify);
self.experimental.merge(&from.experimental);
self.preserve_all_comments
.merge(&from.preserve_all_comments)
}
}
impl<K, V, S> Merge for IndexMap<K, V, S>
where
K: Clone + Eq + std::hash::Hash,
V: Clone,
S: Clone + BuildHasher,
{
fn merge(&mut self, from: &Self) {
if self.is_empty() {
*self = (*from).clone();
}
}
}
impl<K, V, S> Merge for HashMap<K, V, S>
where
K: Clone + Eq + std::hash::Hash,
V: Clone,
S: Clone + BuildHasher,
{
fn merge(&mut self, from: &Self) {
if self.is_empty() {
*self = (*from).clone();
} else {
for (k, v) in from {
self.entry(k.clone()).or_insert_with(|| v.clone());
}
}
}
}
impl Merge for EsVersion {
fn merge(&mut self, from: &Self) {
if *self < *from {
*self = *from
}
}
}
impl Merge for Option<ModuleConfig> {
fn merge(&mut self, from: &Self) {
if let Some(ref c2) = *from {
*self = Some(c2.clone())
}
}
}
impl Merge for bool {
fn merge(&mut self, from: &Self) {
*self |= *from
}
}
impl Merge for Syntax {
fn merge(&mut self, from: &Self) {
match (&mut *self, from) {
(Syntax::Es(a), Syntax::Es(b)) => {
a.merge(b);
}
(Syntax::Typescript(a), Syntax::Typescript(b)) => {
a.merge(b);
}
_ => {
*self = *from;
}
}
}
}
impl Merge for swc_ecma_parser::EsConfig {
fn merge(&mut self, from: &Self) {
self.jsx |= from.jsx;
self.fn_bind |= from.fn_bind;
self.decorators |= from.decorators;
self.decorators_before_export |= from.decorators_before_export;
self.export_default_from |= from.export_default_from;
self.import_assertions |= from.import_assertions;
self.private_in_object |= from.private_in_object;
}
}
impl Merge for swc_ecma_parser::TsConfig {
fn merge(&mut self, from: &Self) {
self.tsx |= from.tsx;
self.decorators |= from.decorators;
}
}
impl Merge for Assumptions {
fn merge(&mut self, from: &Self) {
self.array_like_is_iterable |= from.array_like_is_iterable;
self.constant_reexports |= from.constant_reexports;
self.constant_super |= from.constant_super;
self.enumerable_module_meta |= from.enumerable_module_meta;
self.ignore_function_length |= from.ignore_function_length;
self.ignore_function_name |= from.ignore_function_name;
self.ignore_to_primitive_hint |= from.ignore_to_primitive_hint;
self.iterable_is_array |= from.iterable_is_array;
self.mutable_template_object |= from.mutable_template_object;
self.no_class_calls |= from.no_class_calls;
self.no_document_all |= from.no_document_all;
self.no_incomplete_ns_import_detection |= from.no_incomplete_ns_import_detection;
self.no_new_arrows |= from.no_new_arrows;
self.object_rest_no_symbols |= from.object_rest_no_symbols;
self.private_fields_as_properties |= from.private_fields_as_properties;
self.pure_getters |= from.pure_getters;
self.set_class_methods |= from.set_class_methods;
self.set_computed_properties |= from.set_computed_properties;
self.set_public_class_fields |= from.set_public_class_fields;
self.set_spread_properties |= from.set_spread_properties;
self.skip_for_of_iterator_closing |= from.skip_for_of_iterator_closing;
self.super_is_callable_constructor |= from.super_is_callable_constructor;
self.ts_enum_is_readonly |= from.ts_enum_is_readonly;
}
}
impl Merge for TransformConfig {
fn merge(&mut self, from: &Self) {
self.optimizer.merge(&from.optimizer);
self.const_modules.merge(&from.const_modules);
self.react.merge(&from.react);
self.hidden.merge(&from.hidden);
}
}
impl Merge for OptimizerConfig {
fn merge(&mut self, from: &Self) {
self.globals.merge(&from.globals)
}
}
impl Merge for GlobalPassOption {
fn merge(&mut self, from: &Self) {
*self = from.clone();
}
}
impl Merge for react::Options {
fn merge(&mut self, from: &Self) {
if *from != react::Options::default() {
*self = from.clone();
}
}
}
impl Merge for ConstModulesConfig {
fn merge(&mut self, from: &Self) {
*self = from.clone()
}
}
impl Merge for HiddenTransformConfig {
fn merge(&mut self, from: &Self) {
self.jest |= from.jest;
}
}
fn build_resolver(base_url: PathBuf, paths: CompiledPaths) -> Box<SwcImportResolver> { fn build_resolver(base_url: PathBuf, paths: CompiledPaths) -> Box<SwcImportResolver> {
static CACHE: Lazy<DashMap<(PathBuf, CompiledPaths), SwcImportResolver, ahash::RandomState>> = static CACHE: Lazy<DashMap<(PathBuf, CompiledPaths), SwcImportResolver, ahash::RandomState>> =
Lazy::new(Default::default); Lazy::new(Default::default);

View File

@ -1,102 +0,0 @@
use serde::{Deserialize, Serialize};
use super::Merge;
/// Note: `{}` (empty object) is treated as `true`.
#[derive(Clone, Serialize, Debug)]
#[serde(untagged)]
pub enum BoolOrObject<T> {
Bool(bool),
Obj(T),
}
impl<T> From<bool> for BoolOrObject<T> {
fn from(v: bool) -> Self {
BoolOrObject::Bool(v)
}
}
impl<T> Default for BoolOrObject<T> {
fn default() -> Self {
Self::Bool(false)
}
}
impl<T> BoolOrObject<T> {
pub fn into_obj(self) -> Option<T>
where
T: Default,
{
match self {
BoolOrObject::Bool(true) => Some(Default::default()),
BoolOrObject::Bool(false) => None,
BoolOrObject::Obj(o) => Some(o),
}
}
}
impl<T> Merge for BoolOrObject<T>
where
T: Clone + Merge + Default,
{
fn merge(&mut self, from: &Self) {
match self {
BoolOrObject::Bool(l) => match from {
BoolOrObject::Bool(r) => {
*l |= *r;
}
BoolOrObject::Obj(_) => *self = from.clone(),
},
BoolOrObject::Obj(o) => match from {
BoolOrObject::Bool(r) => {
if *r {
o.merge(&Default::default())
}
}
BoolOrObject::Obj(r) => o.merge(r),
},
}
}
}
impl<'de, T> Deserialize<'de> for BoolOrObject<T>
where
T: Deserialize<'de>,
{
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
#[derive(Deserialize)]
#[serde(untagged)]
enum Deser<T> {
Bool(bool),
Obj(T),
EmptyObject(EmptyStruct),
}
#[derive(Deserialize)]
#[serde(deny_unknown_fields)]
struct EmptyStruct {}
let content = swc_common::private::serde::de::Content::deserialize(deserializer)?;
let deserializer =
swc_common::private::serde::de::ContentRefDeserializer::<D::Error>::new(&content);
let res = Deser::deserialize(deserializer);
match res {
Ok(v) => Ok(match v {
Deser::Bool(v) => BoolOrObject::Bool(v),
Deser::Obj(v) => BoolOrObject::Obj(v),
Deser::EmptyObject(_) => BoolOrObject::Bool(true),
}),
Err(..) => {
let d =
swc_common::private::serde::de::ContentDeserializer::<D::Error>::new(content);
Ok(BoolOrObject::Obj(T::deserialize(d)?))
}
}
}
}

View File

@ -120,8 +120,8 @@ use std::{
use anyhow::{bail, Context, Error}; use anyhow::{bail, Context, Error};
use atoms::JsWord; use atoms::JsWord;
use common::{collections::AHashMap, comments::SingleThreadedComments, errors::HANDLER, Span}; use common::{collections::AHashMap, comments::SingleThreadedComments, errors::HANDLER};
use config::{util::BoolOrObject, IsModule, JsMinifyCommentOption, JsMinifyOptions}; use config::{IsModule, JsMinifyCommentOption, JsMinifyOptions};
use json_comments::StripComments; use json_comments::StripComments;
use once_cell::sync::Lazy; use once_cell::sync::Lazy;
use serde::Serialize; use serde::Serialize;
@ -135,6 +135,8 @@ use swc_common::{
sync::Lrc, sync::Lrc,
BytePos, FileName, Globals, Mark, SourceFile, SourceMap, Spanned, GLOBALS, BytePos, FileName, Globals, Mark, SourceFile, SourceMap, Spanned, GLOBALS,
}; };
pub use swc_config::config_types::{BoolConfig, BoolOr, BoolOrDataConfig};
use swc_config::merge::Merge;
use swc_ecma_ast::{EsVersion, Ident, Program}; use swc_ecma_ast::{EsVersion, Ident, Program};
use swc_ecma_codegen::{self, text_writer::WriteJs, Emitter, Node}; use swc_ecma_codegen::{self, text_writer::WriteJs, Emitter, Node};
use swc_ecma_loader::resolvers::{ use swc_ecma_loader::resolvers::{
@ -159,7 +161,7 @@ use swc_timer::timer;
pub use crate::builder::PassBuilder; pub use crate::builder::PassBuilder;
use crate::config::{ use crate::config::{
BuiltInput, Config, ConfigFile, InputSourceMap, Merge, Options, Rc, RootMode, SourceMapsConfig, BuiltInput, Config, ConfigFile, InputSourceMap, Options, Rc, RootMode, SourceMapsConfig,
}; };
mod builder; mod builder;
@ -584,74 +586,14 @@ impl SourceMapGenConfig for SwcSourceMapConfig<'_> {
} }
} }
pub fn minify_global_comments( pub(crate) fn minify_file_comments(
comments: &SwcComments,
span: Span,
minify: bool,
preserve_comments: Option<BoolOrObject<JsMinifyCommentOption>>,
) {
let preserve_comments = preserve_comments.unwrap_or({
if minify {
BoolOrObject::Obj(JsMinifyCommentOption::PreserveSomeComments)
} else {
BoolOrObject::Obj(JsMinifyCommentOption::PreserveAllComments)
}
});
match preserve_comments {
BoolOrObject::Bool(true)
| BoolOrObject::Obj(JsMinifyCommentOption::PreserveAllComments) => {}
BoolOrObject::Obj(JsMinifyCommentOption::PreserveSomeComments) => {
let preserve_excl = |pos: &BytePos, vc: &mut Vec<Comment>| -> bool {
if *pos < span.lo || *pos >= span.hi {
return true;
}
// Preserve license comments.
if vc.iter().any(|c| c.text.contains("@license")) {
return true;
}
vc.retain(|c: &Comment| c.text.starts_with('!'));
!vc.is_empty()
};
comments.leading.retain(preserve_excl);
comments.trailing.retain(preserve_excl);
}
BoolOrObject::Bool(false) => {
let remove_all_in_range = |pos: &BytePos, _: &mut Vec<Comment>| -> bool {
if *pos < span.lo || *pos >= span.hi {
return true;
}
false
};
comments.leading.retain(remove_all_in_range);
comments.trailing.retain(remove_all_in_range);
}
}
}
pub fn minify_file_comments(
comments: &SingleThreadedComments, comments: &SingleThreadedComments,
minify: bool, preserve_comments: BoolOr<JsMinifyCommentOption>,
preserve_comments: Option<BoolOrObject<JsMinifyCommentOption>>,
) { ) {
let preserve_comments = preserve_comments.unwrap_or({
if minify {
BoolOrObject::Obj(JsMinifyCommentOption::PreserveSomeComments)
} else {
BoolOrObject::Obj(JsMinifyCommentOption::PreserveAllComments)
}
});
match preserve_comments { match preserve_comments {
BoolOrObject::Bool(true) BoolOr::Bool(true) | BoolOr::Data(JsMinifyCommentOption::PreserveAllComments) => {}
| BoolOrObject::Obj(JsMinifyCommentOption::PreserveAllComments) => {}
BoolOrObject::Obj(JsMinifyCommentOption::PreserveSomeComments) => { BoolOr::Data(JsMinifyCommentOption::PreserveSomeComments) => {
let preserve_excl = |_: &BytePos, vc: &mut Vec<Comment>| -> bool { let preserve_excl = |_: &BytePos, vc: &mut Vec<Comment>| -> bool {
// Preserve license comments. // Preserve license comments.
if vc.iter().any(|c| c.text.contains("@license")) { if vc.iter().any(|c| c.text.contains("@license")) {
@ -667,7 +609,7 @@ pub fn minify_file_comments(
t.retain(preserve_excl); t.retain(preserve_excl);
} }
BoolOrObject::Bool(false) => { BoolOr::Bool(false) => {
let (mut l, mut t) = comments.borrow_all_mut(); let (mut l, mut t) = comments.borrow_all_mut();
l.clear(); l.clear();
t.clear(); t.clear();
@ -725,7 +667,7 @@ impl Compiler {
.context("failed to process config file")?; .context("failed to process config file")?;
if let Some(config_file) = config_file { if let Some(config_file) = config_file {
config.merge(&config_file.into_config(Some(path))?) config.merge(config_file.into_config(Some(path))?)
} }
if let Some(c) = &mut config { if let Some(c) = &mut config {
@ -831,7 +773,6 @@ impl Compiler {
opts.output_path.as_deref(), opts.output_path.as_deref(),
opts.source_file_name.clone(), opts.source_file_name.clone(),
handler, handler,
opts.is_module,
Some(config), Some(config),
comments, comments,
before_pass, before_pass,
@ -963,10 +904,10 @@ impl Compiler {
let target = opts.ecma.clone().into(); let target = opts.ecma.clone().into();
let (source_map, orig) = match &opts.source_map { let (source_map, orig) = opts
BoolOrObject::Bool(false) => (SourceMapsConfig::Bool(false), None), .source_map
BoolOrObject::Bool(true) => (SourceMapsConfig::Bool(true), None), .as_ref()
BoolOrObject::Obj(obj) => { .map(|obj| -> Result<_, Error> {
let orig = obj let orig = obj
.content .content
.as_ref() .as_ref()
@ -975,17 +916,32 @@ impl Compiler {
Some(v) => Some(v?), Some(v) => Some(v?),
None => None, None => None,
}; };
(SourceMapsConfig::Bool(true), orig) Ok((SourceMapsConfig::Bool(true), orig))
} })
}; .unwrap_as_option(|v| {
Some(Ok(match v {
Some(true) => (SourceMapsConfig::Bool(true), None),
_ => (SourceMapsConfig::Bool(false), None),
}))
})
.unwrap()?;
let mut min_opts = MinifyOptions { let mut min_opts = MinifyOptions {
compress: opts compress: opts
.compress .compress
.clone() .clone()
.into_obj() .unwrap_as_option(|default| match default {
Some(true) | None => Some(Default::default()),
_ => None,
})
.map(|v| v.into_config(self.cm.clone())), .map(|v| v.into_config(self.cm.clone())),
mangle: opts.mangle.clone().into_obj(), mangle: opts
.mangle
.clone()
.unwrap_as_option(|default| match default {
Some(true) | None => Some(Default::default()),
_ => None,
}),
..Default::default() ..Default::default()
}; };
@ -1065,7 +1021,13 @@ impl Compiler {
module.fold_with(&mut fixer(Some(&comments as &dyn Comments))) module.fold_with(&mut fixer(Some(&comments as &dyn Comments)))
}); });
minify_file_comments(&comments, true, Some(opts.format.comments.clone())); let preserve_comments = opts
.format
.comments
.clone()
.into_inner()
.unwrap_or(BoolOr::Data(JsMinifyCommentOption::PreserveSomeComments));
minify_file_comments(&comments, preserve_comments);
self.print( self.print(
&module, &module,
@ -1135,7 +1097,7 @@ impl Compiler {
}); });
if let Some(comments) = &config.comments { if let Some(comments) = &config.comments {
minify_file_comments(comments, config.minify, config.preserve_comments); minify_file_comments(comments, config.preserve_comments);
} }
self.print( self.print(

View File

@ -8,7 +8,7 @@ use std::{
use anyhow::{bail, Context, Error}; use anyhow::{bail, Context, Error};
use swc::{ use swc::{
config::{Config, JsMinifyOptions, JscConfig, ModuleConfig, Options, SourceMapsConfig}, config::{Config, JsMinifyOptions, JscConfig, ModuleConfig, Options, SourceMapsConfig},
try_with_handler, Compiler, HandlerOpts, try_with_handler, BoolOrDataConfig, Compiler, HandlerOpts,
}; };
use swc_common::{errors::ColorConfig, SourceMap}; use swc_common::{errors::ColorConfig, SourceMap};
use swc_ecma_ast::EsVersion; use swc_ecma_ast::EsVersion;
@ -84,12 +84,12 @@ fn create_matrix(entry: &Path) -> Vec<Options> {
jsc: JscConfig { jsc: JscConfig {
syntax: Some(syntax), syntax: Some(syntax),
transform: None, transform: None,
external_helpers, external_helpers: external_helpers.into(),
target: Some(target), target: Some(target),
minify: if minify { minify: if minify {
Some(JsMinifyOptions { Some(JsMinifyOptions {
compress: true.into(), compress: BoolOrDataConfig::from_bool(true),
mangle: true.into(), mangle: BoolOrDataConfig::from_bool(true),
format: Default::default(), format: Default::default(),
ecma: Default::default(), ecma: Default::default(),
keep_classnames: Default::default(), keep_classnames: Default::default(),
@ -107,7 +107,7 @@ fn create_matrix(entry: &Path) -> Vec<Options> {
..Default::default() ..Default::default()
}, },
module: Some(ModuleConfig::CommonJs(Default::default())), module: Some(ModuleConfig::CommonJs(Default::default())),
minify, minify: minify.into(),
..Default::default() ..Default::default()
}, },
source_maps: if source_map { source_maps: if source_map {

View File

@ -9,7 +9,7 @@ use swc::{
BuiltInput, Config, IsModule, JscConfig, ModuleConfig, Options, SourceMapsConfig, BuiltInput, Config, IsModule, JscConfig, ModuleConfig, Options, SourceMapsConfig,
TransformConfig, TransformConfig,
}, },
minify_file_comments, Compiler, TransformOutput, Compiler, TransformOutput,
}; };
use swc_common::{ use swc_common::{
chain, chain,
@ -772,8 +772,6 @@ fn should_visit() {
}) })
}); });
minify_file_comments(&comments, config.minify, config.preserve_comments);
Ok(c.print( Ok(c.print(
&program, &program,
None, None,
@ -831,7 +829,7 @@ fn tests(input_dir: PathBuf) {
output_path: Some(output.join(entry.file_name())), output_path: Some(output.join(entry.file_name())),
config: Config { config: Config {
jsc: JscConfig { jsc: JscConfig {
external_helpers: true, external_helpers: true.into(),
..Default::default() ..Default::default()
}, },
..Default::default() ..Default::default()

View File

@ -139,23 +139,23 @@ fn shopify_2_same_opt() {
no_early_errors: false, no_early_errors: false,
})), })),
transform: None, transform: None,
external_helpers: false, external_helpers: false.into(),
target: Some(EsVersion::Es5), target: Some(EsVersion::Es5),
loose: false, loose: false.into(),
keep_class_names: false, keep_class_names: false.into(),
base_url: Default::default(), base_url: Default::default(),
paths: Default::default(), paths: Default::default(),
minify: None, minify: None,
experimental: Default::default(), experimental: Default::default(),
lints: Default::default(), lints: Default::default(),
assumptions: Default::default(), assumptions: Default::default(),
preserve_all_comments: false, preserve_all_comments: false.into(),
}, },
module: None, module: None,
minify: false, minify: false.into(),
input_source_map: InputSourceMap::Bool(false), input_source_map: Some(InputSourceMap::Bool(false)),
source_maps: None, source_maps: None,
inline_sources_content: false, inline_sources_content: false.into(),
..Default::default() ..Default::default()
}, },
skip_helper_injection: false, skip_helper_injection: false,
@ -218,10 +218,10 @@ fn shopify_3_reduce_defaults() {
..Default::default() ..Default::default()
}, },
module: None, module: None,
minify: false, minify: false.into(),
input_source_map: InputSourceMap::Bool(false), input_source_map: InputSourceMap::Bool(false).into(),
source_maps: None, source_maps: None,
inline_sources_content: false, inline_sources_content: false.into(),
..Default::default() ..Default::default()
}, },
cwd: "/Users/kdy1/projects/example-swcify".into(), cwd: "/Users/kdy1/projects/example-swcify".into(),

View File

@ -30,7 +30,7 @@ fn file(f: &str) -> Result<(), StdErr> {
&handler, &handler,
&Options { &Options {
config: Config { config: Config {
inline_sources_content: true, inline_sources_content: true.into(),
..Default::default() ..Default::default()
}, },
swcrc: true, swcrc: true,
@ -85,7 +85,7 @@ fn inline(f: &str) -> Result<(), StdErr> {
&handler, &handler,
&Options { &Options {
config: Config { config: Config {
inline_sources_content: true, inline_sources_content: true.into(),
..Default::default() ..Default::default()
}, },
swcrc: true, swcrc: true,

View File

@ -137,9 +137,9 @@ fn compile(input: &Path, output: &Path, opts: Options) {
tsx: input.to_string_lossy().ends_with(".tsx"), tsx: input.to_string_lossy().ends_with(".tsx"),
decorators: true, decorators: true,
dts: false, dts: false,
no_early_errors: true, no_early_errors: false,
})), })),
external_helpers: true, external_helpers: true.into(),
..opts.config.jsc ..opts.config.jsc
}, },
source_maps: Some(SourceMapsConfig::Bool( source_maps: Some(SourceMapsConfig::Bool(

View File

@ -0,0 +1,16 @@
[package]
authors = ["강동윤 <kdy1997.dev@gmail.com>"]
description = "Types for configuring swc"
documentation = "https://rustdoc.swc.rs/swc_config/"
edition = "2021"
license = "Apache-2.0"
name = "swc_config"
repository = "https://github.com/swc-project/swc.git"
version = "0.1.0"
[dependencies]
anyhow = "1"
indexmap = "1"
serde = { version = "1", features = ["derive"] }
serde_json = "1"
swc_config_macro = { version = "0.1.0", path = "../swc_config_macro" }

View File

@ -0,0 +1,51 @@
use serde::{Deserialize, Serialize};
use crate::merge::Merge;
#[derive(
Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize,
)]
pub struct BoolConfig<const DEFAULT: bool>(#[serde(default)] Option<bool>);
impl<const DEFAULT: bool> BoolConfig<DEFAULT> {
#[inline]
pub fn new(value: Option<bool>) -> Self {
Self(value)
}
#[inline]
pub fn into_bool(self) -> bool {
self.into()
}
}
impl<const DEFAULT: bool> From<BoolConfig<DEFAULT>> for bool {
#[inline]
fn from(v: BoolConfig<DEFAULT>) -> Self {
match v.0 {
Some(v) => v,
_ => DEFAULT,
}
}
}
impl<const DEFAULT: bool> From<Option<bool>> for BoolConfig<DEFAULT> {
#[inline]
fn from(v: Option<bool>) -> Self {
Self(v)
}
}
impl<const DEFAULT: bool> From<bool> for BoolConfig<DEFAULT> {
#[inline]
fn from(v: bool) -> Self {
Self(Some(v))
}
}
impl<const DEFAULT: bool> Merge for BoolConfig<DEFAULT> {
#[inline]
fn merge(&mut self, other: Self) {
self.0.merge(other.0);
}
}

View File

@ -0,0 +1,140 @@
use serde::{Deserialize, Serialize};
use crate::merge::Merge;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
pub struct BoolOrDataConfig<T>(#[serde(default)] Option<BoolOr<T>>);
impl<T> BoolOrDataConfig<T> {
pub fn from_bool(v: bool) -> Self {
Self(Some(BoolOr::Bool(v)))
}
pub fn from_obj(v: T) -> Self {
v.into()
}
pub fn as_ref(&self) -> BoolOrDataConfig<&T> {
match &self.0 {
Some(BoolOr::Data(v)) => BoolOrDataConfig::from_obj(v),
Some(BoolOr::Bool(b)) => BoolOrDataConfig::from_bool(*b),
None => BoolOrDataConfig::default(),
}
}
pub fn or<F>(self, default: F) -> Self
where
F: FnOnce() -> Self,
{
match self.0 {
Some(..) => self,
None => default(),
}
}
pub fn unwrap_as_option<F>(self, default: F) -> Option<T>
where
F: FnOnce(Option<bool>) -> Option<T>,
{
match self.0 {
Some(BoolOr::Data(v)) => Some(v),
Some(BoolOr::Bool(b)) => default(Some(b)),
None => default(None),
}
}
pub fn map<F, N>(self, op: F) -> BoolOrDataConfig<N>
where
F: FnOnce(T) -> N,
{
match self.0 {
Some(BoolOr::Data(v)) => BoolOrDataConfig::from_obj(op(v)),
Some(BoolOr::Bool(b)) => BoolOrDataConfig::from_bool(b),
None => BoolOrDataConfig::default(),
}
}
pub fn is_true(&self) -> bool {
matches!(self.0, Some(BoolOr::Bool(true)))
}
pub fn is_false(&self) -> bool {
matches!(self.0, Some(BoolOr::Bool(false)))
}
pub fn is_obj(&self) -> bool {
matches!(self.0, Some(BoolOr::Data(_)))
}
pub fn into_inner(self) -> Option<BoolOr<T>> {
self.0
}
}
impl<T> From<T> for BoolOrDataConfig<T> {
fn from(v: T) -> Self {
Self(Some(BoolOr::Data(v)))
}
}
impl<T> Default for BoolOrDataConfig<T> {
fn default() -> Self {
Self(Default::default())
}
}
impl<T> Merge for BoolOrDataConfig<T> {
#[inline]
fn merge(&mut self, other: Self) {
self.0.merge(other.0)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
#[serde(untagged)]
pub enum BoolOr<T> {
Bool(bool),
Data(T),
}
impl<'de, T> Deserialize<'de> for BoolOr<T>
where
T: Deserialize<'de>,
{
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
#[derive(Deserialize)]
#[serde(untagged)]
enum Deser<T> {
Bool(bool),
Obj(T),
EmptyObject(EmptyStruct),
}
#[derive(Deserialize)]
#[serde(deny_unknown_fields)]
struct EmptyStruct {}
use serde::__private::de;
let content = de::Content::deserialize(deserializer)?;
let deserializer = de::ContentRefDeserializer::<D::Error>::new(&content);
let res = Deser::deserialize(deserializer);
match res {
Ok(v) => Ok(match v {
Deser::Bool(v) => BoolOr::Bool(v),
Deser::Obj(v) => BoolOr::Data(v),
Deser::EmptyObject(_) => BoolOr::Bool(true),
}),
Err(..) => {
let d = de::ContentDeserializer::<D::Error>::new(content);
Ok(BoolOr::Data(T::deserialize(d)?))
}
}
}
}

View File

@ -0,0 +1,7 @@
pub use self::{
bool_config::BoolConfig,
bool_or_data::{BoolOr, BoolOrDataConfig},
};
mod bool_config;
mod bool_or_data;

View File

@ -0,0 +1,4 @@
#[macro_use]
mod macros;
pub mod config_types;
pub mod merge;

View File

@ -0,0 +1 @@

View File

@ -0,0 +1,83 @@
use std::{collections::HashMap, path::PathBuf};
use indexmap::IndexMap;
pub use swc_config_macro::Merge;
/// Deriavable trait for overrding configurations.
///
/// Typically, correct implementation of this trait for a struct is calling
/// merge for all fields, and `#[derive(Merge)]` will do it for you.
pub trait Merge: Sized {
/// `self` has higher priority.
fn merge(&mut self, other: Self);
}
/// Modifies `self` iff `self` is [None]
impl<T> Merge for Option<T> {
#[inline]
fn merge(&mut self, other: Self) {
if self.is_none() {
*self = other;
}
}
}
impl<T> Merge for Box<T>
where
T: Merge,
{
#[inline]
fn merge(&mut self, other: Self) {
(**self).merge(*other);
}
}
/// Modifies `self` iff `self` is empty.
impl<T> Merge for Vec<T> {
#[inline]
fn merge(&mut self, other: Self) {
if self.is_empty() {
*self = other;
}
}
}
/// Modifies `self` iff `self` is empty.
impl<K, V, S> Merge for HashMap<K, V, S> {
#[inline]
fn merge(&mut self, other: Self) {
if self.is_empty() {
*self = other;
}
}
}
/// Modifies `self` iff `self` is empty.
impl<K, V, S> Merge for IndexMap<K, V, S> {
#[inline]
fn merge(&mut self, other: Self) {
if self.is_empty() {
*self = other;
}
}
}
/// Modifies `self` iff `self` is empty.
impl Merge for String {
#[inline]
fn merge(&mut self, other: Self) {
if self.is_empty() {
*self = other;
}
}
}
/// Modifies `self` iff `self` is empty.
impl Merge for PathBuf {
#[inline]
fn merge(&mut self, other: Self) {
if self.as_os_str().is_empty() {
*self = other;
}
}
}

View File

@ -0,0 +1,29 @@
use serde_json::Value;
use swc_config::config_types::BoolConfig;
fn bool_config(v: Value) -> BoolConfig<false> {
serde_json::from_value(v).unwrap()
}
#[test]
fn test_bool_config_serde() {
assert_eq!(bool_config(Value::Null), BoolConfig::new(None));
assert_eq!(bool_config(Value::Bool(true)), BoolConfig::new(Some(true)));
assert_eq!(
bool_config(Value::Bool(false)),
BoolConfig::new(Some(false))
);
}
#[test]
fn test_bool_config_default() {
assert_eq!(
BoolConfig::<false>::default(),
BoolConfig::<false>::new(None)
);
assert_eq!(BoolConfig::<true>::default(), BoolConfig::<true>::new(None));
assert!(!BoolConfig::<false>::default().into_bool());
assert!(BoolConfig::<true>::default().into_bool());
}

View File

@ -0,0 +1,24 @@
use swc_config::merge::Merge;
#[derive(Merge)]
struct Fields {
a: Option<()>,
}
#[test]
fn test_fields() {
let mut fields = Fields { a: None };
fields.merge(Fields { a: Some(()) });
assert_eq!(fields.a, Some(()));
}
#[derive(Merge)]
struct Tuple(Option<()>);
#[test]
fn test_tuple() {
let mut tuple = Tuple(None);
tuple.merge(Tuple(Some(())));
assert_eq!(tuple.0, Some(()));
}

View File

@ -0,0 +1,19 @@
[package]
authors = ["강동윤 <kdy1997.dev@gmail.com>"]
description = "Macros to prevent mistakes"
documentation = "https://rustdoc.swc.rs/swc_config_macro/"
edition = "2021"
license = "Apache-2.0"
name = "swc_config_macro"
repository = "https://github.com/swc-project/swc.git"
version = "0.1.0"
[lib]
proc-macro = true
[dependencies]
pmutil = "0.5.3"
proc-macro2 = "1"
quote = "1"
swc_macros_common = { version = "0.3.5", path = "../swc_macros_common" }
syn = "1"

View File

@ -0,0 +1,10 @@
extern crate proc_macro;
mod merge;
#[proc_macro_derive(Merge)]
pub fn derive_merge(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
let input = syn::parse(input).expect("failed to parse input as DeriveInput");
self::merge::expand(input).into()
}

View File

@ -0,0 +1,64 @@
use pmutil::{q, SpanExt};
use proc_macro2::TokenStream;
use quote::ToTokens;
use swc_macros_common::{access_field, join_stmts};
use syn::{DeriveInput, Expr, Field, Fields, Stmt};
pub fn expand(input: DeriveInput) -> TokenStream {
match &input.data {
syn::Data::Struct(s) => {
let body = call_merge_for_fields(&q!({ self }), &s.fields);
let body = join_stmts(&body);
q!(
Vars {
Type: &input.ident,
body
},
{
#[automatically_derived]
impl swc_config::merge::Merge for Type {
fn merge(&mut self, _other: Self) {
body
}
}
}
)
.into()
}
syn::Data::Enum(_) => unimplemented!("derive(Merge) does not support an enum"),
syn::Data::Union(_) => unimplemented!("derive(Merge) does not support a union"),
}
}
fn call_merge_for_fields(obj: &dyn ToTokens, fields: &Fields) -> Vec<Stmt> {
fn call_merge(obj: &dyn ToTokens, idx: usize, f: &Field) -> Expr {
let r = q!({ _other });
q!(
Vars {
l: access_field(obj, idx, f),
r: access_field(&r, idx, f),
},
{ swc_config::merge::Merge::merge(&mut l, r) }
)
.parse()
}
match fields {
Fields::Named(fs) => fs
.named
.iter()
.enumerate()
.map(|(idx, f)| call_merge(obj, idx, f))
.map(|expr| Stmt::Semi(expr, fs.brace_token.span.as_token()))
.collect(),
Fields::Unnamed(fs) => fs
.unnamed
.iter()
.enumerate()
.map(|(idx, f)| call_merge(obj, idx, f))
.map(|expr| Stmt::Semi(expr, fs.paren_token.span.as_token()))
.collect(),
Fields::Unit => unimplemented!("derive(Merge) does not support a unit struct"),
}
}

View File

@ -24,6 +24,7 @@ swc_atoms = { version = "0.2.9", path = "../swc_atoms" }
swc_common = { version = "0.17.23", path = "../swc_common", features = [ swc_common = { version = "0.17.23", path = "../swc_common", features = [
"concurrent", "concurrent",
] } ] }
swc_config = { version = "0.1.0", path = "../swc_config" }
swc_ecma_ast = { version = "0.77.0", path = "../swc_ecma_ast" } swc_ecma_ast = { version = "0.77.0", path = "../swc_ecma_ast" }
swc_ecma_utils = { version = "0.83.0", path = "../swc_ecma_utils" } swc_ecma_utils = { version = "0.83.0", path = "../swc_ecma_utils" }
swc_ecma_visit = { version = "0.63.0", path = "../swc_ecma_visit" } swc_ecma_visit = { version = "0.63.0", path = "../swc_ecma_visit" }

View File

@ -1,6 +1,7 @@
use std::fmt::Debug; use std::fmt::Debug;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use swc_config::merge::Merge;
#[cfg(feature = "non_critical_lints")] #[cfg(feature = "non_critical_lints")]
use crate::rules::non_critical_lints::{ use crate::rules::non_critical_lints::{
@ -32,11 +33,12 @@ impl Default for LintRuleReaction {
enum LintRuleLevel { enum LintRuleLevel {
Str(LintRuleReaction), Str(LintRuleReaction),
Number(u8), Number(u8),
Unspecified,
} }
impl Default for LintRuleLevel { impl Default for LintRuleLevel {
fn default() -> Self { fn default() -> Self {
Self::Str(LintRuleReaction::Off) Self::Unspecified
} }
} }
@ -49,6 +51,7 @@ impl From<LintRuleLevel> for LintRuleReaction {
2 => LintRuleReaction::Error, 2 => LintRuleReaction::Error,
_ => LintRuleReaction::Off, _ => LintRuleReaction::Off,
}, },
LintRuleLevel::Unspecified => LintRuleReaction::Off,
} }
} }
} }
@ -69,7 +72,19 @@ impl<T: Debug + Clone + Serialize + Default> RuleConfig<T> {
} }
} }
#[derive(Debug, Clone, Default, Serialize, Deserialize)] impl<T> Merge for RuleConfig<T>
where
T: Debug + Clone + Serialize + Default,
{
fn merge(&mut self, other: Self) {
if let LintRuleLevel::Unspecified = self.0 {
self.0 = other.0;
self.1 = other.1;
}
}
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, Merge)]
#[non_exhaustive] #[non_exhaustive]
#[serde(rename_all = "kebab-case")] #[serde(rename_all = "kebab-case")]
pub struct LintConfig { pub struct LintConfig {

View File

@ -37,6 +37,7 @@ serde_json = "1.0.61"
swc_atoms = { version = "0.2", path = "../swc_atoms" } swc_atoms = { version = "0.2", path = "../swc_atoms" }
swc_cached = { version = "0.1.0", path = "../swc_cached" } swc_cached = { version = "0.1.0", path = "../swc_cached" }
swc_common = { version = "0.17.23", path = "../swc_common" } swc_common = { version = "0.17.23", path = "../swc_common" }
swc_config = { version = "0.1.0", path = "../swc_config" }
swc_ecma_ast = { version = "0.77.0", path = "../swc_ecma_ast" } swc_ecma_ast = { version = "0.77.0", path = "../swc_ecma_ast" }
swc_ecma_codegen = { version = "0.106.0", path = "../swc_ecma_codegen" } swc_ecma_codegen = { version = "0.106.0", path = "../swc_ecma_codegen" }
swc_ecma_parser = { version = "0.103.0", path = "../swc_ecma_parser" } swc_ecma_parser = { version = "0.103.0", path = "../swc_ecma_parser" }

View File

@ -2,6 +2,7 @@ use serde::{Deserialize, Serialize};
use swc_atoms::JsWord; use swc_atoms::JsWord;
use swc_cached::regex::CachedRegex; use swc_cached::regex::CachedRegex;
use swc_common::{collections::AHashMap, Mark}; use swc_common::{collections::AHashMap, Mark};
use swc_config::merge::Merge;
use swc_ecma_ast::{EsVersion, Expr}; use swc_ecma_ast::{EsVersion, Expr};
pub mod terser; pub mod terser;
@ -68,13 +69,13 @@ pub struct MangleOptions {
pub reserved: Vec<JsWord>, pub reserved: Vec<JsWord>,
} }
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[derive(Debug, Clone, Default, Serialize, Deserialize, Merge)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct ManglePropertiesOptions { pub struct ManglePropertiesOptions {
#[serde(default, alias = "reserved")] #[serde(default, alias = "reserved")]
pub reserved: Vec<JsWord>, pub reserved: Vec<JsWord>,
#[serde(default, alias = "undeclared")] #[serde(default, alias = "undeclared")]
pub undeclared: bool, pub undeclared: Option<bool>,
#[serde(default)] #[serde(default)]
pub regex: Option<CachedRegex>, pub regex: Option<CachedRegex>,
} }

View File

@ -75,9 +75,7 @@ fn compressed(compressed_file: PathBuf) {
&MinifyOptions { &MinifyOptions {
mangle: Some(MangleOptions { mangle: Some(MangleOptions {
props: Some(ManglePropertiesOptions { props: Some(ManglePropertiesOptions {
reserved: Default::default(), ..Default::default()
undeclared: false,
regex: Default::default(),
}), }),
top_level: true, top_level: true,
keep_class_names: false, keep_class_names: false,

View File

@ -21,23 +21,24 @@ ahash = "0.7.4"
dashmap = "5.1.0" dashmap = "5.1.0"
indexmap = "1.6.1" indexmap = "1.6.1"
once_cell = "1.10.0" once_cell = "1.10.0"
rayon = {version = "1.5.1", optional = true} rayon = { version = "1.5.1", optional = true }
rustc-hash = "1.1.0"
serde_json = "1.0.61" serde_json = "1.0.61"
swc_atoms = {version = "0.2", path = "../swc_atoms"} swc_atoms = { version = "0.2", path = "../swc_atoms" }
swc_common = {version = "0.17.23", path = "../swc_common"} swc_common = { version = "0.17.23", path = "../swc_common" }
swc_ecma_ast = {version = "0.77.0", path = "../swc_ecma_ast"} swc_ecma_ast = { version = "0.77.0", path = "../swc_ecma_ast" }
swc_ecma_parser = {version = "0.103.0", path = "../swc_ecma_parser"} swc_ecma_parser = { version = "0.103.0", path = "../swc_ecma_parser" }
swc_ecma_transforms_base = {version = "0.82.0", path = "../swc_ecma_transforms_base"} swc_ecma_transforms_base = { version = "0.82.0", path = "../swc_ecma_transforms_base" }
swc_ecma_transforms_macros = {version = "0.3.0", path = "../swc_ecma_transforms_macros"} swc_ecma_transforms_macros = { version = "0.3.0", path = "../swc_ecma_transforms_macros" }
swc_ecma_utils = {version = "0.83.0", path = "../swc_ecma_utils"} swc_ecma_utils = { version = "0.83.0", path = "../swc_ecma_utils" }
swc_ecma_visit = {version = "0.63.0", path = "../swc_ecma_visit"} swc_ecma_visit = { version = "0.63.0", path = "../swc_ecma_visit" }
tracing = "0.1.32" tracing = "0.1.32"
[dev-dependencies] [dev-dependencies]
swc_ecma_transforms_compat = {version = "0.96.0", path = "../swc_ecma_transforms_compat"} swc_ecma_transforms_compat = { version = "0.96.0", path = "../swc_ecma_transforms_compat" }
swc_ecma_transforms_module = {version = "0.109.0", path = "../swc_ecma_transforms_module"} swc_ecma_transforms_module = { version = "0.109.0", path = "../swc_ecma_transforms_module" }
swc_ecma_transforms_proposal = {version = "0.104.0", path = "../swc_ecma_transforms_proposal"} swc_ecma_transforms_proposal = { version = "0.104.0", path = "../swc_ecma_transforms_proposal" }
swc_ecma_transforms_react = {version = "0.111.0", path = "../swc_ecma_transforms_react"} swc_ecma_transforms_react = { version = "0.111.0", path = "../swc_ecma_transforms_react" }
swc_ecma_transforms_testing = {version = "0.84.0", path = "../swc_ecma_transforms_testing"} swc_ecma_transforms_testing = { version = "0.84.0", path = "../swc_ecma_transforms_testing" }
swc_ecma_transforms_typescript = {version = "0.114.0", path = "../swc_ecma_transforms_typescript"} swc_ecma_transforms_typescript = { version = "0.114.0", path = "../swc_ecma_transforms_typescript" }
testing = {version = "0.19.0", path = "../testing"} testing = { version = "0.19.0", path = "../testing" }

View File

@ -2,6 +2,7 @@ use std::{collections::HashMap, sync::Arc};
use dashmap::DashMap; use dashmap::DashMap;
use once_cell::sync::Lazy; use once_cell::sync::Lazy;
use rustc_hash::FxHashMap;
use swc_atoms::JsWord; use swc_atoms::JsWord;
use swc_common::{errors::HANDLER, sync::Lrc, util::move_map::MoveMap, FileName, SourceMap}; use swc_common::{errors::HANDLER, sync::Lrc, util::move_map::MoveMap, FileName, SourceMap};
use swc_ecma_ast::*; use swc_ecma_ast::*;
@ -11,7 +12,7 @@ use swc_ecma_visit::{noop_fold_type, Fold, FoldWith};
pub fn const_modules( pub fn const_modules(
cm: Lrc<SourceMap>, cm: Lrc<SourceMap>,
globals: HashMap<JsWord, HashMap<JsWord, String>>, globals: FxHashMap<JsWord, FxHashMap<JsWord, String>>,
) -> impl Fold { ) -> impl Fold {
ConstModules { ConstModules {
globals: globals globals: globals

View File

@ -6,7 +6,7 @@ edition = "2021"
license = "Apache-2.0" license = "Apache-2.0"
name = "swc_macros_common" name = "swc_macros_common"
repository = "https://github.com/swc-project/swc.git" repository = "https://github.com/swc-project/swc.git"
version = "0.3.4" version = "0.3.5"
[lib] [lib]
bench = false bench = false

View File

@ -1,9 +1,10 @@
extern crate proc_macro; extern crate proc_macro;
use pmutil::synom_ext::FromSpan;
#[cfg(procmacro2_semver_exempt)] #[cfg(procmacro2_semver_exempt)]
use pmutil::SpanExt; use pmutil::SpanExt;
use pmutil::{synom_ext::FromSpan, Quote, SpanExt};
use proc_macro2::Span; use proc_macro2::Span;
use quote::ToTokens;
use syn::*; use syn::*;
pub mod binder; pub mod binder;
@ -78,6 +79,32 @@ pub fn doc_str(attr: &Attribute) -> Option<String> {
Some(parse_tts(attr)) Some(parse_tts(attr))
} }
pub fn access_field(obj: &dyn ToTokens, idx: usize, f: &Field) -> Expr {
Expr::Field(ExprField {
attrs: Default::default(),
base: syn::parse2(obj.to_token_stream())
.expect("swc_macros_common::access_field: failed to parse object"),
dot_token: Span::call_site().as_token(),
member: match &f.ident {
Some(id) => Member::Named(id.clone()),
_ => Member::Unnamed(Index {
index: idx as _,
span: Span::call_site(),
}),
},
})
}
pub fn join_stmts(stmts: &[Stmt]) -> Quote {
let mut q = Quote::new_call_site();
for s in stmts {
q.push_tokens(s);
}
q
}
/// fail! is a panic! with location reporting. /// fail! is a panic! with location reporting.
#[macro_export] #[macro_export]
macro_rules! fail { macro_rules! fail {

View File

@ -202,12 +202,12 @@ impl SwcLoader {
..Default::default() ..Default::default()
}) })
}, },
external_helpers: true, external_helpers: true.into(),
..c.jsc.clone() ..c.jsc.clone()
}, },
module: None, module: None,
minify: false, minify: false.into(),
input_source_map: InputSourceMap::Bool(false), input_source_map: InputSourceMap::Bool(false).into(),
..c.clone() ..c.clone()
} }
}, },

View File

@ -6,9 +6,7 @@ it("should compress", async () => {
console.log(foo) console.log(foo)
`); `);
expect(code).toMatchInlineSnapshot( expect(code).toMatchInlineSnapshot(`"import a from\\"@src/app\\";console.log(a)"`);
`"import foo from\\"@src/app\\";console.log(foo)"`
);
}); });
it("should accept object", async () => { it("should accept object", async () => {
@ -20,9 +18,7 @@ it("should accept object", async () => {
{} {}
); );
expect(code).toMatchInlineSnapshot( expect(code).toMatchInlineSnapshot(`"import a from\\"@src/app\\";console.log(a)"`);
`"import foo from\\"@src/app\\";console.log(foo)"`
);
}); });
it("should accept { mangle = true }", async () => { it("should accept { mangle = true }", async () => {

View File

@ -176,18 +176,16 @@ it("should respect `error.filename = false`", async () => {
}); });
it("should merge parser config", async () => { it("should support overring `jsc.externalHelpers` using js api", async () => {
const filename = path.resolve( const filename = path.resolve(
__dirname + "/../../tests/issue-2546/input.ts" __dirname + "/../../tests/issue-3834/input.js"
); );
const { code } = await swc.transformFile(filename, { const { code } = await swc.transformFile(filename, {
jsc: { jsc: {
parser: { externalHelpers: false,
syntax: "typescript",
}
} }
}) })
expect(code).not.toBeFalsy() expect(code).toContain('function _classCallCheck')
}); });

View File

@ -5,7 +5,7 @@
export interface TransformOutput { export interface TransformOutput {
code: string code: string
map?: string | undefined | null map?: string
} }
export function bundle(confItems: Buffer, signal?: AbortSignal | undefined | null): Promise<{ [index: string]: { code: string, map?: string } }> export function bundle(confItems: Buffer, signal?: AbortSignal | undefined | null): Promise<{ [index: string]: { code: string, map?: string } }>
export function minify(code: Buffer, opts: Buffer, signal?: AbortSignal | undefined | null): Promise<TransformOutput> export function minify(code: Buffer, opts: Buffer, signal?: AbortSignal | undefined | null): Promise<TransformOutput>
@ -25,7 +25,7 @@ export function initCustomTraceSubscriber(traceOutFilePath?: string | undefined
/** Hack for `Type Generation` */ /** Hack for `Type Generation` */
export interface TransformOutput { export interface TransformOutput {
code: string code: string
map?: string | undefined | null map?: string
} }
export type JsCompiler = Compiler export type JsCompiler = Compiler
export class Compiler { export class Compiler {

View File

@ -0,0 +1,8 @@
{
"jsc": {
"externalHelpers": true
},
"module": {
"type": "commonjs"
}
}

View File

@ -0,0 +1,4 @@
export default class MyClass {
}