Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Empty file modified prebuild.sh
100644 → 100755
Empty file.
1 change: 0 additions & 1 deletion src/spec/tag/omission.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand Down
46 changes: 46 additions & 0 deletions src/unit/jsonscript.rs
Original file line number Diff line number Diff line change
@@ -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(&["</script"]);
}

#[inline(always)]
pub fn process_json(proc: &mut Processor, cfg: &Cfg) -> 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(())
}
1 change: 1 addition & 0 deletions src/unit/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
10 changes: 8 additions & 2 deletions src/unit/style.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,14 +29,20 @@ lazy_static! {

lazy_static! {
static ref STYLE_END: AhoCorasick = AhoCorasickBuilder::new().ascii_case_insensitive(true).build(&["</style"]);
static ref SCRIPT_STYLE_END: AhoCorasick = AhoCorasickBuilder::new().ascii_case_insensitive(true).build(&["</script"]);
}

#[inline(always)]
pub fn process_style(proc: &mut Processor, cfg: &Cfg) -> 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.
Expand Down
83 changes: 75 additions & 8 deletions src/unit/tag.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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,
}
Expand Down Expand Up @@ -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) => {
Expand Down Expand Up @@ -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"</"), Discard).require("closing tag name".to_string()) {
Ok(_) =>
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));
};

Expand All @@ -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,
};

Expand All @@ -222,8 +285,8 @@ pub fn process_tag(
};

let closing_tag_checkpoint = ReadCheckpoint::new(proc);
proc.m(IsSeq(b"</"), Discard).require("closing tag")?;
let closing_tag = proc.m(WhileInLookup(TAG_NAME_CHAR), Discard).require("closing tag name")?;
proc.m(IsSeq(b"</"), Discard).require("closing tag".to_string())?;
let closing_tag = proc.m(WhileInLookup(TAG_NAME_CHAR), Discard).require("closing tag name".to_string())?;
proc.make_lowercase(closing_tag);

// We need to check closing tag matches as otherwise when we later write closing tag, it might be longer than source closing tag and cause source to be overwritten.
Expand All @@ -232,14 +295,18 @@ pub fn process_tag(
closing_tag_checkpoint.restore(proc);
Ok(MaybeClosingTag(None))
} else {
//Ok(MaybeClosingTag(None))
Err(ErrorType::ClosingTagMismatch {
expected: unsafe { String::from_utf8_unchecked(proc[tag_name].to_vec()) },
got: unsafe { String::from_utf8_unchecked(proc[closing_tag].to_vec()) },
})
}
} else {
let tagname = unsafe { String::from_utf8_unchecked(proc[tag_name].to_vec()) };
let closingtagname = unsafe { String::from_utf8_unchecked(proc[closing_tag].to_vec()) };
let formated = format!("closing tag end {} {} ", tagname, closingtagname );
proc.m(WhileInLookup(WHITESPACE), Discard);
proc.m(IsChar(b'>'), Discard).require("closing tag end")?;
proc.m(IsChar(b'>'), Discard).require(formated)?;
Ok(MaybeClosingTag(Some(tag_name)))
}
}