Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(es/minifier): support reduce_escaped_newline #10232

Merged
merged 11 commits into from
Mar 31, 2025

Conversation

wjw99830
Copy link
Contributor

@wjw99830 wjw99830 commented Mar 19, 2025

Description: Add compress.experimental.reduce_escaped_newline option to convert StringLiteral with escaped newline \n to TemplateLiteral with newline character(only for compress.ecma >= 2015).

Default is true.

BREAKING CHANGE: None.

Related issue (if exists): #10229

@wjw99830 wjw99830 requested a review from a team as a code owner March 19, 2025 16:58
Copy link

changeset-bot bot commented Mar 19, 2025

🦋 Changeset detected

Latest commit: ea90d46

The changes in this PR will be included in the next version bump.

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

Copy link

codspeed-hq bot commented Mar 19, 2025

CodSpeed Performance Report

Merging #10232 will improve performances by 1.32%

Comparing wjw99830:feat/reduce_escaped_newline (ea90d46) with main (0d88041)

Summary

⚡ 1 improvements
✅ 151 untouched benchmarks

Benchmarks breakdown

Benchmark BASE HEAD Change
es/lints/libs/d3 28.1 ms 27.7 ms +1.32%

@kdy1
Copy link
Member

kdy1 commented Mar 20, 2025

I think we should not add a new option for this

@wjw99830
Copy link
Contributor Author

wjw99830 commented Mar 20, 2025

I think we should not add a new option for this

You mean enable the feature by default and cannot be turned off ?

I have the same idea in fact. But considering the stability of the feature, I added that.

@kdy1
Copy link
Member

kdy1 commented Mar 20, 2025

If so, can we have an experimental config instead? Like compress.experimental.reduce_escaped_newline? You may need to define CompressExperimentalOptions struct. (And please make it #[non_exhaustive])

@wjw99830
Copy link
Contributor Author

Good idea.I'll try it later.

@wjw99830
Copy link
Contributor Author

I find that swc_ecma_codegen will generate \n for \\n within TemplateElement.raw.

let unescape = match c {
    '\\' => match iter.next() {
        Some(c) => match c {
            'n' => Some('\n'),
            // ...
        },
        // ...
    },
    _ => Some(c),
};

match unescape {
    Some('\n') => buf.push('\n'),
    // ...
}

It’s same as what I do inside reduce_escaped_newline_for_tpl_element, so I will remove it to prevent redundant work.

It still makes sense to retain reduce_escaped_newline_for_str_lit, means that reduce_escaped_newline works only when compress.ecma >= 2015. Do you have any better ideas about the design of this option?

@kdy1
Copy link
Member

kdy1 commented Mar 20, 2025

Maybe add an option to the codegen option?
It's already #[non_exhaustive], so you would need to add one method and call it from

swc/crates/swc/src/lib.rs

Lines 910 to 917 in b192dc8

codegen_config: swc_ecma_codegen::Config::default()
.with_target(target)
.with_minify(true)
.with_ascii_only(opts.format.ascii_only)
.with_emit_assert_for_import_attributes(
opts.format.emit_assert_for_import_attributes,
)
.with_inline_script(opts.format.inline_script),

@wjw99830
Copy link
Contributor Author

OK. Maybe we should call it reduce_escaped_controls to including \t(or possible others in future).

@kdy1 kdy1 self-assigned this Mar 23, 2025
@kdy1 kdy1 added this to the Planned milestone Mar 23, 2025
@@ -137,4 +137,29 @@ impl Optimizer<'_> {
_ => {}
}
}

pub(super) fn reduce_escaped_newline_for_str_lit(&mut self, expr: &mut Expr) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we still need this, even with the codegen config?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is used to transform string literal to template literal. Maybe should rename the method name, it's not clear enough.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Document added.

quasis: vec![TplElement {
span: s.span,
cooked: Some(s.value.clone()),
raw: s.value.clone(),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This may cause an issue.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The function for Tpl#raw => Str#value is at https://rustdoc.swc.rs/swc_core/ecma/utils/swc_ecma_ast/struct.Str.html#method.from_tpl_raw , but let me search for Str => Tpl function...

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should use functions at

pub(super) fn convert_str_value_to_tpl_cooked(value: &Atom) -> Cow<str> {
value
.replace("\\\\", "\\")
.replace("\\`", "`")
.replace("\\$", "$")
.into()
}
pub(super) fn convert_str_value_to_tpl_raw(value: &Atom) -> Cow<str> {
value
.replace('\\', "\\\\")
.replace('`', "\\`")
.replace('$', "\\$")
.replace('\n', "\\n")
.replace('\r', "\\r")
.into()
}
pub(super) fn convert_str_raw_to_tpl_raw(value: &str) -> Atom {
value.replace('`', "\\`").replace('$', "\\$").into()
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This function replace '\n' to "\\n" for Tpl#raw, but I just want to preserve the '\n'. It seems little bit redundant if replace "\\n" back to '\n'.

Copy link
Member

@kdy1 kdy1 Mar 23, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If so, can you add test cases for inputs that contain those strings, respectively?

One test per the search text in "", like one for "\$" in convert_str_value_to_tpl_cooked (you need 3 test cases for the function) and "$" for convert_str_value_to_tpl_raw?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In pure/strings.rs and pure/misc.rs, it appears to be invoked with Str#value...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I use a new fn to convert Str#value to Tpl#raw without replacing for \n and test for some edge cases.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does the original function need to be fixed in this PR?

Copy link
Member

@kdy1 kdy1 Mar 29, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It’s fine to skip for this PR unless there’s a test failure

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any other suggestions for this review(or above)?

Copy link
Member

@kdy1 kdy1 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Other things look good. Only one concern about consistency. Thanks!

#[non_exhaustive]
pub struct TerserExperimentalOptions {
#[serde(default = "true_by_default")]
pub reduce_escaped_newline: bool,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
pub reduce_escaped_newline: bool,
pub reduce_escaped_newline: Option<bool>,

Sorry, I missed this one in the previous review.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

true_by_default with bool means

  • experimental: {} => true, regardless of defaults
  • experimental: null => Uses defaults

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think those two should behave identically because the user didn't specify the reduce_escaped_newline

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's my oversight. Thanks to point out.

kdy1
kdy1 previously approved these changes Mar 31, 2025
Copy link
Member

@kdy1 kdy1 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you!

@kdy1 kdy1 requested a review from a team as a code owner March 31, 2025 02:27
@kdy1 kdy1 merged commit 64fb286 into swc-project:main Mar 31, 2025
18 checks passed
@kdy1 kdy1 modified the milestones: Planned, v1.11.14 Mar 31, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

Successfully merging this pull request may close these issues.

2 participants