Skip to content

Commit 86186db

Browse files
authored
feat(html/minifier): Support keep_head_and_body (#11008)
**Description:** We should add the option to binding and close that issue after publishing new version and upgrade the `swc_html` in node bindings, **Related issue:** - #10994
1 parent f59f233 commit 86186db

File tree

4 files changed

+103
-38
lines changed

4 files changed

+103
-38
lines changed

.changeset/neat-ravens-applaud.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
swc_html_codegen: major
3+
---
4+
5+
feat(html/minifier): support `keep_head_and_body`

crates/swc_html_codegen/src/lib.rs

Lines changed: 51 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ pub struct CodegenConfig<'a> {
3232
/// Don't print optional tags (only when `minify` enabled)
3333
/// By default `true` when `minify` enabled, otherwise `false`
3434
pub tag_omission: Option<bool>,
35+
/// Keep <head> tags and </body> closing tag when `tag_omission` is enabled
36+
pub keep_head_and_body: Option<bool>,
3537
/// Making SVG and MathML elements self-closing where possible (only when
3638
/// `minify` enabled) By default `false` when `minify` enabled,
3739
/// otherwise `true`
@@ -58,6 +60,7 @@ where
5860
// For legacy `<plaintext>`
5961
is_plaintext: bool,
6062
tag_omission: bool,
63+
keep_head_and_body: bool,
6164
self_closing_void_elements: bool,
6265
quotes: bool,
6366
}
@@ -68,6 +71,7 @@ where
6871
{
6972
pub fn new(wr: W, config: CodegenConfig<'a>) -> Self {
7073
let tag_omission = config.tag_omission.unwrap_or(config.minify);
74+
let keep_head_and_body = config.keep_head_and_body.unwrap_or(false);
7175
let self_closing_void_elements = config.tag_omission.unwrap_or(!config.minify);
7276
let quotes = config.quotes.unwrap_or(!config.minify);
7377

@@ -77,6 +81,7 @@ where
7781
ctx: Default::default(),
7882
is_plaintext: false,
7983
tag_omission,
84+
keep_head_and_body,
8085
self_closing_void_elements,
8186
quotes,
8287
}
@@ -227,49 +232,56 @@ where
227232
// A head element's start tag can be omitted if the element is empty, or if the
228233
// first thing inside the head element is an element.
229234
"head"
230-
if n.children.is_empty()
231-
|| matches!(n.children.first(), Some(Child::Element(..))) =>
235+
if !self.keep_head_and_body
236+
&& (n.children.is_empty()
237+
|| matches!(n.children.first(), Some(Child::Element(..)))) =>
232238
{
233239
true
234240
}
235241
// A body element's start tag can be omitted if the element is empty, or if the
236242
// first thing inside the body element is not ASCII whitespace or a comment, except
237243
// if the first thing inside the body element would be parsed differently outside.
238244
"body"
239-
if n.children.is_empty()
240-
|| (match n.children.first() {
241-
Some(Child::Text(text))
242-
if !text.data.is_empty()
243-
&& text.data.chars().next().unwrap().is_ascii_whitespace() =>
244-
{
245-
false
246-
}
247-
Some(Child::Comment(..)) => false,
248-
Some(Child::Element(Element {
249-
namespace,
250-
tag_name,
251-
..
252-
})) if *namespace == Namespace::HTML
253-
&& matches!(
254-
&**tag_name,
255-
"base"
256-
| "basefont"
257-
| "bgsound"
258-
| "frameset"
259-
| "link"
260-
| "meta"
261-
| "noframes"
262-
| "noscript"
263-
| "script"
264-
| "style"
265-
| "template"
266-
| "title"
267-
) =>
268-
{
269-
false
270-
}
271-
_ => true,
272-
}) =>
245+
if !self.keep_head_and_body
246+
&& (n.children.is_empty()
247+
|| (match n.children.first() {
248+
Some(Child::Text(text))
249+
if !text.data.is_empty()
250+
&& text
251+
.data
252+
.chars()
253+
.next()
254+
.unwrap()
255+
.is_ascii_whitespace() =>
256+
{
257+
false
258+
}
259+
Some(Child::Comment(..)) => false,
260+
Some(Child::Element(Element {
261+
namespace,
262+
tag_name,
263+
..
264+
})) if *namespace == Namespace::HTML
265+
&& matches!(
266+
&**tag_name,
267+
"base"
268+
| "basefont"
269+
| "bgsound"
270+
| "frameset"
271+
| "link"
272+
| "meta"
273+
| "noframes"
274+
| "noscript"
275+
| "script"
276+
| "style"
277+
| "template"
278+
| "title"
279+
) =>
280+
{
281+
false
282+
}
283+
_ => true,
284+
})) =>
273285
{
274286
true
275287
}
@@ -432,10 +444,11 @@ where
432444
//
433445
// A body element's end tag can be omitted if the body element is not
434446
// immediately followed by a comment.
435-
"html" | "body" => !matches!(next, Some(Child::Comment(..))),
447+
"html" => !matches!(next, Some(Child::Comment(..))),
448+
"body" if !self.keep_head_and_body => !matches!(next, Some(Child::Comment(..))),
436449
// A head element's end tag can be omitted if the head element is not
437450
// immediately followed by ASCII whitespace or a comment.
438-
"head" => match next {
451+
"head" if !self.keep_head_and_body => match next {
439452
Some(Child::Text(text))
440453
if text.data.chars().next().unwrap().is_ascii_whitespace() =>
441454
{
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
use std::{env, fs};
2+
3+
use swc_common::{errors::HANDLER, FileName};
4+
use swc_html_ast::Document;
5+
use swc_html_codegen::{writer::basic::BasicHtmlWriter, Emit};
6+
use swc_html_minifier::{minify_document, option::MinifyOptions};
7+
use swc_html_parser::parse_file_as_document;
8+
9+
pub fn main() {
10+
let path = env::args().nth(1).expect("should provide a path to file");
11+
let src = fs::read_to_string(path).unwrap();
12+
testing::run_test2(false, |cm, handler| {
13+
HANDLER.set(&handler, || {
14+
let fm = cm.new_source_file(FileName::Anon.into(), src);
15+
16+
let mut errors = Vec::new();
17+
let mut document: Document =
18+
parse_file_as_document(&fm, Default::default(), &mut errors).unwrap();
19+
20+
for err in errors {
21+
err.to_diagnostics(&handler).emit();
22+
}
23+
24+
minify_document(&mut document, &MinifyOptions::default());
25+
26+
let mut buf = String::new();
27+
{
28+
let wr = BasicHtmlWriter::new(&mut buf, None, Default::default());
29+
let mut generator = swc_html_codegen::CodeGenerator::new(
30+
wr,
31+
swc_html_codegen::CodegenConfig {
32+
minify: true,
33+
..Default::default()
34+
},
35+
);
36+
37+
generator.emit(&document).unwrap();
38+
}
39+
40+
println!("{buf}");
41+
42+
Ok(())
43+
})
44+
})
45+
.unwrap();
46+
}

crates/swc_html_minifier/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2221,6 +2221,7 @@ impl<C: MinifyCss> Minifier<'_, C> {
22212221
scripting_enabled: false,
22222222
context_element: context_element.as_ref(),
22232223
tag_omission: None,
2224+
keep_head_and_body: None,
22242225
self_closing_void_elements: None,
22252226
quotes: None,
22262227
},

0 commit comments

Comments
 (0)