diff --git a/crates/swc_ecma_codegen/src/config.rs b/crates/swc_ecma_codegen/src/config.rs index 90ff3f84d48..431028297c0 100644 --- a/crates/swc_ecma_codegen/src/config.rs +++ b/crates/swc_ecma_codegen/src/config.rs @@ -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 + } } diff --git a/crates/swc_ecma_codegen/src/lib.rs b/crates/swc_ecma_codegen/src/lib.rs index b09cf941f02..c4d33bb4709 100644 --- a/crates/swc_ecma_codegen/src/lib.rs +++ b/crates/swc_ecma_codegen/src/lib.rs @@ -75,6 +75,56 @@ where pub wr: W, } +fn replace_close_inline_script(raw: &str) -> Cow { + let chars = raw.as_bytes(); + let pattern_len = 8; // + 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" + 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)?; diff --git a/crates/swc_ecma_minifier/src/js.rs b/crates/swc_ecma_minifier/src/js.rs index eaf933d1ce4..1db01f61304 100644 --- a/crates/swc_ecma_minifier/src/js.rs +++ b/crates/swc_ecma_minifier/src/js.rs @@ -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. diff --git a/crates/swc_ecma_minifier/tests/format.rs b/crates/swc_ecma_minifier/tests/format.rs new file mode 100644 index 00000000000..21baa765c69 --- /dev/null +++ b/crates/swc_ecma_minifier/tests/format.rs @@ -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, m: &Module, config: Config) -> String { + let mut buf = vec![]; + + { + let mut wr = Box::new(JsWriter::new(cm.clone(), "\n", &mut buf, None)) as Box; + + 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(""); +"#; + let expected = r#"console.log("<\/sCrIpT>");"#; + assert_format( + src, + expected, + Config::default().with_inline_script(true).with_minify(true), + ) +}