Minify JS/CSS comments; faster removal of boolean attr values; sorted generated JSON objects

This commit is contained in:
Wilson Lin 2020-01-08 11:40:06 +11:00
parent 9a9b543b26
commit a9bb4c924f
7 changed files with 84 additions and 74 deletions

View File

@ -296,11 +296,9 @@ Any entities in attribute values are decoded, and then the shortest representati
- Single quoted, with any `'` encoded.
- Unquoted, with `"`/`'` first character (if applicable), `>` last character (if applicable), and any whitespace encoded.
Some attributes have their whitespace (after any decoding) trimmed and collapsed:
`class` attributes have their whitespace (after any decoding) trimmed and collapsed.
- `class`
[Boolean attributes](./gen/boolean_attrs.json) will have their values removed.
[Boolean attribute](./gen/boolean_attrs.json) values are removed.
`type` attributes on `script` tags with a value equaling a [JavaScript MIME type](https://mimesniff.spec.whatwg.org/#javascript-mime-type) are removed.
`type` attributes on `style` tags are removed.
@ -309,11 +307,16 @@ If an attribute value is empty after any processing, it is completely removed (i
Spaces are removed between attributes if possible.
# Script and style
Insignificant whitespace is trimmed and collapsed inside `<script>` with JS code and `<style>`.
JS and CSS comments are removed inside `<script>` and `<style>`.
### Other
- Comments are removed.
- Entities are decoded if valid (see relevant parsing section).
- Whitespace is trimmed and collapsed inside `<script>` with JS code and `<style>`.
### Ignored

View File

@ -1,4 +1,13 @@
{
"allowfullscreen": [
"iframe"
],
"allowtransparency": [
"iframe"
],
"async": [
"script"
],
"autofocus": [
"button",
"input",
@ -6,6 +15,21 @@
"select",
"textarea"
],
"autoplay": [
"media"
],
"checked": [
"input"
],
"controls": [
"media"
],
"default": [
"track"
],
"defer": [
"script"
],
"disabled": [
"button",
"fieldset",
@ -16,32 +40,36 @@
"select",
"textarea"
],
"disablepictureinpicture": [
"video"
],
"formnovalidate": [
"button",
"input"
],
"loop": [
"media"
],
"multiple": [
"input",
"select"
],
"muted": [
"media"
],
"nomodule": [
"script"
],
"novalidate": [
"form"
],
"open": [
"details",
"dialog"
],
"novalidate": [
"form"
],
"allowfullscreen": [
"iframe"
],
"allowtransparency": [
"iframe"
],
"seamless": [
"iframe"
],
"checked": [
"input"
],
"multiple": [
"input",
"select"
"playsinline": [
"media",
"video"
],
"readonly": [
"input",
@ -52,44 +80,16 @@
"select",
"textarea"
],
"autoplay": [
"media"
],
"controls": [
"media"
],
"loop": [
"media"
],
"muted": [
"media"
],
"playsinline": [
"media",
"video"
],
"reversed": [
"ol"
],
"selected": [
"option"
],
"async": [
"script"
],
"defer": [
"script"
],
"nomodule": [
"script"
],
"scoped": [
"style"
],
"default": [
"track"
"seamless": [
"iframe"
],
"disablepictureinpicture": [
"video"
"selected": [
"option"
]
}

View File

@ -63,8 +63,9 @@ const processReactTypeDeclarations = async (source) => {
node.forEachChild(c => void unvisited.push(c));
}
// Sort output JSON object by property so diffs are clearer.
await fs.writeFile(BOOLEAN_ATTRS_PATH, JSON.stringify(
Object.fromEntries(booleanAttributes.entries()),
Object.fromEntries([...booleanAttributes.entries()].sort((a, b) => a[0].localeCompare(b[0]))),
null,
2,
));

View File

@ -3,7 +3,7 @@ use phf::{phf_set, Set};
use crate::err::ProcessingResult;
use crate::proc::{Processor, ProcessorRange};
use crate::spec::codepoint::is_control;
use crate::unit::attr::value::{DelimiterType, process_attr_value, ProcessedAttrValue};
use crate::unit::attr::value::{DelimiterType, process_attr_value, ProcessedAttrValue, skip_attr_value};
mod value;
@ -49,15 +49,14 @@ pub fn process_attr(proc: &mut Processor, element: ProcessorRange) -> Processing
let (typ, value) = if !has_value {
(AttrType::NoValue, None)
} else {
// TODO Don't process if going to erase anyway.
let val = process_attr_value(proc, should_collapse_and_trim_value_ws)?;
if is_boolean {
proc.erase_written(after_name);
skip_attr_value(proc)?;
(AttrType::NoValue, None)
} else {
match val {
match process_attr_value(proc, should_collapse_and_trim_value_ws)? {
ProcessedAttrValue { value: None, .. } => {
// Value is empty, which is equivalent to no value, so discard `=` and any quotes.
// Value is empty, which is equivalent to no value, so discard `=`.
debug_assert_eq!(proc.written_count(after_name), 1);
proc.erase_written(after_name);
(AttrType::NoValue, None)
}

View File

@ -133,6 +133,13 @@ impl Metrics {
}
}
pub fn skip_attr_value(proc: &mut Processor) -> ProcessingResult<()> {
let src_delimiter = chain!(proc.match_pred(is_attr_quote).require_with_reason("attribute value delimiter quote")?.discard().char());
chain!(proc.match_while_not_char(src_delimiter).discard());
chain!(proc.match_char(src_delimiter).require_with_reason("attribute value delimiter quote")?.discard());
Ok(())
}
pub struct ProcessedAttrValue {
pub delimiter: DelimiterType,
pub value: Option<ProcessorRange>,

View File

@ -8,19 +8,19 @@ fn is_string_delimiter(c: u8) -> bool {
fn parse_comment_single(proc: &mut Processor) -> ProcessingResult<()> {
if cfg!(debug_assertions) {
chain!(proc.match_seq(b"//").expect().keep());
chain!(proc.match_seq(b"//").expect().discard());
} else {
proc.accept_amount_expect(2);
proc.skip_amount_expect(2);
};
// Comment can end at closing </script>.
// TODO Optimise
while !chain!(proc.match_line_terminator().keep().matched()) {
while !chain!(proc.match_line_terminator().discard().matched()) {
if chain!(proc.match_seq(b"</script>").matched()) {
break;
}
proc.accept()?;
proc.skip()?;
}
Ok(())
@ -28,19 +28,19 @@ fn parse_comment_single(proc: &mut Processor) -> ProcessingResult<()> {
fn parse_comment_multi(proc: &mut Processor) -> ProcessingResult<()> {
if cfg!(debug_assertions) {
chain!(proc.match_seq(b"/*").expect().keep());
chain!(proc.match_seq(b"/*").expect().discard());
} else {
proc.accept_amount_expect(2);
proc.skip_amount_expect(2);
};
// Comment can end at closing </script>.
// TODO Optimise
while !chain!(proc.match_seq(b"*/").keep().matched()) {
while !chain!(proc.match_seq(b"*/").discard().matched()) {
if chain!(proc.match_seq(b"</script>").matched()) {
break;
}
proc.accept()?;
proc.skip()?;
};
Ok(())

View File

@ -11,14 +11,14 @@ fn is_string_delimiter(c: u8) -> bool {
fn parse_comment(proc: &mut Processor) -> ProcessingResult<()> {
if cfg!(debug_assertions) {
chain!(proc.match_seq(b"/*").expect().keep());
chain!(proc.match_seq(b"/*").expect().discard());
} else {
proc.accept_amount_expect(2);
proc.skip_amount_expect(2);
};
// Unlike script tags, style comments do NOT end at closing tag.
while !chain!(proc.match_seq(b"*/").keep().matched()) {
proc.accept()?;
while !chain!(proc.match_seq(b"*/").discard().matched()) {
proc.skip()?;
};
Ok(())