FAQ
+- What do we want?
- Our data.
- When do we want it?
- Now.
- Where is it?
- We are not sure.
diff --git a/crates/swc_html_codegen/src/lib.rs b/crates/swc_html_codegen/src/lib.rs index 6719815f88e..f38ff150d50 100644 --- a/crates/swc_html_codegen/src/lib.rs +++ b/crates/swc_html_codegen/src/lib.rs @@ -136,8 +136,12 @@ where formatting_newline!(self); } - #[emitter] - fn emit_element(&mut self, n: &Element) -> Result { + fn basic_emit_element( + &mut self, + n: &Element, + _parent: Option<&Element>, + next: Option<&Child>, + ) -> Result { write_raw!(self, "<"); write_raw!(self, &n.tag_name); @@ -200,11 +204,69 @@ where ..self.ctx }; - self.with_ctx(ctx) - .emit_list(&n.children, ListFormat::NotDelimited)?; + if self.config.minify { + self.with_ctx(ctx) + .emit_list_for_tag_omission(n, &n.children)?; + } else { + self.with_ctx(ctx) + .emit_list(&n.children, ListFormat::NotDelimited)?; + } } - if self.is_plaintext { + let can_omit_end_tag = self.is_plaintext + || (self.config.minify + && n.namespace == Namespace::HTML + && match &*n.tag_name { + // Tag omission in text/html: + + // An li element's end tag can be omitted if the li element is immediately + // followed by another li element or if there is no more content in the parent + // element. + "li" => match next { + Some(Child::Element(Element { + namespace, + tag_name, + .. + })) if *namespace == Namespace::HTML && tag_name == "li" => true, + None => true, + _ => false, + }, + // A dt element's end tag can be omitted if the dt element is immediately + // followed by another dt element or a dd element. + "dt" => match next { + Some(Child::Element(Element { + namespace, + tag_name, + .. + })) if *namespace == Namespace::HTML + && (tag_name == "dt" || tag_name == "dd") => + { + true + } + _ => false, + }, + + // A dd element's end tag can be omitted if the dd element is immediately + // followed by another dd element or a dt element, or if there is no more + // content in the parent element. + "dd" => match next { + Some(Child::Element(Element { + namespace, + tag_name, + .. + })) if *namespace == Namespace::HTML + && (tag_name == "dd" || tag_name == "dt") => + { + true + } + None => true, + _ => false, + }, + + _ => false, + }); + + if can_omit_end_tag { return Ok(()); } @@ -212,6 +274,13 @@ where write_raw!(self, "/"); write_raw!(self, &n.tag_name); write_raw!(self, ">"); + + Ok(()) + } + + #[emitter] + fn emit_element(&mut self, n: &Element) -> Result { + self.basic_emit_element(n, None, None)?; } #[emitter] @@ -565,6 +634,23 @@ where Ok(()) } + fn emit_list_for_tag_omission(&mut self, parent: &Element, nodes: &[Child]) -> Result { + for (idx, node) in nodes.iter().enumerate() { + match node { + Child::Element(element) => { + let next = nodes.get(idx + 1); + + self.basic_emit_element(element, Some(parent), next)?; + } + _ => { + emit!(self, node) + } + } + } + + Ok(()) + } + fn write_delim(&mut self, f: ListFormat) -> Result { match f & ListFormat::DelimitersMask { ListFormat::None => {} diff --git a/crates/swc_html_codegen/tests/fixture.rs b/crates/swc_html_codegen/tests/fixture.rs index 8db6eefe4d5..2fa4e7a6259 100644 --- a/crates/swc_html_codegen/tests/fixture.rs +++ b/crates/swc_html_codegen/tests/fixture.rs @@ -425,6 +425,7 @@ fn test_indent_type_option(input: PathBuf) { ); } +// TODO minified verification #[testing::fixture("../swc_html_parser/tests/fixture/**/*.html")] fn parser_verify(input: PathBuf) { verify_document(&input, None, None, None, false); diff --git a/crates/swc_html_codegen/tests/fixture/optional/input.html b/crates/swc_html_codegen/tests/fixture/optional/input.html new file mode 100644 index 00000000000..7d8162f7948 --- /dev/null +++ b/crates/swc_html_codegen/tests/fixture/optional/input.html @@ -0,0 +1,18 @@ + + +
For more information, read this Stack Overflow answer.
a
a
a
a
a