feat(es/minifier): Support format.inline_script (#8252)

This commit is contained in:
bohan 2023-11-15 05:37:05 +08:00 committed by GitHub
parent 5fed604276
commit f059270348
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 151 additions and 3 deletions

View File

@ -36,6 +36,9 @@ pub struct Config {
#[cfg_attr(feature = "serde-impl", serde(default))]
pub emit_assert_for_import_attributes: bool,
#[cfg_attr(feature = "serde-impl", serde(default))]
pub inline_script: bool,
}
impl Default for Config {
@ -46,6 +49,7 @@ impl Default for Config {
ascii_only: false,
omit_last_semi: false,
emit_assert_for_import_attributes: false,
inline_script: false,
}
}
}
@ -78,4 +82,9 @@ impl Config {
self.emit_assert_for_import_attributes = emit_assert_for_import_attributes;
self
}
pub fn with_inline_script(mut self, inline_script: bool) -> Self {
self.inline_script = inline_script;
self
}
}

View File

@ -75,6 +75,56 @@ where
pub wr: W,
}
fn replace_close_inline_script(raw: &str) -> Cow<str> {
let chars = raw.as_bytes();
let pattern_len = 8; // </script>
let matched = |i: usize| {
if i + pattern_len >= chars.len() {
return false;
}
chars[i] == b'<'
&& chars[i + 1] == b'/'
&& (chars[i + 2] == b's' || chars[i + 2] == b'S')
&& (chars[i + 3] == b'c' || chars[i + 3] == b'C')
&& (chars[i + 4] == b'r' || chars[i + 4] == b'R')
&& (chars[i + 5] == b'i' || chars[i + 5] == b'I')
&& (chars[i + 6] == b'p' || chars[i + 6] == b'P')
&& (chars[i + 7] == b't' || chars[i + 7] == b'T')
&& (chars[i + 8] == b'>'
|| chars[i + 8] == b' '
|| chars[i + 8] == b'\t'
|| chars[i + 8] == b'\n'
|| chars[i + 8] == b'\x0C'
|| chars[i + 8] == b'\r')
};
let mut matched_list = (0..chars.len()).filter(|&i| matched(i)).peekable();
if matched_list.peek().is_none() {
return Cow::Borrowed(raw);
}
let mut result = String::new();
let mut last_end = 0;
for start in matched_list {
for c in unsafe { chars.get_unchecked(last_end..start) } {
result.push(*c as char);
}
for (index, c) in unsafe { chars.get_unchecked(start..start + pattern_len) }
.iter()
.enumerate()
{
if index == 1 {
// "</s" -> <\/s"
result.push('\\');
}
result.push(*c as char);
}
last_end = start + pattern_len;
}
for c in unsafe { chars.get_unchecked(last_end..chars.len()) } {
result.push(*c as char);
}
Cow::Owned(result)
}
impl<'a, W, S: SourceMapper> Emitter<'a, W, S>
where
W: WriteJs,
@ -579,7 +629,13 @@ where
}
}
let value = get_quoted_utf16(&node.value, self.cfg.ascii_only, target);
let mut value = get_quoted_utf16(&node.value, self.cfg.ascii_only, target);
if self.cfg.inline_script {
value = replace_close_inline_script(&value)
.replace("\x3c!--", "\\x3c!--")
.replace("/--\x3e/", "--\\x3e");
}
self.wr.write_str_lit(DUMMY_SP, &value)?;

View File

@ -130,8 +130,7 @@ pub struct JsMinifyFormatOptions {
#[serde(default, alias = "indent_start")]
pub indent_start: bool,
/// Not implemented yet.
#[serde(default, alias = "inline_script")]
#[serde(default = "true_by_default", alias = "inline_script")]
pub inline_script: bool,
/// Not implemented yet.

View File

@ -0,0 +1,84 @@
use swc_common::{sync::Lrc, FileName, Mark, SourceMap};
use swc_ecma_ast::*;
use swc_ecma_codegen::{
text_writer::{omit_trailing_semi, JsWriter, WriteJs},
Config, Emitter,
};
use swc_ecma_minifier::{optimize, option::ExtraOptions};
use swc_ecma_parser::{parse_file_as_module, Syntax};
use testing::NormalizedOutput;
fn print(cm: Lrc<SourceMap>, m: &Module, config: Config) -> String {
let mut buf = vec![];
{
let mut wr = Box::new(JsWriter::new(cm.clone(), "\n", &mut buf, None)) as Box<dyn WriteJs>;
if config.minify {
wr = Box::new(omit_trailing_semi(wr));
}
let mut emitter = Emitter {
cfg: config,
cm,
comments: None,
wr,
};
emitter.emit_module(m).unwrap();
}
String::from_utf8(buf).unwrap()
}
fn assert_format(src: &str, expected: &str, opts: Config) {
testing::run_test2(false, |cm, _| {
let fm = cm.new_source_file(FileName::Anon, src.into());
let program = parse_file_as_module(
&fm,
Syntax::Es(Default::default()),
Default::default(),
None,
&mut vec![],
)
.unwrap();
let unresolved_mark = Mark::new();
let top_level_mark = Mark::new();
let m = optimize(
program.into(),
cm.clone(),
None,
None,
&Default::default(),
&ExtraOptions {
unresolved_mark,
top_level_mark,
},
)
.expect_module();
let actual = print(cm, &m, opts);
assert_eq!(
NormalizedOutput::from(actual),
NormalizedOutput::from(expected.to_owned())
);
Ok(())
})
.unwrap()
}
#[test]
fn inline_script() {
let src = r#"
console.log("</sCrIpT>");
"#;
let expected = r#"console.log("<\/sCrIpT>");"#;
assert_format(
src,
expected,
Config::default().with_inline_script(true).with_minify(true),
)
}