From cada50b01746978d7c2c16fd03c3a6672aeed118 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Donny/=EA=B0=95=EB=8F=99=EC=9C=A4?= Date: Fri, 7 Jun 2024 16:17:05 +0900 Subject: [PATCH] feat(es/minifier): Support `module: "unknown"` (#9026) **Related issue:** - Closes #8571 --- .../binding_core_wasm/__tests__/simple.js | 6 +- crates/swc/src/lib.rs | 62 +++++++-------- crates/swc/tests/projects.rs | 2 +- crates/swc_compiler_base/src/lib.rs | 78 +------------------ crates/swc_config/src/lib.rs | 2 + crates/swc_config/src/module.rs | 76 ++++++++++++++++++ crates/swc_ecma_minifier/src/js.rs | 10 ++- .../core/__tests__/minify/issue_8437_test.mjs | 3 +- packages/core/__tests__/minify_test.mjs | 4 +- packages/types/index.ts | 2 +- packages/types/package.json | 2 +- 11 files changed, 132 insertions(+), 115 deletions(-) create mode 100644 crates/swc_config/src/module.rs diff --git a/bindings/binding_core_wasm/__tests__/simple.js b/bindings/binding_core_wasm/__tests__/simple.js index fc650c5c4ae..805e79f7bd9 100644 --- a/bindings/binding_core_wasm/__tests__/simple.js +++ b/bindings/binding_core_wasm/__tests__/simple.js @@ -190,7 +190,8 @@ describe("parse", () => { describe("minify", () => { it("should work", () => { const output = swc.minifySync( - "const somename = 1; console.log(somename);" + "const somename = 1; console.log(somename);", + { module: false } ); expect(output).toMatchInlineSnapshot(` @@ -202,7 +203,8 @@ describe("minify", () => { it("should work with async facade", async () => { const output = await swc.minify( - "const somename = 1; console.log(somename);" + "const somename = 1; console.log(somename);", + { module: false } ); expect(output).toMatchInlineSnapshot(` diff --git a/crates/swc/src/lib.rs b/crates/swc/src/lib.rs index 0ead40dfde4..3a4b5217551 100644 --- a/crates/swc/src/lib.rs +++ b/crates/swc/src/lib.rs @@ -804,7 +804,35 @@ impl Compiler { // https://github.com/swc-project/swc/issues/2254 - if opts.module { + if opts.keep_fnames { + if let Some(opts) = &mut min_opts.compress { + opts.keep_fnames = true; + } + if let Some(opts) = &mut min_opts.mangle { + opts.keep_fn_names = true; + } + } + + let comments = SingleThreadedComments::default(); + + let program = self + .parse_js( + fm.clone(), + handler, + target, + Syntax::Es(EsConfig { + jsx: true, + decorators: true, + decorators_before_export: true, + import_attributes: true, + ..Default::default() + }), + opts.module, + Some(&comments), + ) + .context("failed to parse input file")?; + + if program.is_module() { if let Some(opts) = &mut min_opts.compress { if opts.top_level.is_none() { opts.top_level = Some(TopLevelOptions { functions: true }); @@ -818,40 +846,12 @@ impl Compiler { } } - if opts.keep_fnames { - if let Some(opts) = &mut min_opts.compress { - opts.keep_fnames = true; - } - if let Some(opts) = &mut min_opts.mangle { - opts.keep_fn_names = true; - } - } - - let comments = SingleThreadedComments::default(); - - let module = self - .parse_js( - fm.clone(), - handler, - target, - Syntax::Es(EsConfig { - jsx: true, - decorators: true, - decorators_before_export: true, - import_attributes: true, - ..Default::default() - }), - IsModule::Bool(opts.module), - Some(&comments), - ) - .context("failed to parse input file")?; - let source_map_names = if source_map.enabled() { let mut v = swc_compiler_base::IdentCollector { names: Default::default(), }; - module.visit_with(&mut v); + program.visit_with(&mut v); v.names } else { @@ -864,7 +864,7 @@ impl Compiler { let is_mangler_enabled = min_opts.mangle.is_some(); let module = self.run_transform(handler, false, || { - let module = module.fold_with(&mut paren_remover(Some(&comments))); + let module = program.fold_with(&mut paren_remover(Some(&comments))); let module = module.fold_with(&mut resolver(unresolved_mark, top_level_mark, false)); diff --git a/crates/swc/tests/projects.rs b/crates/swc/tests/projects.rs index 2bc29f0d8d2..a0c7388c49e 100644 --- a/crates/swc/tests/projects.rs +++ b/crates/swc/tests/projects.rs @@ -1107,7 +1107,7 @@ fn issue_7513_2() { fm, handler, &JsMinifyOptions { - module: true, + module: IsModule::Bool(true), compress: BoolOrDataConfig::from_bool(true), mangle: BoolOrDataConfig::from_obj(MangleOptions { props: None, diff --git a/crates/swc_compiler_base/src/lib.rs b/crates/swc_compiler_base/src/lib.rs index bd202ac7c82..7d43309dec9 100644 --- a/crates/swc_compiler_base/src/lib.rs +++ b/crates/swc_compiler_base/src/lib.rs @@ -1,15 +1,12 @@ use std::{ - env, fmt, + env, path::{Path, PathBuf}, }; use anyhow::{Context, Error}; use base64::prelude::{Engine, BASE64_STANDARD}; use once_cell::sync::Lazy; -use serde::{ - de::{Unexpected, Visitor}, - Deserialize, Deserializer, Serialize, Serializer, -}; +use serde::{Deserialize, Serialize}; use swc_atoms::JsWord; use swc_common::{ collections::AHashMap, @@ -19,7 +16,8 @@ use swc_common::{ sync::Lrc, BytePos, FileName, SourceFile, SourceMap, }; -use swc_config::{config_types::BoolOr, merge::Merge}; +use swc_config::config_types::BoolOr; +pub use swc_config::IsModule; use swc_ecma_ast::{EsVersion, Ident, Program}; use swc_ecma_codegen::{text_writer::WriteJs, Emitter, Node}; use swc_ecma_minifier::js::JsMinifyCommentOption; @@ -384,74 +382,6 @@ impl Default for SourceMapsConfig { } } -#[derive(Clone, Debug, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub enum IsModule { - Bool(bool), - Unknown, -} - -impl Default for IsModule { - fn default() -> Self { - IsModule::Bool(true) - } -} - -impl Merge for IsModule { - fn merge(&mut self, other: Self) { - if *self == Default::default() { - *self = other; - } - } -} - -impl Serialize for IsModule { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - match *self { - IsModule::Bool(ref b) => b.serialize(serializer), - IsModule::Unknown => "unknown".serialize(serializer), - } - } -} - -impl<'de> Deserialize<'de> for IsModule { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - struct IsModuleVisitor; - - impl<'de> Visitor<'de> for IsModuleVisitor { - type Value = IsModule; - - fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { - formatter.write_str("a boolean or the string 'unknown'") - } - - fn visit_bool(self, b: bool) -> Result - where - E: serde::de::Error, - { - Ok(IsModule::Bool(b)) - } - - fn visit_str(self, s: &str) -> Result - where - E: serde::de::Error, - { - match s { - "unknown" => Ok(IsModule::Unknown), - _ => Err(serde::de::Error::invalid_value(Unexpected::Str(s), &self)), - } - } - } - - deserializer.deserialize_any(IsModuleVisitor) - } -} - pub struct IdentCollector { pub names: AHashMap, } diff --git a/crates/swc_config/src/lib.rs b/crates/swc_config/src/lib.rs index bc7aa5b24ce..0ca3d8cf6d5 100644 --- a/crates/swc_config/src/lib.rs +++ b/crates/swc_config/src/lib.rs @@ -2,10 +2,12 @@ pub mod config_types; pub mod merge; +mod module; #[cfg(feature = "sourcemap")] mod source_map; pub use swc_cached::{regex::CachedRegex, Error}; +pub use crate::module::IsModule; #[cfg(feature = "sourcemap")] pub use crate::source_map::*; diff --git a/crates/swc_config/src/module.rs b/crates/swc_config/src/module.rs new file mode 100644 index 00000000000..61c730df649 --- /dev/null +++ b/crates/swc_config/src/module.rs @@ -0,0 +1,76 @@ +use std::fmt; + +use serde::{ + de::{Unexpected, Visitor}, + Deserialize, Deserializer, Serialize, Serializer, +}; + +use crate::merge::Merge; + +#[derive(Clone, Debug, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub enum IsModule { + Bool(bool), + Unknown, +} + +impl Default for IsModule { + fn default() -> Self { + IsModule::Bool(true) + } +} + +impl Merge for IsModule { + fn merge(&mut self, other: Self) { + if *self == Default::default() { + *self = other; + } + } +} + +impl Serialize for IsModule { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + match *self { + IsModule::Bool(ref b) => b.serialize(serializer), + IsModule::Unknown => "unknown".serialize(serializer), + } + } +} + +struct IsModuleVisitor; + +impl<'de> Visitor<'de> for IsModuleVisitor { + type Value = IsModule; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("a boolean or the string 'unknown'") + } + + fn visit_bool(self, b: bool) -> Result + where + E: serde::de::Error, + { + Ok(IsModule::Bool(b)) + } + + fn visit_str(self, s: &str) -> Result + where + E: serde::de::Error, + { + match s { + "unknown" => Ok(IsModule::Unknown), + _ => Err(serde::de::Error::invalid_value(Unexpected::Str(s), &self)), + } + } +} + +impl<'de> Deserialize<'de> for IsModule { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + deserializer.deserialize_any(IsModuleVisitor) + } +} diff --git a/crates/swc_ecma_minifier/src/js.rs b/crates/swc_ecma_minifier/src/js.rs index 21afc8b7efb..10835b2a914 100644 --- a/crates/swc_ecma_minifier/src/js.rs +++ b/crates/swc_ecma_minifier/src/js.rs @@ -1,7 +1,7 @@ //! NOT A PUBLIC API use serde::{Deserialize, Serialize}; -use swc_config::{config_types::BoolOrDataConfig, SourceMapContent}; +use swc_config::{config_types::BoolOrDataConfig, IsModule, SourceMapContent}; use crate::option::{ terser::{TerserCompressorOptions, TerserEcmaVersion}, @@ -33,8 +33,8 @@ pub struct JsMinifyOptions { #[serde(default, alias = "keep_fnames")] pub keep_fnames: bool, - #[serde(default)] - pub module: bool, + #[serde(default = "default_module")] + pub module: IsModule, #[serde(default)] pub safari10: bool, @@ -210,3 +210,7 @@ pub enum JsMinifyCommentOption { #[serde(rename = "all")] PreserveAllComments, } + +fn default_module() -> IsModule { + IsModule::Bool(false) +} diff --git a/packages/core/__tests__/minify/issue_8437_test.mjs b/packages/core/__tests__/minify/issue_8437_test.mjs index ffa556c356b..a14177a220b 100644 --- a/packages/core/__tests__/minify/issue_8437_test.mjs +++ b/packages/core/__tests__/minify/issue_8437_test.mjs @@ -37,7 +37,8 @@ toFixed(1.2345, 2, Math.round, 1); async function minify() { const { code } = await swc.minify(origin, { compress: true, - mangle: false + mangle: false, + module: false }); return code; } diff --git a/packages/core/__tests__/minify_test.mjs b/packages/core/__tests__/minify_test.mjs index 1250b45e2e5..aff82ccf86c 100644 --- a/packages/core/__tests__/minify_test.mjs +++ b/packages/core/__tests__/minify_test.mjs @@ -260,7 +260,9 @@ it("should accept non-strict code", async () => { a = 1; delete a; console.log(a); - `); + `, { + module: false + }); expect(code).toMatchInlineSnapshot(`"a=1,delete a,console.log(a);"`); }); diff --git a/packages/types/index.ts b/packages/types/index.ts index dbaebf256be..99be5f70afb 100644 --- a/packages/types/index.ts +++ b/packages/types/index.ts @@ -27,7 +27,7 @@ export interface JsMinifyOptions { keep_fnames?: boolean; - module?: boolean; + module?: boolean | "unknown"; safari10?: boolean; diff --git a/packages/types/package.json b/packages/types/package.json index e27814e2c3b..088691c86ca 100644 --- a/packages/types/package.json +++ b/packages/types/package.json @@ -1,7 +1,7 @@ { "name": "@swc/types", "packageManager": "yarn@4.0.2", - "version": "0.1.7", + "version": "0.1.8", "description": "Typings for the swc project.", "sideEffects": false, "scripts": {