swc_html_minifier/
option.rs

1use serde::{de::DeserializeOwned, Deserialize, Serialize};
2use swc_config::regex::CachedRegex;
3#[cfg(feature = "default-css-minifier")]
4use swc_css_codegen::CodegenConfig as CssCodegenOptions;
5#[cfg(feature = "default-css-minifier")]
6use swc_css_minifier::options::MinifyOptions as CssMinifyOptions;
7#[cfg(feature = "default-css-minifier")]
8use swc_css_parser::parser::ParserConfig as CssParserOptions;
9use swc_ecma_ast::EsVersion;
10use swc_ecma_codegen::Config as JsCodegenOptions;
11use swc_ecma_minifier::option::MinifyOptions as JsMinifyOptions;
12use swc_ecma_parser::Syntax;
13
14#[derive(Debug, Clone, Serialize, Deserialize)]
15#[serde(rename_all = "kebab-case")]
16#[serde(deny_unknown_fields)]
17pub enum MinifierType {
18    JsModule,
19    JsScript,
20    Json,
21    Css,
22    Html,
23}
24
25#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
26#[serde(deny_unknown_fields)]
27#[serde(rename_all = "kebab-case")]
28pub enum CollapseWhitespaces {
29    /// Keep whitespaces
30    None,
31    /// Remove all whitespaces
32    All,
33    /// Remove and collapse whitespaces based on the `display` CSS property
34    Smart,
35    /// Collapse multiple whitespace into one whitespace, remove
36    /// all whitespace in the `head` element and trim whitespaces for the `body`
37    /// element
38    Conservative,
39    /// Collapse multiple whitespace into one whitespace, remove
40    /// all whitespace in the `head` element, trim whitespaces for the `body`
41    /// element and remove spaces between `metadata` elements (i.e.
42    /// `script`/`style`/etc, for elements that have `display: none`)
43    AdvancedConservative,
44    /// Remove all whitespace in the `head` element, trim whitespaces for the
45    /// `body` element, remove spaces between `metadata` elements (i.e.
46    /// `script`/`style`/etc, for elements that have `display: none`)
47    #[default]
48    OnlyMetadata,
49}
50
51#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
52#[serde(deny_unknown_fields)]
53#[serde(rename_all = "kebab-case")]
54pub enum RemoveRedundantAttributes {
55    /// Do not remove redundant attributes
56    None,
57    /// Remove all redundant attributes
58    All,
59    /// Remove deprecated and svg redundant (they used for styling) and `xmlns`
60    /// attributes (for example the `type` attribute for the `style` tag and
61    /// `xmlns` for svg)
62    #[default]
63    Smart,
64}
65
66#[derive(Debug, Serialize, Deserialize, Clone)]
67#[serde(deny_unknown_fields)]
68#[serde(untagged)]
69pub enum MinifyJsonOption {
70    Bool(bool),
71    Options(Box<JsonOptions>),
72}
73
74#[derive(Debug, Serialize, Deserialize, Clone)]
75#[serde(rename_all = "camelCase")]
76#[serde(deny_unknown_fields)]
77pub struct JsonOptions {
78    pub pretty: bool,
79}
80
81#[derive(Debug, Serialize, Deserialize, Clone)]
82#[serde(deny_unknown_fields)]
83#[serde(untagged)]
84pub enum MinifyJsOption {
85    Bool(bool),
86    Options(Box<JsOptions>),
87}
88
89#[derive(Debug, Serialize, Deserialize, Clone)]
90#[serde(rename_all = "camelCase")]
91#[serde(deny_unknown_fields)]
92pub struct JsOptions {
93    #[serde(default)]
94    pub parser: JsParserOptions,
95    #[serde(default)]
96    pub minifier: JsMinifyOptions,
97    #[serde(default)]
98    pub codegen: JsCodegenOptions,
99}
100
101#[derive(Default, Debug, Serialize, Deserialize, Clone)]
102#[serde(rename_all = "camelCase")]
103pub struct JsParserOptions {
104    #[serde(default)]
105    pub comments: bool,
106    #[serde(flatten)]
107    pub syntax: Syntax,
108    #[serde(default)]
109    pub target: EsVersion,
110}
111
112#[derive(Debug, Serialize, Deserialize, Clone)]
113#[serde(deny_unknown_fields)]
114#[serde(untagged)]
115pub enum MinifyCssOption<CO> {
116    Bool(bool),
117    Options(CO),
118}
119
120#[cfg(feature = "default-css-minifier")]
121#[derive(Debug, Serialize, Deserialize, Clone)]
122#[serde(rename_all = "camelCase")]
123#[serde(deny_unknown_fields)]
124pub struct CssOptions {
125    #[serde(default)]
126    pub parser: CssParserOptions,
127    #[serde(default)]
128    pub minifier: CssMinifyOptions,
129    #[serde(default)]
130    pub codegen: CssCodegenOptions,
131}
132
133#[derive(Debug, Clone, Serialize, Deserialize)]
134#[serde(rename_all = "camelCase")]
135#[serde(deny_unknown_fields)]
136pub struct MinifyOptions<CO> {
137    #[serde(default)]
138    pub force_set_html5_doctype: bool,
139    #[serde(default)]
140    pub collapse_whitespaces: CollapseWhitespaces,
141    // Remove safe empty elements with metadata content, i.e. the `script` and `style` element
142    // without content and attributes, `meta` and `link` elements without attributes and etc
143    #[serde(default = "true_by_default")]
144    pub remove_empty_metadata_elements: bool,
145    #[serde(default = "true_by_default")]
146    pub remove_comments: bool,
147    #[serde(default = "default_preserve_comments")]
148    pub preserve_comments: Option<Vec<CachedRegex>>,
149    #[serde(default = "true_by_default")]
150    pub minify_conditional_comments: bool,
151    /// Prevent to remove empty attributes, by default we only remove attributes
152    /// that are safe to remove (for example - empty a `style` attribute),
153    /// but in edge cases it can be unsafe because some libraries can
154    /// interact with DOM like with strings (i.e. don't use DOM API) and in this
155    /// case strings will be different, which can break the work of
156    /// libraries
157    #[serde(default = "true_by_default")]
158    pub remove_empty_attributes: bool,
159    #[serde(default)]
160    pub remove_redundant_attributes: RemoveRedundantAttributes,
161    #[serde(default = "true_by_default")]
162    pub collapse_boolean_attributes: bool,
163    /// Merge the same metadata elements into one (for example, consecutive
164    /// `style` elements will be merged into one `style` element)
165    #[serde(default = "true_by_default")]
166    pub merge_metadata_elements: bool,
167    /// Remove extra whitespace in space and comma separated attribute values
168    /// (where it is safe) and remove `javascript:` prefix for event handler
169    /// attributes
170    #[serde(default = "true_by_default")]
171    pub normalize_attributes: bool,
172    #[serde(default = "minify_json_by_default")]
173    pub minify_json: MinifyJsonOption,
174    #[serde(default = "minify_js_by_default")]
175    pub minify_js: MinifyJsOption,
176    #[serde(default = "minify_css_by_default")]
177    pub minify_css: MinifyCssOption<CO>,
178    // Allow to compress value of custom script elements,
179    // i.e. `<script type="text/html"><div><!-- text --> <div data-foo="bar> Text </div></script>`
180    //
181    // The first item is tag_name
182    // The second is attribute name
183    // The third is type of minifier
184    #[serde(default)]
185    pub minify_additional_scripts_content: Option<Vec<(CachedRegex, MinifierType)>>,
186    /// Allow to compress value of custom attributes,
187    /// i.e. `<div data-js="myFunction(100 * 2, 'foo' + 'bar')"></div>`
188    ///
189    /// The first item is tag_name
190    /// The second is attribute name
191    /// The third is type of minifier
192    pub minify_additional_attributes: Option<Vec<(CachedRegex, MinifierType)>>,
193    /// Sorting the values of `class`, `rel`, etc. of attributes
194    #[serde(default = "true_by_default")]
195    pub sort_space_separated_attribute_values: bool,
196    #[serde(default)]
197    pub sort_attributes: bool,
198}
199
200/// Implement default using serde.
201impl<CO: DeserializeOwned> Default for MinifyOptions<CO> {
202    fn default() -> Self {
203        serde_json::from_value(serde_json::Value::Object(Default::default())).unwrap()
204    }
205}
206
207const fn true_by_default() -> bool {
208    true
209}
210
211const fn minify_json_by_default() -> MinifyJsonOption {
212    MinifyJsonOption::Bool(true)
213}
214
215const fn minify_js_by_default() -> MinifyJsOption {
216    MinifyJsOption::Bool(true)
217}
218
219const fn minify_css_by_default<CO>() -> MinifyCssOption<CO> {
220    MinifyCssOption::Bool(true)
221}
222
223fn default_preserve_comments() -> Option<Vec<CachedRegex>> {
224    Some(vec![
225        // License comments
226        CachedRegex::new("@preserve").unwrap(),
227        CachedRegex::new("@copyright").unwrap(),
228        CachedRegex::new("@lic").unwrap(),
229        CachedRegex::new("@cc_on").unwrap(),
230        // Allow to keep custom comments
231        CachedRegex::new("^!").unwrap(),
232        // Server-side comments
233        CachedRegex::new("^\\s*#").unwrap(),
234        // Conditional IE comments
235        CachedRegex::new("^\\[if\\s[^\\]+]").unwrap(),
236        CachedRegex::new("\\[endif]").unwrap(),
237    ])
238}