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
- crate: better_scoped_tls
os: ubuntu-latest
- crate: binding_commons
os: ubuntu-latest
- crate: binding_core_node
os: ubuntu-latest
- crate: binding_core_wasm
os: ubuntu-latest
- crate: binding_css_node
os: ubuntu-latest
- crate: enum_kind
os: ubuntu-latest
- crate: from_variant
@ -136,6 +140,10 @@ jobs:
cargo hack check --feature-powerset --no-dev-deps
- crate: swc_common
os: windows-latest
- crate: swc_config
os: ubuntu-latest
- crate: swc_config_macro
os: ubuntu-latest
- crate: swc_css
os: ubuntu-latest
- crate: swc_css_ast

45
Cargo.lock generated
View File

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

View File

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

View File

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

View File

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

View File

@ -48,7 +48,7 @@ impl Task for TransformTask {
try_with(
self.c.cm.clone(),
!options.config.error.filename,
!options.config.error.filename.into_bool(),
|handler| {
self.c.run(|| match &self.input {
Input::Program(ref s) => {
@ -130,7 +130,10 @@ pub fn transform_sync(s: String, is_module: bool, opts: Buffer) -> napi::Result<
options.config.adjust(Path::new(&options.filename));
}
try_with(c.cm.clone(), !options.config.error.filename, |handler| {
try_with(
c.cm.clone(),
!options.config.error.filename.into_bool(),
|handler| {
c.run(|| {
if is_module {
let program: Program =
@ -148,7 +151,8 @@ pub fn transform_sync(s: String, is_module: bool, opts: Buffer) -> napi::Result<
c.process_js_file(fm, handler, &options)
}
})
})
},
)
.convert_err()
}
@ -189,7 +193,10 @@ pub fn transform_file_sync(
options.config.adjust(Path::new(&options.filename));
}
try_with(c.cm.clone(), !options.config.error.filename, |handler| {
try_with(
c.cm.clone(),
!options.config.error.filename.into_bool(),
|handler| {
c.run(|| {
if is_module {
let program: Program =
@ -200,6 +207,7 @@ pub fn transform_file_sync(
c.process_js_file(fm, handler, &options)
}
})
})
},
)
.convert_err()
}

View File

@ -114,7 +114,7 @@ pub fn print_sync(s: JsValue, opts: JsValue) -> Result<JsValue, JsValue> {
.unwrap_or(SourceMapsConfig::Bool(false)),
&Default::default(),
None,
opts.config.minify,
opts.config.minify.into(),
None,
)
.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" }
serde = { version = "1", features = ["derive"] }
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_codegen = { path = "../swc_css_codegen" }
swc_css_minifier = { path = "../swc_css_minifier" }

View File

@ -47,6 +47,7 @@ swc_common = {version = "0.17.23", path = "../swc_common", features = [
"concurrent",
"parking_lot",
] }
swc_config = { version = "0.1.0", path = "../swc_config" }
swc_ecma_ast = { version = "0.77.0", path = "../swc_ecma_ast" }
swc_ecma_codegen = { version = "0.106.0", path = "../swc_ecma_codegen" }
swc_ecma_ext_transforms = { version = "0.69.0", path = "../swc_ecma_ext_transforms" }
@ -80,6 +81,7 @@ swc_plugin_runner = {version = "0.54.0", path = "../swc_plugin_runner", optional
swc_timer = { version = "0.5.0", path = "../swc_timer" }
swc_visit = { version = "0.3.0", path = "../swc_visit" }
tracing = "0.1.32"
rustc-hash = "1.1.0"
[dependencies.napi-derive]
default-features = false

View File

@ -4,7 +4,7 @@ extern crate swc_node_base;
use std::{path::PathBuf, sync::Arc};
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};
fn mk() -> swc::Compiler {
@ -35,8 +35,8 @@ fn bench_minify(b: &mut Bencher, filename: &str) {
fm,
handler,
&JsMinifyOptions {
compress: true.into(),
mangle: true.into(),
compress: BoolOrDataConfig::from_bool(true),
mangle: BoolOrDataConfig::from_bool(true),
format: Default::default(),
ecma: Default::default(),
keep_classnames: Default::default(),

View File

@ -1,7 +1,7 @@
use std::{path::Path, sync::Arc};
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;
fn main() {
@ -23,8 +23,8 @@ fn main() {
fm,
handler,
&JsMinifyOptions {
compress: true.into(),
mangle: true.into(),
compress: BoolOrDataConfig::from_bool(true),
mangle: BoolOrDataConfig::from_bool(true),
format: Default::default(),
ecma: 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 either::Either;
use rustc_hash::FxHashMap;
use swc_atoms::JsWord;
use swc_common::{
chain,
@ -21,9 +22,7 @@ use swc_ecma_transforms::{
};
use swc_ecma_visit::{as_folder, noop_visit_mut_type, VisitMut};
use crate::config::{
util::BoolOrObject, CompiledPaths, GlobalPassOption, JsMinifyOptions, ModuleConfig,
};
use crate::config::{CompiledPaths, GlobalPassOption, JsMinifyOptions, ModuleConfig};
/// Builder is used to create a high performance `Compiler`.
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(
self,
globals: HashMap<JsWord, HashMap<JsWord, String>>,
globals: FxHashMap<JsWord, FxHashMap<JsWord, String>>,
) -> PassBuilder<'a, 'b, impl swc_ecma_visit::Fold> {
let cm = self.cm.clone();
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
.minify
.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);
let module_scope = Rc::new(RefCell::new(Scope::default()));
@ -351,7 +350,14 @@ impl VisitMut for MinifierPass {
fn visit_mut_module(&mut self, m: &mut Module) {
if let Some(options) = &self.options {
let opts = MinifyOptions {
compress: options.compress.clone().into_obj().map(|mut v| {
compress: options
.compress
.clone()
.unwrap_as_option(|default| match default {
Some(true) => Some(Default::default()),
_ => None,
})
.map(|mut v| {
if v.const_to_let.is_none() {
v.const_to_let = Some(true);
}
@ -361,7 +367,13 @@ impl VisitMut for MinifierPass {
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()
};

View File

@ -3,7 +3,6 @@ use std::{
cell::RefCell,
collections::{HashMap, HashSet},
env, fmt,
hash::BuildHasher,
path::{Path, PathBuf},
rc::Rc as RustRc,
sync::Arc,
@ -15,6 +14,7 @@ use dashmap::DashMap;
use either::Either;
use indexmap::IndexMap;
use once_cell::sync::Lazy;
use rustc_hash::FxHashMap;
use serde::{
de::{Unexpected, Visitor},
Deserialize, Deserializer, Serialize, Serializer,
@ -28,6 +28,10 @@ use swc_common::{
errors::Handler,
FileName, Mark, SourceMap, SyntaxContext,
};
use swc_config::{
config_types::{BoolConfig, BoolOr, BoolOrDataConfig},
merge::Merge,
};
use swc_ecma_ast::{EsVersion, Expr, Program};
use swc_ecma_ext_transforms::jest;
use swc_ecma_lints::{
@ -40,7 +44,7 @@ use swc_ecma_loader::{
};
use swc_ecma_minifier::option::{
terser::{TerserCompressorOptions, TerserEcmaVersion, TerserTopLevelOptions},
MangleOptions, ManglePropertiesOptions,
MangleOptions,
};
#[allow(deprecated)]
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_visit::{Fold, VisitMutWith};
use self::util::BoolOrObject;
use crate::{
builder::PassBuilder,
dropped_comments_preserver::dropped_comments_preserver,
@ -71,7 +74,6 @@ use crate::{
#[cfg(test)]
mod tests;
pub mod util;
#[derive(Clone, Debug, Copy)]
pub enum IsModule {
@ -269,7 +271,6 @@ impl Options {
output_path: Option<&Path>,
source_file_name: Option<String>,
handler: &Handler,
is_module: IsModule,
config: Option<Config>,
comments: Option<&'a SingleThreadedComments>,
custom_before_pass: impl FnOnce(&Program) -> P,
@ -277,11 +278,12 @@ impl Options {
where
P: 'a + swc_ecma_visit::Fold,
{
let mut config = config.unwrap_or_default();
config.merge(&self.config);
let mut cfg = self.config.clone();
cfg.merge(config.unwrap_or_default());
let is_module = self.is_module;
let mut source_maps = self.source_maps.clone();
source_maps.merge(&config.source_maps);
source_maps.merge(cfg.source_maps.clone());
let JscConfig {
assumptions,
@ -298,7 +300,11 @@ impl Options {
lints,
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(|| {
if loose {
@ -338,7 +344,10 @@ impl Options {
js_minify = js_minify.map(|c| {
let compress = c
.compress
.into_obj()
.unwrap_as_option(|default| match default {
Some(true) => Some(Default::default()),
_ => None,
})
.map(|mut c| {
if c.toplevel.is_none() {
c.toplevel = Some(TerserTopLevelOptions::Bool(true));
@ -346,19 +355,22 @@ impl Options {
c
})
.map(BoolOrObject::Obj)
.unwrap_or(BoolOrObject::Bool(false));
.map(BoolOrDataConfig::from_obj)
.unwrap_or_else(|| BoolOrDataConfig::from_bool(false));
let mangle = c
.mangle
.into_obj()
.unwrap_as_option(|default| match default {
Some(true) => Some(Default::default()),
_ => None,
})
.map(|mut c| {
c.top_level = true;
c
})
.map(BoolOrObject::Obj)
.unwrap_or(BoolOrObject::Bool(false));
.map(BoolOrDataConfig::from_obj)
.unwrap_or_else(|| BoolOrDataConfig::from_bool(false));
JsMinifyOptions {
compress,
@ -371,9 +383,21 @@ impl Options {
let regenerator = transform.regenerator.clone();
let preserve_comments = if preserve_all_comments {
Some(BoolOrObject::from(true))
BoolOr::Bool(true)
} 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() {
@ -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 = {
if let Some(opts) = optimizer.and_then(|o| o.globals) {
@ -439,18 +466,18 @@ impl Options {
Some(hygiene::Config { keep_class_names })
})
.fixer(!self.disable_fixer)
.preset_env(config.env)
.preset_env(cfg.env)
.regenerator(regenerator)
.finalize(
base_url,
paths.into_iter().collect(),
base,
syntax,
config.module,
cfg.module,
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
// 1. filesystem access for the cache
@ -570,15 +597,15 @@ impl Options {
Ok(BuiltInput {
program,
minify: config.minify,
minify: cfg.minify.into_bool(),
pass,
external_helpers,
syntax,
target: es_version,
is_module,
source_maps: source_maps.unwrap_or(SourceMapsConfig::Bool(false)),
inline_sources_content: config.inline_sources_content,
input_source_map: config.input_source_map.clone(),
inline_sources_content: cfg.inline_sources_content.into_bool(),
input_source_map: cfg.input_source_map.clone().unwrap_or_default(),
output_path: output_path.map(|v| v.to_path_buf()),
source_file_name,
comments: comments.cloned(),
@ -648,17 +675,8 @@ impl Default for Rc {
exclude: Some(FileMatcher::Regex("\\.tsx?$".into())),
jsc: JscConfig {
syntax: Some(Default::default()),
transform: None,
external_helpers: false,
target: Default::default(),
loose: false,
keep_class_names: false,
..Default::default()
},
module: None,
minify: false,
source_maps: None,
input_source_map: InputSourceMap::default(),
..Default::default()
},
Config {
@ -670,17 +688,8 @@ impl Default for Rc {
tsx: true,
..Default::default()
})),
transform: None,
external_helpers: false,
target: Default::default(),
loose: false,
keep_class_names: false,
..Default::default()
},
module: None,
minify: false,
source_maps: None,
input_source_map: InputSourceMap::default(),
..Default::default()
},
Config {
@ -692,17 +701,8 @@ impl Default for Rc {
tsx: false,
..Default::default()
})),
transform: None,
external_helpers: false,
target: Default::default(),
loose: false,
keep_class_names: false,
..Default::default()
},
module: None,
minify: false,
source_maps: None,
input_source_map: InputSourceMap::default(),
..Default::default()
},
])
@ -747,7 +747,7 @@ impl Rc {
}
/// 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")]
pub struct Config {
#[serde(default)]
@ -766,17 +766,17 @@ pub struct Config {
pub module: Option<ModuleConfig>,
#[serde(default)]
pub minify: bool,
pub minify: BoolConfig<false>,
#[serde(default)]
pub input_source_map: InputSourceMap,
pub input_source_map: Option<InputSourceMap>,
/// Possible values are: `'inline'`, `true`, `false`.
#[serde(default)]
pub source_maps: Option<SourceMapsConfig>,
#[serde(default = "true_by_default")]
pub inline_sources_content: bool,
#[serde(default)]
pub inline_sources_content: BoolConfig<true>,
#[serde(default)]
pub error: ErrorConfig,
@ -790,10 +790,10 @@ pub struct Config {
#[serde(deny_unknown_fields, rename_all = "camelCase")]
pub struct JsMinifyOptions {
#[serde(default)]
pub compress: BoolOrObject<TerserCompressorOptions>,
pub compress: BoolOrDataConfig<TerserCompressorOptions>,
#[serde(default)]
pub mangle: BoolOrObject<MangleOptions>,
pub mangle: BoolOrDataConfig<MangleOptions>,
#[serde(default)]
pub format: JsMinifyFormatOptions,
@ -817,7 +817,7 @@ pub struct JsMinifyOptions {
pub toplevel: bool,
#[serde(default)]
pub source_map: BoolOrObject<TerserSourceMapOption>,
pub source_map: BoolOrDataConfig<TerserSourceMapOption>,
#[serde(default)]
pub output_path: Option<String>,
@ -864,7 +864,7 @@ pub struct JsMinifyFormatOptions {
pub braces: bool,
#[serde(default)]
pub comments: BoolOrObject<JsMinifyCommentOption>,
pub comments: BoolOrDataConfig<JsMinifyCommentOption>,
/// Not implemented yet.
#[serde(default)]
@ -872,7 +872,7 @@ pub struct JsMinifyFormatOptions {
/// Not implemented yet.
#[serde(default, alias = "indent_level")]
pub indent_level: usize,
pub indent_level: Option<usize>,
/// Not implemented yet.
#[serde(default, alias = "indent_start")]
@ -892,7 +892,7 @@ pub struct JsMinifyFormatOptions {
/// Not implemented yet.
#[serde(default, alias = "max_line_len")]
pub max_line_len: BoolOrObject<usize>,
pub max_line_len: usize,
/// Not implemented yet.
#[serde(default)]
@ -1054,13 +1054,13 @@ pub struct BuiltInput<P: swc_ecma_visit::Fold> {
pub source_file_name: Option<String>,
pub comments: Option<SingleThreadedComments>,
pub preserve_comments: Option<BoolOrObject<JsMinifyCommentOption>>,
pub preserve_comments: BoolOr<JsMinifyCommentOption>,
pub inline_sources_content: bool,
}
/// `jsc` in `.swcrc`.
#[derive(Debug, Default, Clone, Serialize, Deserialize)]
#[derive(Debug, Default, Clone, Serialize, Deserialize, Merge)]
#[serde(deny_unknown_fields, rename_all = "camelCase")]
pub struct JscConfig {
#[serde(default)]
@ -1073,16 +1073,16 @@ pub struct JscConfig {
pub transform: Option<TransformConfig>,
#[serde(default)]
pub external_helpers: bool,
pub external_helpers: BoolConfig<false>,
#[serde(default)]
pub target: Option<EsVersion>,
#[serde(default)]
pub loose: bool,
pub loose: BoolConfig<false>,
#[serde(default)]
pub keep_class_names: bool,
pub keep_class_names: BoolConfig<false>,
#[serde(default)]
pub base_url: PathBuf,
@ -1100,11 +1100,11 @@ pub struct JscConfig {
pub lints: LintConfig,
#[serde(default)]
pub preserve_all_comments: bool,
pub preserve_all_comments: BoolConfig<false>,
}
/// `jsc.experimental` in `.swcrc`
#[derive(Debug, Default, Clone, Serialize, Deserialize)]
#[derive(Debug, Default, Clone, Serialize, Deserialize, Merge)]
#[serde(deny_unknown_fields, rename_all = "camelCase")]
pub struct JscExperimental {
/// This requires cargo feature `plugin`.
@ -1112,7 +1112,7 @@ pub struct JscExperimental {
pub plugins: Option<Vec<PluginConfig>>,
/// If true, keeps import assertions in the output.
#[serde(default)]
pub keep_import_assertions: bool,
pub keep_import_assertions: BoolConfig<false>,
/// Location where swc may stores its intermediate cache.
/// Currently this is only being used for wasm plugin's bytecache.
/// Path should be absolute directory, which will be created if not exist.
@ -1122,19 +1122,6 @@ pub struct JscExperimental {
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`.
pub type Paths = IndexMap<String, Vec<String>, ahash::RandomState>;
pub(crate) type CompiledPaths = Vec<(String, Vec<String>)>;
@ -1296,21 +1283,21 @@ pub struct HiddenTransformConfig {
pub jest: bool,
}
#[derive(Debug, Default, Clone, Serialize, Deserialize)]
#[derive(Debug, Default, Clone, Serialize, Deserialize, Merge)]
#[serde(deny_unknown_fields, rename_all = "camelCase")]
pub struct ConstModulesConfig {
#[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")]
pub struct OptimizerConfig {
#[serde(default)]
pub globals: Option<GlobalPassOption>,
#[serde(default = "true_by_default")]
pub simplify: bool,
#[serde(default)]
pub simplify: BoolConfig<true>,
#[serde(default)]
pub jsonify: Option<JsonifyOption>,
@ -1327,15 +1314,10 @@ fn default_jsonify_min_cost() -> usize {
1024
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
#[derive(Debug, Default, Clone, Copy, Serialize, Deserialize, Merge)]
#[serde(deny_unknown_fields, rename_all = "camelCase")]
pub struct ErrorConfig {
pub filename: bool,
}
impl Default for ErrorConfig {
fn default() -> Self {
ErrorConfig { filename: true }
}
pub filename: BoolConfig<true>,
}
#[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> {
static CACHE: Lazy<DashMap<(PathBuf, CompiledPaths), SwcImportResolver, ahash::RandomState>> =
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 atoms::JsWord;
use common::{collections::AHashMap, comments::SingleThreadedComments, errors::HANDLER, Span};
use config::{util::BoolOrObject, IsModule, JsMinifyCommentOption, JsMinifyOptions};
use common::{collections::AHashMap, comments::SingleThreadedComments, errors::HANDLER};
use config::{IsModule, JsMinifyCommentOption, JsMinifyOptions};
use json_comments::StripComments;
use once_cell::sync::Lazy;
use serde::Serialize;
@ -135,6 +135,8 @@ use swc_common::{
sync::Lrc,
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_codegen::{self, text_writer::WriteJs, Emitter, Node};
use swc_ecma_loader::resolvers::{
@ -159,7 +161,7 @@ use swc_timer::timer;
pub use crate::builder::PassBuilder;
use crate::config::{
BuiltInput, Config, ConfigFile, InputSourceMap, Merge, Options, Rc, RootMode, SourceMapsConfig,
BuiltInput, Config, ConfigFile, InputSourceMap, Options, Rc, RootMode, SourceMapsConfig,
};
mod builder;
@ -584,74 +586,14 @@ impl SourceMapGenConfig for SwcSourceMapConfig<'_> {
}
}
pub fn minify_global_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(
pub(crate) fn minify_file_comments(
comments: &SingleThreadedComments,
minify: bool,
preserve_comments: Option<BoolOrObject<JsMinifyCommentOption>>,
preserve_comments: BoolOr<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) => {}
BoolOr::Bool(true) | BoolOr::Data(JsMinifyCommentOption::PreserveAllComments) => {}
BoolOrObject::Obj(JsMinifyCommentOption::PreserveSomeComments) => {
BoolOr::Data(JsMinifyCommentOption::PreserveSomeComments) => {
let preserve_excl = |_: &BytePos, vc: &mut Vec<Comment>| -> bool {
// Preserve license comments.
if vc.iter().any(|c| c.text.contains("@license")) {
@ -667,7 +609,7 @@ pub fn minify_file_comments(
t.retain(preserve_excl);
}
BoolOrObject::Bool(false) => {
BoolOr::Bool(false) => {
let (mut l, mut t) = comments.borrow_all_mut();
l.clear();
t.clear();
@ -725,7 +667,7 @@ impl Compiler {
.context("failed to process 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 {
@ -831,7 +773,6 @@ impl Compiler {
opts.output_path.as_deref(),
opts.source_file_name.clone(),
handler,
opts.is_module,
Some(config),
comments,
before_pass,
@ -963,10 +904,10 @@ impl Compiler {
let target = opts.ecma.clone().into();
let (source_map, orig) = match &opts.source_map {
BoolOrObject::Bool(false) => (SourceMapsConfig::Bool(false), None),
BoolOrObject::Bool(true) => (SourceMapsConfig::Bool(true), None),
BoolOrObject::Obj(obj) => {
let (source_map, orig) = opts
.source_map
.as_ref()
.map(|obj| -> Result<_, Error> {
let orig = obj
.content
.as_ref()
@ -975,17 +916,32 @@ impl Compiler {
Some(v) => Some(v?),
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 {
compress: opts
.compress
.clone()
.into_obj()
.unwrap_as_option(|default| match default {
Some(true) | None => Some(Default::default()),
_ => None,
})
.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()
};
@ -1065,7 +1021,13 @@ impl Compiler {
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(
&module,
@ -1135,7 +1097,7 @@ impl Compiler {
});
if let Some(comments) = &config.comments {
minify_file_comments(comments, config.minify, config.preserve_comments);
minify_file_comments(comments, config.preserve_comments);
}
self.print(

View File

@ -8,7 +8,7 @@ use std::{
use anyhow::{bail, Context, Error};
use swc::{
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_ecma_ast::EsVersion;
@ -84,12 +84,12 @@ fn create_matrix(entry: &Path) -> Vec<Options> {
jsc: JscConfig {
syntax: Some(syntax),
transform: None,
external_helpers,
external_helpers: external_helpers.into(),
target: Some(target),
minify: if minify {
Some(JsMinifyOptions {
compress: true.into(),
mangle: true.into(),
compress: BoolOrDataConfig::from_bool(true),
mangle: BoolOrDataConfig::from_bool(true),
format: Default::default(),
ecma: Default::default(),
keep_classnames: Default::default(),
@ -107,7 +107,7 @@ fn create_matrix(entry: &Path) -> Vec<Options> {
..Default::default()
},
module: Some(ModuleConfig::CommonJs(Default::default())),
minify,
minify: minify.into(),
..Default::default()
},
source_maps: if source_map {

View File

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

View File

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

View File

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

View File

@ -137,9 +137,9 @@ fn compile(input: &Path, output: &Path, opts: Options) {
tsx: input.to_string_lossy().ends_with(".tsx"),
decorators: true,
dts: false,
no_early_errors: true,
no_early_errors: false,
})),
external_helpers: true,
external_helpers: true.into(),
..opts.config.jsc
},
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 = [
"concurrent",
] }
swc_config = { version = "0.1.0", path = "../swc_config" }
swc_ecma_ast = { version = "0.77.0", path = "../swc_ecma_ast" }
swc_ecma_utils = { version = "0.83.0", path = "../swc_ecma_utils" }
swc_ecma_visit = { version = "0.63.0", path = "../swc_ecma_visit" }

View File

@ -1,6 +1,7 @@
use std::fmt::Debug;
use serde::{Deserialize, Serialize};
use swc_config::merge::Merge;
#[cfg(feature = "non_critical_lints")]
use crate::rules::non_critical_lints::{
@ -32,11 +33,12 @@ impl Default for LintRuleReaction {
enum LintRuleLevel {
Str(LintRuleReaction),
Number(u8),
Unspecified,
}
impl Default for LintRuleLevel {
fn default() -> Self {
Self::Str(LintRuleReaction::Off)
Self::Unspecified
}
}
@ -49,6 +51,7 @@ impl From<LintRuleLevel> for LintRuleReaction {
2 => LintRuleReaction::Error,
_ => 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]
#[serde(rename_all = "kebab-case")]
pub struct LintConfig {

View File

@ -37,6 +37,7 @@ serde_json = "1.0.61"
swc_atoms = { version = "0.2", path = "../swc_atoms" }
swc_cached = { version = "0.1.0", path = "../swc_cached" }
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_codegen = { version = "0.106.0", path = "../swc_ecma_codegen" }
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_cached::regex::CachedRegex;
use swc_common::{collections::AHashMap, Mark};
use swc_config::merge::Merge;
use swc_ecma_ast::{EsVersion, Expr};
pub mod terser;
@ -68,13 +69,13 @@ pub struct MangleOptions {
pub reserved: Vec<JsWord>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[derive(Debug, Clone, Default, Serialize, Deserialize, Merge)]
#[serde(rename_all = "camelCase")]
pub struct ManglePropertiesOptions {
#[serde(default, alias = "reserved")]
pub reserved: Vec<JsWord>,
#[serde(default, alias = "undeclared")]
pub undeclared: bool,
pub undeclared: Option<bool>,
#[serde(default)]
pub regex: Option<CachedRegex>,
}

View File

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

View File

@ -22,6 +22,7 @@ dashmap = "5.1.0"
indexmap = "1.6.1"
once_cell = "1.10.0"
rayon = { version = "1.5.1", optional = true }
rustc-hash = "1.1.0"
serde_json = "1.0.61"
swc_atoms = { version = "0.2", path = "../swc_atoms" }
swc_common = { version = "0.17.23", path = "../swc_common" }

View File

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

View File

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

View File

@ -1,9 +1,10 @@
extern crate proc_macro;
use pmutil::synom_ext::FromSpan;
#[cfg(procmacro2_semver_exempt)]
use pmutil::SpanExt;
use pmutil::{synom_ext::FromSpan, Quote, SpanExt};
use proc_macro2::Span;
use quote::ToTokens;
use syn::*;
pub mod binder;
@ -78,6 +79,32 @@ pub fn doc_str(attr: &Attribute) -> Option<String> {
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.
#[macro_export]
macro_rules! fail {

View File

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

View File

@ -6,9 +6,7 @@ it("should compress", async () => {
console.log(foo)
`);
expect(code).toMatchInlineSnapshot(
`"import foo from\\"@src/app\\";console.log(foo)"`
);
expect(code).toMatchInlineSnapshot(`"import a from\\"@src/app\\";console.log(a)"`);
});
it("should accept object", async () => {
@ -20,9 +18,7 @@ it("should accept object", async () => {
{}
);
expect(code).toMatchInlineSnapshot(
`"import foo from\\"@src/app\\";console.log(foo)"`
);
expect(code).toMatchInlineSnapshot(`"import a from\\"@src/app\\";console.log(a)"`);
});
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(
__dirname + "/../../tests/issue-2546/input.ts"
__dirname + "/../../tests/issue-3834/input.js"
);
const { code } = await swc.transformFile(filename, {
jsc: {
parser: {
syntax: "typescript",
}
externalHelpers: false,
}
})
expect(code).not.toBeFalsy()
expect(code).toContain('function _classCallCheck')
});

View File

@ -5,7 +5,7 @@
export interface TransformOutput {
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 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` */
export interface TransformOutput {
code: string
map?: string | undefined | null
map?: string
}
export type JsCompiler = 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 {
}