diff --git a/Cargo.toml b/Cargo.toml index d8efb179..4383c3c6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,11 +17,12 @@ maintenance = { status = "actively-developed" } [features] default = [] -js-esbuild = ["crossbeam", "esbuild-rs"] +js-esbuild = ["crossbeam", "esbuild-rs", "minify"] [dependencies] aho-corasick = "0.7" crossbeam = { version = "0.7", optional = true } esbuild-rs = { version = "0.8.30", optional = true } +minify = { version = "1.2", optional = true } lazy_static = "1.4" memchr = "2" diff --git a/prebuild.sh b/prebuild.sh old mode 100644 new mode 100755 diff --git a/src/spec/tag/omission.rs b/src/spec/tag/omission.rs index 8720c6eb..7c93259c 100644 --- a/src/spec/tag/omission.rs +++ b/src/spec/tag/omission.rs @@ -90,7 +90,6 @@ lazy_static! { followed_by.insert(b"aside"); followed_by.insert(b"blockquote"); followed_by.insert(b"details"); - followed_by.insert(b"div"); followed_by.insert(b"dl"); followed_by.insert(b"fieldset"); followed_by.insert(b"figcaption"); diff --git a/src/unit/jsonscript.rs b/src/unit/jsonscript.rs new file mode 100644 index 00000000..bdcf87ec --- /dev/null +++ b/src/unit/jsonscript.rs @@ -0,0 +1,46 @@ +use aho_corasick::{AhoCorasick, AhoCorasickBuilder}; +use lazy_static::lazy_static; +use crate::cfg::Cfg; +use crate::err::ProcessingResult; +use crate::proc::MatchAction::*; +use crate::proc::MatchMode::*; +use crate::proc::Processor; +#[cfg(feature = "js-esbuild")] +use { + std::sync::Arc, + minify::json::minify, + crate::proc::EsbuildSection, + crate::proc::checkpoint::WriteCheckpoint, +}; + +lazy_static! { + static ref SCRIPT_END: AhoCorasick = AhoCorasickBuilder::new().ascii_case_insensitive(true).build(&[" ProcessingResult<()> { + #[cfg(feature = "js-esbuild")] + let start = WriteCheckpoint::new(proc); + proc.require_not_at_end()?; + proc.m(WhileNotSeq(&SCRIPT_END), Keep); + // `process_tag` will require closing tag. + + // TODO This is copied from style.rs. + #[cfg(feature = "js-esbuild")] + if cfg.minify_js { + let src = start.written_range(proc); + let (wg, results) = proc.new_esbuild_section(); + let raw_json = unsafe { String::from_utf8_unchecked(proc[src].to_vec()) }; + let result = minify(&raw_json[..]); + let mut guard = results.lock().unwrap(); + guard.push(EsbuildSection { + src, + escaped: result.as_bytes().to_vec(), + }); + drop(guard); + drop(results); + drop(wg); + }; + + Ok(()) +} diff --git a/src/unit/mod.rs b/src/unit/mod.rs index c45f54cb..40cf9998 100644 --- a/src/unit/mod.rs +++ b/src/unit/mod.rs @@ -3,6 +3,7 @@ pub mod bang; pub mod comment; pub mod content; pub mod instruction; +pub mod jsonscript; pub mod script; pub mod style; pub mod tag; diff --git a/src/unit/style.rs b/src/unit/style.rs index 5f8819a6..67a7d1d9 100644 --- a/src/unit/style.rs +++ b/src/unit/style.rs @@ -29,14 +29,20 @@ lazy_static! { lazy_static! { static ref STYLE_END: AhoCorasick = AhoCorasickBuilder::new().ascii_case_insensitive(true).build(&[" ProcessingResult<()> { +pub fn process_style(proc: &mut Processor, cfg: &Cfg, as_script: bool) -> ProcessingResult<()> { #[cfg(feature = "js-esbuild")] let start = WriteCheckpoint::new(proc); proc.require_not_at_end()?; - proc.m(WhileNotSeq(&STYLE_END), Keep); + + if as_script { + proc.m(WhileNotSeq( &SCRIPT_STYLE_END ), Keep); + } else { + proc.m(WhileNotSeq( &STYLE_END ), Keep); + } // `process_tag` will require closing tag. // TODO This is copied from script.rs. diff --git a/src/unit/tag.rs b/src/unit/tag.rs index 45290b2a..48f217b8 100644 --- a/src/unit/tag.rs +++ b/src/unit/tag.rs @@ -10,6 +10,7 @@ use crate::spec::tag::void::VOID_TAGS; use crate::unit::attr::{AttrType, process_attr, ProcessedAttr}; use crate::unit::content::process_content; use crate::unit::script::process_script; +use crate::unit::jsonscript::process_json; use crate::unit::style::process_style; use crate::gen::attrs::{ATTRS, AttributeMinification}; use crate::spec::tag::ns::Namespace; @@ -36,14 +37,30 @@ lazy_static! { s.insert(b"text/livescript"); s.insert(b"text/x-ecmascript"); s.insert(b"text/x-javascript"); + s.insert(b"text/rmscript"); s }; + + pub static ref JSON_MIME_TYPES: HashSet<&'static [u8]> = { + let mut s = HashSet::<&'static [u8]>::new(); + s.insert(b"application/json"); + s.insert(b"application/ld+json"); + s + }; + + pub static ref SCRIPTSTYLES_MIME_TYPES: HashSet<&'static [u8]> = { + let mut s = HashSet::<&'static [u8]>::new(); + s.insert(b"text/rmstyle"); + s + }; } #[derive(Copy, Clone)] enum TagType { ScriptJs, + JsonData, ScriptData, + ScriptStyle, Style, Other, } @@ -161,11 +178,28 @@ pub fn process_tag( let script_tag_type_is_js = value .filter(|v| !JAVASCRIPT_MIME_TYPES.contains(&proc[*v])) .is_none(); - if script_tag_type_is_js { - erase_attr = true; - } else { + + erase_attr = false; + if !script_tag_type_is_js { // Tag does not contain JS, don't minify JS. - tag_type = TagType::ScriptData; + let script_tag_type_is_style = value + .filter(|v| !SCRIPTSTYLES_MIME_TYPES.contains(&proc[*v])) + .is_none(); + + if script_tag_type_is_style { + tag_type = TagType::ScriptStyle; + } else { + let script_tag_type_is_json = value + .filter(|v| !JSON_MIME_TYPES.contains(&proc[*v])) + .is_none(); + + if script_tag_type_is_json { + tag_type = TagType::JsonData; + }else{ + erase_attr = true; + tag_type = TagType::ScriptData; + } + } }; } (_, name) => { @@ -199,6 +233,33 @@ pub fn process_tag( proc.write_slice(b"/>"); }; }; + + if is_void_tag { + let closing_tag_checkpoint = ReadCheckpoint::new(proc); + let closing_tag = match proc.m(IsSeq(b" + match proc.m(WhileInLookup(TAG_NAME_CHAR), Discard).require("closing tag name".to_string()){ + Ok(tag) => Some(tag), + Err(_) => None, + } + , + Err(_) => None + }; + + return match closing_tag { + Some(tag) => { + proc.make_lowercase(tag); + if proc[tag] == proc[tag_name] { + proc.m(IsSeq(b">"), Discard); + }else{ + closing_tag_checkpoint.restore(proc); + } + Ok(MaybeClosingTag(None)) + }, + None => Ok(MaybeClosingTag(None)) + } + + } return Ok(MaybeClosingTag(None)); }; @@ -211,8 +272,10 @@ pub fn process_tag( let mut closing_tag_omitted = false; match tag_type { TagType::ScriptData => process_script(proc, cfg, false)?, + TagType::JsonData => process_json(proc, cfg)?, TagType::ScriptJs => process_script(proc, cfg, true)?, - TagType::Style => process_style(proc, cfg)?, + TagType::ScriptStyle => process_style(proc, cfg, true)?, + TagType::Style => process_style(proc, cfg, false)?, _ => closing_tag_omitted = process_content(proc, cfg, child_ns, Some(tag_name), descendant_of_pre)?.closing_tag_omitted, }; @@ -222,8 +285,8 @@ pub fn process_tag( }; let closing_tag_checkpoint = ReadCheckpoint::new(proc); - proc.m(IsSeq(b"'), Discard).require("closing tag end")?; + proc.m(IsChar(b'>'), Discard).require(formated)?; Ok(MaybeClosingTag(Some(tag_name))) } }