Skip to content

Commit

Permalink
feat(html/codegen): Support context element (#4887)
Browse files Browse the repository at this point in the history
  • Loading branch information
alexander-akait committed Jun 5, 2022
1 parent 3812fb2 commit ae1ff1e
Show file tree
Hide file tree
Showing 7 changed files with 137 additions and 130 deletions.
2 changes: 1 addition & 1 deletion crates/swc_html_codegen/src/ctx.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ where

#[derive(Debug, Default, Clone, Copy)]
pub(crate) struct Ctx {
pub skip_escape_text: bool,
pub need_escape_text: bool,
pub need_extra_newline_in_text: bool,
}

Expand Down
150 changes: 77 additions & 73 deletions crates/swc_html_codegen/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,18 @@ mod emit;
mod list;
pub mod writer;

#[derive(Debug, Clone, Copy, Default)]
#[derive(Debug, Clone, Default)]
pub struct CodegenConfig {
pub minify: bool,
pub scripting_enabled: bool,
/// Should be used only for `DocumentFragment` code generation
pub context_element: Option<Element>,
}

enum TagOmissionParent<'a> {
Document(&'a Document),
DocumentFragment(&'a DocumentFragment),
Element(&'a Element),
}

#[derive(Debug)]
Expand Down Expand Up @@ -52,50 +60,26 @@ where
#[emitter]
fn emit_document(&mut self, n: &Document) -> Result {
if self.config.minify {
for (idx, node) in n.children.iter().enumerate() {
match node {
Child::Element(element) => {
let prev = if idx > 0 {
n.children.get(idx - 1)
} else {
None
};
let next = n.children.get(idx + 1);

self.basic_emit_element(element, None, prev, next)?;
}
_ => {
emit!(self, node)
}
}
}
self.emit_list_for_tag_omission(TagOmissionParent::Document(n))?;
} else {
self.emit_list(&n.children, ListFormat::NotDelimited)?;
}
}

#[emitter]
fn emit_document_fragment(&mut self, n: &DocumentFragment) -> Result {
let ctx = if let Some(context_element) = &self.config.context_element {
self.create_context_for_element(context_element)
} else {
Default::default()
};

if self.config.minify {
for (idx, node) in n.children.iter().enumerate() {
match node {
Child::Element(element) => {
let prev = if idx > 0 {
n.children.get(idx - 1)
} else {
None
};
let next = n.children.get(idx + 1);

self.basic_emit_element(element, None, prev, next)?;
}
_ => {
emit!(self, node)
}
}
}
self.with_ctx(ctx)
.emit_list_for_tag_omission(TagOmissionParent::DocumentFragment(n))?;
} else {
self.emit_list(&n.children, ListFormat::NotDelimited)?;
self.with_ctx(ctx)
.emit_list(&n.children, ListFormat::NotDelimited)?;
}
}

Expand Down Expand Up @@ -183,6 +167,10 @@ where
prev: Option<&Child>,
next: Option<&Child>,
) -> Result {
if self.is_plaintext {
return Ok(());
}

let has_attributes = !n.attributes.is_empty();
let can_omit_start_tag = self.config.minify
&& !has_attributes
Expand Down Expand Up @@ -342,29 +330,18 @@ where
return Ok(());
}

self.is_plaintext = matches!(&*n.tag_name, "plaintext");
if !self.is_plaintext {
self.is_plaintext = matches!(&*n.tag_name, "plaintext");
}

if let Some(content) = &n.content {
emit!(self, content);
} else if !n.children.is_empty() {
let skip_escape_text = match &*n.tag_name {
"style" | "script" | "xmp" | "iframe" | "noembed" | "noframes" => true,
"noscript" => self.config.scripting_enabled,
_ if self.is_plaintext => true,
_ => false,
};
let need_extra_newline_in_text =
n.namespace == Namespace::HTML && matches!(&*n.tag_name, "textarea" | "pre");

let ctx = Ctx {
skip_escape_text,
need_extra_newline_in_text,
..self.ctx
};
let ctx = self.create_context_for_element(n);

if self.config.minify {
self.with_ctx(ctx)
.emit_list_for_tag_omission(n, &n.children)?;
.emit_list_for_tag_omission(TagOmissionParent::Element(n))?;
} else {
self.with_ctx(ctx)
.emit_list(&n.children, ListFormat::NotDelimited)?;
Expand Down Expand Up @@ -713,9 +690,7 @@ where

#[emitter]
fn emit_text(&mut self, n: &Text) -> Result {
if self.ctx.skip_escape_text {
write_str!(self, n.span, &n.value);
} else {
if self.ctx.need_escape_text {
let mut data = String::with_capacity(n.value.len());

if self.ctx.need_extra_newline_in_text && n.value.contains('\n') {
Expand All @@ -729,6 +704,8 @@ where
}

write_str!(self, n.span, &data);
} else {
write_str!(self, n.span, &n.value);
}
}

Expand All @@ -743,6 +720,23 @@ where
write_str!(self, n.span, &comment);
}

fn create_context_for_element(&self, n: &Element) -> Ctx {
let need_escape_text = match &*n.tag_name {
"style" | "script" | "xmp" | "iframe" | "noembed" | "noframes" | "plaintext" => false,
"noscript" => !self.config.scripting_enabled,
_ if self.is_plaintext => false,
_ => true,
};
let need_extra_newline_in_text =
n.namespace == Namespace::HTML && matches!(&*n.tag_name, "textarea" | "pre");

Ctx {
need_escape_text,
need_extra_newline_in_text,
..self.ctx
}
}

#[emitter]
fn emit_token_and_span(&mut self, n: &TokenAndSpan) -> Result {
let span = n.span;
Expand Down Expand Up @@ -1002,6 +996,34 @@ where
}
}

fn emit_list_for_tag_omission(&mut self, parent: TagOmissionParent) -> Result {
let nodes = match &parent {
TagOmissionParent::Document(document) => &document.children,
TagOmissionParent::DocumentFragment(document_fragment) => &document_fragment.children,
TagOmissionParent::Element(element) => &element.children,
};
let parent = match parent {
TagOmissionParent::Element(element) => Some(element),
_ => None,
};

for (idx, node) in nodes.iter().enumerate() {
match node {
Child::Element(element) => {
let prev = if idx > 0 { nodes.get(idx - 1) } else { None };
let next = nodes.get(idx + 1);

self.basic_emit_element(element, parent, prev, next)?;
}
_ => {
emit!(self, node)
}
}
}

Ok(())
}

fn emit_list<N>(&mut self, nodes: &[N], format: ListFormat) -> Result
where
Self: Emit<N>,
Expand All @@ -1022,24 +1044,6 @@ 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 prev = if idx > 0 { nodes.get(idx - 1) } else { None };
let next = nodes.get(idx + 1);

self.basic_emit_element(element, Some(parent), prev, next)?;
}
_ => {
emit!(self, node)
}
}
}

