Skip to content

Commit 1bf837e

Browse files
authoredJan 26, 2025··
perf(es/react): Use proper string types for react configuration (#9949)
**Description:** We do not need to allocate. **Related issue:** - Closes #9947
1 parent d5f40a0 commit 1bf837e

File tree

15 files changed

+134
-52
lines changed

15 files changed

+134
-52
lines changed
 

‎.changeset/shiny-maps-film.md

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
swc_common: patch
3+
swc_ecma_transforms_base: patch
4+
swc_ecma_transforms_react: major
5+
---
6+
7+
perf(es/react): Use proper string types for react configuration

‎Cargo.lock

+2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎crates/swc_bundler/src/lib.rs

+1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#![deny(clippy::all)]
22
#![allow(unstable_name_collisions)]
33
#![allow(clippy::mutable_key_type)]
4+
#![cfg_attr(not(test), allow(unused))]
45

56
pub use self::{
67
bundler::{Bundle, BundleKind, Bundler, Config, ModuleType},

‎crates/swc_common/src/lib.rs

+1
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
//! Use `ahash` instead of `rustc_hash` for `AHashMap` and `AHashSet`.
3434
#![deny(clippy::all)]
3535
#![cfg_attr(docsrs, feature(doc_cfg))]
36+
#![cfg_attr(not(test), allow(unused))]
3637

3738
use std::fmt::Debug;
3839

‎crates/swc_ecma_transforms/Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ concurrent = [
2222
"swc_ecma_transforms_compat/concurrent",
2323
"swc_ecma_transforms_optimization/concurrent",
2424
"swc_ecma_transforms_react/concurrent",
25+
"swc_ecma_transforms_typescript/concurrent",
2526
]
2627
module = ["swc_ecma_transforms_module"]
2728
multi-module-decorator = ["swc_ecma_transforms_proposal/multi-module"]

‎crates/swc_ecma_transforms_base/src/lib.rs

+1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
// #![cfg_attr(test, deny(warnings))]
22
#![allow(clippy::mutable_key_type)]
3+
#![cfg_attr(not(test), allow(unused))]
34

45
pub use self::resolver::resolver;
56

‎crates/swc_ecma_transforms_react/Cargo.toml

+9-8
Original file line numberDiff line numberDiff line change
@@ -13,18 +13,19 @@ version = "7.0.0"
1313
bench = false
1414

1515
[features]
16-
concurrent = ["rayon"]
16+
concurrent = ["swc_common/concurrent", "rayon"]
1717
default = ["serde-impl"]
1818
serde-impl = ["serde"]
1919

2020
[dependencies]
21-
base64 = { workspace = true }
22-
dashmap = { workspace = true }
23-
indexmap = { workspace = true }
24-
once_cell = { workspace = true }
25-
rayon = { workspace = true, optional = true }
26-
serde = { workspace = true, features = ["derive"], optional = true }
27-
sha1 = { workspace = true }
21+
base64 = { workspace = true }
22+
dashmap = { workspace = true }
23+
indexmap = { workspace = true }
24+
once_cell = { workspace = true }
25+
rayon = { workspace = true, optional = true }
26+
rustc-hash = { workspace = true }
27+
serde = { workspace = true, features = ["derive"], optional = true }
28+
sha1 = { workspace = true }
2829

2930
string_enum = { version = "1.0.0", path = "../string_enum" }
3031
swc_allocator = { version = "2.0.0", path = "../swc_allocator", default-features = false }

‎crates/swc_ecma_transforms_react/src/jsx/mod.rs

+73-28
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,16 @@
11
#![allow(clippy::redundant_allocation)]
22

3-
use std::{borrow::Cow, iter, iter::once, sync::Arc};
3+
use std::{
4+
borrow::Cow,
5+
iter::{self, once},
6+
sync::RwLock,
7+
};
48

9+
use once_cell::sync::Lazy;
10+
use rustc_hash::FxHashMap;
511
use serde::{Deserialize, Serialize};
612
use string_enum::StringEnum;
7-
use swc_atoms::{Atom, JsWord};
13+
use swc_atoms::{atom, Atom, JsWord};
814
use swc_common::{
915
comments::{Comment, CommentKind, Comments},
1016
errors::HANDLER,
@@ -56,12 +62,12 @@ pub struct Options {
5662

5763
/// For automatic runtime
5864
#[serde(default)]
59-
pub import_source: Option<String>,
65+
pub import_source: Option<Atom>,
6066

6167
#[serde(default)]
62-
pub pragma: Option<String>,
68+
pub pragma: Option<Lrc<String>>,
6369
#[serde(default)]
64-
pub pragma_frag: Option<String>,
70+
pub pragma_frag: Option<Lrc<String>>,
6571

6672
#[serde(default)]
6773
pub throw_if_namespace: Option<bool>,
@@ -93,16 +99,31 @@ pub struct Options {
9399
pub refresh: Option<RefreshOptions>,
94100
}
95101

96-
pub fn default_import_source() -> String {
97-
"react".into()
102+
#[cfg(feature = "concurrent")]
103+
macro_rules! static_str {
104+
($s:expr) => {{
105+
static VAL: Lazy<Lrc<String>> = Lazy::new(|| Lrc::new($s.into()));
106+
VAL.clone()
107+
}};
108+
}
109+
110+
#[cfg(not(feature = "concurrent"))]
111+
macro_rules! static_str {
112+
($s:expr) => {
113+
Lrc::new($s.into())
114+
};
115+
}
116+
117+
pub fn default_import_source() -> Atom {
118+
atom!("react")
98119
}
99120

100-
pub fn default_pragma() -> String {
101-
"React.createElement".into()
121+
pub fn default_pragma() -> Lrc<String> {
122+
static_str!("React.createElement")
102123
}
103124

104-
pub fn default_pragma_frag() -> String {
105-
"React.Fragment".into()
125+
pub fn default_pragma_frag() -> Lrc<String> {
126+
static_str!("React.Fragment")
106127
}
107128

108129
fn default_throw_if_namespace() -> bool {
@@ -113,10 +134,10 @@ fn default_throw_if_namespace() -> bool {
113134
pub fn parse_expr_for_jsx(
114135
cm: &SourceMap,
115136
name: &str,
116-
src: String,
137+
src: Lrc<String>,
117138
top_level_mark: Mark,
118-
) -> Arc<Box<Expr>> {
119-
let fm = cm.new_source_file(
139+
) -> Lrc<Box<Expr>> {
140+
let fm = cm.new_source_file_from(
120141
FileName::Internal(format!("jsx-config-{}.js", name)).into(),
121142
src,
122143
);
@@ -142,7 +163,7 @@ pub fn parse_expr_for_jsx(
142163
apply_mark(&mut expr, top_level_mark);
143164
expr
144165
})
145-
.map(Arc::new)
166+
.map(Lrc::new)
146167
.unwrap_or_else(|()| {
147168
panic!(
148169
"failed to parse jsx option {}: '{}' is not an expression",
@@ -198,10 +219,7 @@ where
198219
top_level_mark,
199220
unresolved_mark,
200221
runtime: options.runtime.unwrap_or_default(),
201-
import_source: options
202-
.import_source
203-
.unwrap_or_else(default_import_source)
204-
.into(),
222+
import_source: options.import_source.unwrap_or_else(default_import_source),
205223
import_jsx: None,
206224
import_jsxs: None,
207225
import_fragment: None,
@@ -239,7 +257,7 @@ where
239257

240258
runtime: Runtime,
241259
/// For automatic runtime.
242-
import_source: JsWord,
260+
import_source: Atom,
243261
/// For automatic runtime.
244262
import_jsx: Option<Ident>,
245263
/// For automatic runtime.
@@ -250,9 +268,9 @@ where
250268
import_fragment: Option<Ident>,
251269
top_level_node: bool,
252270

253-
pragma: Arc<Box<Expr>>,
271+
pragma: Lrc<Box<Expr>>,
254272
comments: Option<C>,
255-
pragma_frag: Arc<Box<Expr>>,
273+
pragma_frag: Lrc<Box<Expr>>,
256274
development: bool,
257275
throw_if_namespace: bool,
258276
}
@@ -262,13 +280,13 @@ pub struct JsxDirectives {
262280
pub runtime: Option<Runtime>,
263281

264282
/// For automatic runtime.
265-
pub import_source: Option<JsWord>,
283+
pub import_source: Option<Atom>,
266284

267285
/// Parsed from `@jsx`
268-
pub pragma: Option<Arc<Box<Expr>>>,
286+
pub pragma: Option<Lrc<Box<Expr>>>,
269287

270288
/// Parsed from `@jsxFrag`
271-
pub pragma_frag: Option<Arc<Box<Expr>>>,
289+
pub pragma_frag: Option<Lrc<Box<Expr>>>,
272290
}
273291

274292
fn respan(e: &mut Expr, span: Span) {
@@ -335,7 +353,7 @@ impl JsxDirectives {
335353
Some("@jsxImportSource") => {
336354
if let Some(src) = val {
337355
res.runtime = Some(Runtime::Automatic);
338-
res.import_source = Some(src.into());
356+
res.import_source = Some(Atom::new(src));
339357
}
340358
}
341359
Some("@jsxFrag") => {
@@ -345,7 +363,7 @@ impl JsxDirectives {
345363
let mut e = (*parse_expr_for_jsx(
346364
cm,
347365
"module-jsx-pragma-frag",
348-
src.to_string(),
366+
cache_source(src),
349367
top_level_mark,
350368
))
351369
.clone();
@@ -361,7 +379,7 @@ impl JsxDirectives {
361379
let mut e = (*parse_expr_for_jsx(
362380
cm,
363381
"module-jsx-pragma",
364-
src.to_string(),
382+
cache_source(src),
365383
top_level_mark,
366384
))
367385
.clone();
@@ -380,6 +398,33 @@ impl JsxDirectives {
380398
}
381399
}
382400

401+
#[cfg(feature = "concurrent")]
402+
fn cache_source(src: &str) -> Lrc<String> {
403+
static CACHE: Lazy<RwLock<FxHashMap<String, Lrc<String>>>> =
404+
Lazy::new(|| RwLock::new(FxHashMap::default()));
405+
406+
{
407+
let cache = CACHE.write().unwrap();
408+
409+
if let Some(cached) = cache.get(src) {
410+
return cached.clone();
411+
}
412+
}
413+
414+
let cached = Lrc::new(src.to_string());
415+
{
416+
let mut cache = CACHE.write().unwrap();
417+
cache.insert(src.to_string(), cached.clone());
418+
}
419+
cached
420+
}
421+
422+
#[cfg(not(feature = "concurrent"))]
423+
fn cache_source(src: &str) -> Lrc<String> {
424+
// We cannot cache because Rc does not implement Send.
425+
Lrc::new(src.to_string())
426+
}
427+
383428
fn is_valid_for_pragma(s: &str) -> bool {
384429
if s.is_empty() {
385430
return false;

‎crates/swc_ecma_transforms_react/src/jsx/tests.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -337,7 +337,7 @@ test!(
337337
|t| tr(
338338
t,
339339
Options {
340-
pragma: Some("dom".into()),
340+
pragma: Some(Lrc::new("dom".into())),
341341
..Default::default()
342342
},
343343
Mark::fresh(Mark::root())
@@ -808,7 +808,7 @@ test!(
808808
|t| tr(
809809
t,
810810
Options {
811-
pragma: Some("h".into()),
811+
pragma: Some(Lrc::new("h".into())),
812812
throw_if_namespace: false.into(),
813813
..Default::default()
814814
},

‎crates/swc_ecma_transforms_react/src/lib.rs

+1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
#![allow(clippy::mutable_key_type)]
33
#![allow(clippy::arc_with_non_send_sync)]
44
#![allow(rustc::untranslatable_diagnostic_trivial)]
5+
#![cfg_attr(not(feature = "concurrent"), allow(unused))]
56

67
use swc_common::{comments::Comments, sync::Lrc, Mark, SourceMap};
78
use swc_ecma_ast::Pass;

‎crates/swc_ecma_transforms_react/src/refresh/options.rs

+7-6
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,22 @@
11
use serde::{Deserialize, Deserializer, Serialize};
2+
use swc_atoms::{atom, Atom};
23
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq)]
34
#[serde(rename_all = "camelCase")]
45
#[serde(deny_unknown_fields)]
56
pub struct RefreshOptions {
67
#[serde(default = "default_refresh_reg")]
7-
pub refresh_reg: String,
8+
pub refresh_reg: Atom,
89
#[serde(default = "default_refresh_sig")]
9-
pub refresh_sig: String,
10+
pub refresh_sig: Atom,
1011
#[serde(default = "default_emit_full_signatures")]
1112
pub emit_full_signatures: bool,
1213
}
1314

14-
fn default_refresh_reg() -> String {
15-
"$RefreshReg$".to_string()
15+
fn default_refresh_reg() -> Atom {
16+
atom!("$RefreshReg$")
1617
}
17-
fn default_refresh_sig() -> String {
18-
"$RefreshSig$".to_string()
18+
fn default_refresh_sig() -> Atom {
19+
atom!("$RefreshSig$")
1920
}
2021
fn default_emit_full_signatures() -> bool {
2122
// Prefer to hash when we can

‎crates/swc_ecma_transforms_react/src/refresh/tests.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -710,8 +710,8 @@ test!(
710710
refresh(
711711
true,
712712
Some(RefreshOptions {
713-
refresh_reg: "import_meta_refreshReg".to_string(),
714-
refresh_sig: "import_meta_refreshSig".to_string(),
713+
refresh_reg: "import_meta_refreshReg".into(),
714+
refresh_sig: "import_meta_refreshSig".into(),
715715
emit_full_signatures: true,
716716
}),
717717
t.cm.clone(),

‎crates/swc_ecma_transforms_typescript/Cargo.toml

+6-2
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,14 @@ version = "7.0.0"
1212
[lib]
1313
bench = false
1414

15+
[features]
16+
concurrent = ["swc_common/concurrent"]
17+
1518
[dependencies]
16-
serde = { workspace = true, features = ["derive"] }
19+
once_cell = { workspace = true }
20+
ryu-js = { workspace = true }
21+
serde = { workspace = true, features = ["derive"] }
1722

18-
ryu-js = { workspace = true }
1923
swc_atoms = { version = "3.0.3", path = "../swc_atoms" }
2024
swc_common = { version = "5.0.0", path = "../swc_common" }
2125
swc_ecma_ast = { version = "5.0.3", path = "../swc_ecma_ast" }

‎crates/swc_ecma_transforms_typescript/src/config.rs

+3-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
use serde::{Deserialize, Serialize};
2+
use swc_common::sync::Lrc;
23

34
#[derive(Debug, Default, Serialize, Deserialize)]
45
#[serde(rename_all = "camelCase")]
@@ -39,11 +40,11 @@ pub struct Config {
3940
pub struct TsxConfig {
4041
/// Note: this pass handle jsx directives in comments
4142
#[serde(default)]
42-
pub pragma: Option<String>,
43+
pub pragma: Option<Lrc<String>>,
4344

4445
/// Note: this pass handle jsx directives in comments
4546
#[serde(default)]
46-
pub pragma_frag: Option<String>,
47+
pub pragma_frag: Option<Lrc<String>>,
4748
}
4849

4950
#[derive(Default, Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]

‎crates/swc_ecma_transforms_typescript/src/typescript.rs

+18-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use std::mem;
22

3+
use once_cell::sync::Lazy;
34
use swc_common::{
45
collections::AHashSet, comments::Comments, sync::Lrc, util::take::Take, Mark, SourceMap, Span,
56
Spanned,
@@ -11,6 +12,21 @@ use swc_ecma_visit::{visit_mut_pass, VisitMut, VisitMutWith};
1112
pub use crate::config::*;
1213
use crate::{strip_import_export::StripImportExport, strip_type::StripType, transform::transform};
1314

15+
#[cfg(feature = "concurrent")]
16+
macro_rules! static_str {
17+
($s:expr) => {{
18+
static VAL: Lazy<Lrc<String>> = Lazy::new(|| Lrc::new($s.into()));
19+
VAL.clone()
20+
}};
21+
}
22+
23+
#[cfg(not(feature = "concurrent"))]
24+
macro_rules! static_str {
25+
($s:expr) => {
26+
Lrc::new($s.into())
27+
};
28+
}
29+
1430
pub fn typescript(config: Config, unresolved_mark: Mark, top_level_mark: Mark) -> impl Pass {
1531
debug_assert_ne!(unresolved_mark, top_level_mark);
1632

@@ -199,7 +215,7 @@ where
199215
self.tsx_config
200216
.pragma
201217
.clone()
202-
.unwrap_or_else(|| "React.createElement".to_string()),
218+
.unwrap_or_else(|| static_str!("React.createElement")),
203219
self.top_level_mark,
204220
);
205221

@@ -209,7 +225,7 @@ where
209225
self.tsx_config
210226
.pragma_frag
211227
.clone()
212-
.unwrap_or_else(|| "React.Fragment".to_string()),
228+
.unwrap_or_else(|| static_str!("React.Fragment")),
213229
self.top_level_mark,
214230
);
215231

0 commit comments

Comments
 (0)
Please sign in to comment.