Repository: swc-project/swc (rust)

Issue:
IIFE prevents minification?
**Describe the bug**

**Input code**

```js
(() => {
	var LONGNAME = 1;
	console.log(LONGNAME);
})();
```

**Config**

```js
const fs = require("fs");
const { minify } = require("@swc/core");
// const { minify } = require("terser");

let config = {
	sourceMap: { filename: "dist/index.js" },
	toplevel: false,
	module: false,
};

minify(fs.readFileSync("index.js", "utf8"), config).then(({ code, map }) => {
	console.log(code);
});

```

**Expected behavior**
Same minified output as terser:
```js
console.log(1);
```

(Most importantly that the variable names are at least renamed, better inlined, and ideally the now redundant IIFE is removed)

**Current behavior**
Only whitespace is removed
```js
(()=>{var LONGNAME=1;console.log(LONGNAME)})()
```

**Version**
The version of @swc/core: both 1.2.105 and 1.2.106


Pull request description:
fix(es/minifier): Fix default values of `toplevel`
**Description:**


**Related issue (if exists):**


 - Closes #2591

Patch:
diff --git a/crates/swc/src/builder.rs b/crates/swc/src/builder.rs
index dd3c965f4d..48d3a9513f 100644
--- a/crates/swc/src/builder.rs
+++ b/crates/swc/src/builder.rs
@@ -12,7 +12,7 @@ use swc_common::{
     FileName, Mark, SourceMap,
 };
 use swc_ecma_ast::{EsVersion, Module};
-use swc_ecma_minifier::option::MinifyOptions;
+use swc_ecma_minifier::option::{terser::TerserTopLevelOptions, MinifyOptions};
 use swc_ecma_parser::Syntax;
 use swc_ecma_transforms::{
     compat, compat::es2022::private_in_object, fixer, helpers, hygiene,
@@ -342,11 +342,16 @@ 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(|v| v.into_config(self.cm.clone())),
+                compress: options.compress.clone().into_obj().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())
+                }),
                 mangle: options.mangle.clone().into_obj(),
                 ..Default::default()
             };
diff --git a/crates/swc/src/config/mod.rs b/crates/swc/src/config/mod.rs
index a3a86d768d..6ab8448c2a 100644
--- a/crates/swc/src/config/mod.rs
+++ b/crates/swc/src/config/mod.rs
@@ -800,7 +800,7 @@ pub struct JsMinifyOptions {
     #[serde(default)]
     pub safari10: bool,
 
-    #[serde(default)]
+    #[serde(default = "true_by_default")]
     pub toplevel: bool,
 
     #[serde(default)]
diff --git a/crates/swc_ecma_minifier/src/option/mod.rs b/crates/swc_ecma_minifier/src/option/mod.rs
index 0b41d0a499..50fbf4af9b 100644
--- a/crates/swc_ecma_minifier/src/option/mod.rs
+++ b/crates/swc_ecma_minifier/src/option/mod.rs
@@ -43,7 +43,7 @@ pub struct MangleOptions {
     #[serde(default, alias = "properties")]
     pub props: Option<ManglePropertiesOptions>,
 
-    #[serde(default, alias = "toplevel")]
+    #[serde(default = "true_by_default", alias = "toplevel")]
     pub top_level: bool,
 
     #[serde(default, alias = "keep_classnames")]


Test patch:
diff --git a/crates/swc_ecma_minifier/tests/compress.rs b/crates/swc_ecma_minifier/tests/compress.rs
index be9e9f5b17..dd86096fb7 100644
--- a/crates/swc_ecma_minifier/tests/compress.rs
+++ b/crates/swc_ecma_minifier/tests/compress.rs
@@ -76,6 +76,12 @@ fn is_ignored(path: &Path) -> bool {
     false
 }
 
+#[derive(Debug, Default, Clone, Deserialize)]
+struct TopLevelOnly {
+    #[serde(default, alias = "toplevel")]
+    top_level: bool,
+}
+
 #[derive(Debug, Clone, Deserialize)]
 #[serde(deny_unknown_fields)]
 #[serde(untagged)]
@@ -84,6 +90,20 @@ enum TestMangleOptions {
     Normal(MangleOptions),
 }
 
+impl TestMangleOptions {
+    fn parse(s: &str) -> Self {
+        let top_level = serde_json::from_str::<TopLevelOnly>(s).unwrap_or_default();
+
+        let mut data = serde_json::from_str::<Self>(s).expect("failed to deserialize mangle.json");
+
+        if let TestMangleOptions::Normal(v) = &mut data {
+            v.top_level = top_level.top_level;
+        }
+
+        data
+    }
+}
+
 #[derive(Debug, Clone, Deserialize)]
 struct TestOptions {
     #[serde(default)]
@@ -184,7 +204,10 @@ fn run(
             mangle: mangle.and_then(|v| match v {
                 TestMangleOptions::Bool(v) => {
                     if v {
-                        Some(Default::default())
+                        Some(MangleOptions {
+                            top_level: false,
+                            ..Default::default()
+                        })
                     } else {
                         None
                     }
@@ -338,8 +361,7 @@ fn fixture(input: PathBuf) {
             );
         }
 
-        let mangle: Option<TestMangleOptions> =
-            mangle.map(|s| serde_json::from_str(&s).expect("failed to deserialize mangle.json"));
+        let mangle: Option<TestMangleOptions> = mangle.map(|s| TestMangleOptions::parse(&s));
 
         let output = run(cm.clone(), &handler, &input, &config, mangle, false);
         let output_module = match output {