Ok(())
}

fn write_delim(&mut self, f: ListFormat) -> Result {
match f & ListFormat::DelimitersMask {
ListFormat::None => {}
Expand Down

1 comment on commit ae1ff1e

@github-actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Benchmark

Benchmark suite Current: ae1ff1e Previous: 5abe847 Ratio
es/full/minify/libraries/antd 1705291664 ns/iter (± 16930968) 2753437880 ns/iter (± 63406343) 0.62
es/full/minify/libraries/d3 431906065 ns/iter (± 3078161) 646844090 ns/iter (± 12399051) 0.67
es/full/minify/libraries/echarts 1984542108 ns/iter (± 15350023) 3113704529 ns/iter (± 23098915) 0.64
es/full/minify/libraries/jquery 105209637 ns/iter (± 669124) 142044988 ns/iter (± 3495498) 0.74
es/full/minify/libraries/lodash 149229818 ns/iter (± 987599) 208002057 ns/iter (± 6708950) 0.72
es/full/minify/libraries/moment 62022473 ns/iter (± 216896) 83153483 ns/iter (± 1861290) 0.75
es/full/minify/libraries/react 20797316 ns/iter (± 30121) 26749089 ns/iter (± 579828) 0.78
es/full/minify/libraries/terser 491171099 ns/iter (± 2514350) 937408783 ns/iter (± 10954099) 0.52
es/full/minify/libraries/three 543995008 ns/iter (± 6516410) 925815267 ns/iter (± 24375707) 0.59
es/full/minify/libraries/typescript 3950578443 ns/iter (± 12599685) 6106788209 ns/iter (± 123948383) 0.65
es/full/minify/libraries/victory 734149202 ns/iter (± 11734931) 1183248743 ns/iter (± 18201928) 0.62
es/full/minify/libraries/vue 156448202 ns/iter (± 825376) 218402607 ns/iter (± 4519441) 0.72
es/full/codegen/es3 34871 ns/iter (± 138) 45246 ns/iter (± 2108) 0.77
es/full/codegen/es5 34948 ns/iter (± 154) 45563 ns/iter (± 2552) 0.77
es/full/codegen/es2015 34956 ns/iter (± 140) 43708 ns/iter (± 2365) 0.80
es/full/codegen/es2016 34928 ns/iter (± 181) 41630 ns/iter (± 4893) 0.84
es/full/codegen/es2017 34964 ns/iter (± 176) 45013 ns/iter (± 6344) 0.78
es/full/codegen/es2018 34960 ns/iter (± 137) 42249 ns/iter (± 1589) 0.83
es/full/codegen/es2019 34974 ns/iter (± 169) 43177 ns/iter (± 2205) 0.81
es/full/codegen/es2020 34981 ns/iter (± 219) 43644 ns/iter (± 4155) 0.80
es/full/all/es3 190114937 ns/iter (± 850341) 251475099 ns/iter (± 9437525) 0.76
es/full/all/es5 179177296 ns/iter (± 698660) 235347156 ns/iter (± 7549782) 0.76
es/full/all/es2015 136702403 ns/iter (± 660618) 187725042 ns/iter (± 7173278) 0.73
es/full/all/es2016 136440191 ns/iter (± 392208) 187965744 ns/iter (± 6717763) 0.73
es/full/all/es2017 135472883 ns/iter (± 724727) 186639983 ns/iter (± 6952223) 0.73
es/full/all/es2018 133670795 ns/iter (± 667154) 178549776 ns/iter (± 8522614) 0.75
es/full/all/es2019 132166519 ns/iter (± 337927) 182419395 ns/iter (± 6963387) 0.72
es/full/all/es2020 127199794 ns/iter (± 315509) 170168647 ns/iter (± 7624342) 0.75
es/full/parser 577129 ns/iter (± 43475) 840918 ns/iter (± 93345) 0.69
es/full/base/fixer 25721 ns/iter (± 254) 39740 ns/iter (± 1747) 0.65
es/full/base/resolver_and_hygiene 137633 ns/iter (± 1447) 191912 ns/iter (± 8133) 0.72
serialization of ast node 181 ns/iter (± 0) 212 ns/iter (± 14) 0.85
serialization of serde 181 ns/iter (± 0) 217 ns/iter (± 9) 0.83

This comment was automatically generated by workflow using github-action-benchmark.

Please sign in to comment.