Skip to content

Commit aea29de

Browse files
authored
perf: replace source to original (#203)
1 parent ee72991 commit aea29de

File tree

8 files changed

+67
-10
lines changed

8 files changed

+67
-10
lines changed

src/cached_source.rs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -87,9 +87,7 @@ impl Source for CachedSource {
8787
}
8888

8989
fn buffer(&self) -> Cow<[u8]> {
90-
let mut buffer = vec![];
91-
self.to_writer(&mut buffer).unwrap();
92-
Cow::Owned(buffer)
90+
self.inner.buffer()
9391
}
9492

9593
fn size(&self) -> usize {
@@ -116,6 +114,10 @@ impl Source for CachedSource {
116114
}
117115
}
118116

117+
fn write_to_string(&self, string: &mut String) {
118+
self.inner.write_to_string(string);
119+
}
120+
119121
fn to_writer(&self, writer: &mut dyn std::io::Write) -> std::io::Result<()> {
120122
self.inner.to_writer(writer)
121123
}

src/concat_source.rs

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -167,11 +167,11 @@ impl Source for ConcatSource {
167167
if children.len() == 1 {
168168
children[0].source()
169169
} else {
170-
let mut buf = String::with_capacity(self.size());
171-
for child in children {
172-
buf.push_str(&child.source().into_string_lossy());
173-
}
174-
SourceValue::String(Cow::Owned(buf))
170+
// Use to_writer to avoid multiple heap allocations that would occur
171+
// when concatenating nested ConcatSource instances directly
172+
let mut string = String::with_capacity(self.size());
173+
self.write_to_string(&mut string);
174+
SourceValue::String(Cow::Owned(string))
175175
}
176176
}
177177

@@ -180,6 +180,8 @@ impl Source for ConcatSource {
180180
if children.len() == 1 {
181181
children[0].buffer()
182182
} else {
183+
// Use to_writer to avoid multiple heap allocations that would occur
184+
// when concatenating nested ConcatSource instances directly
183185
let mut buffer = Vec::with_capacity(self.size());
184186
self.to_writer(&mut buffer).unwrap();
185187
Cow::Owned(buffer)
@@ -204,6 +206,12 @@ impl Source for ConcatSource {
204206
result
205207
}
206208

209+
fn write_to_string(&self, string: &mut String) {
210+
for child in self.optimized_children() {
211+
child.write_to_string(string);
212+
}
213+
}
214+
207215
fn to_writer(&self, writer: &mut dyn std::io::Write) -> std::io::Result<()> {
208216
for child in self.optimized_children() {
209217
child.to_writer(writer)?;

src/original_source.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,10 @@ impl Source for OriginalSource {
7373
get_map(object_pool, chunks.as_ref(), options)
7474
}
7575

76+
fn write_to_string(&self, string: &mut String) {
77+
string.push_str(self.value.as_ref());
78+
}
79+
7680
fn to_writer(&self, writer: &mut dyn std::io::Write) -> std::io::Result<()> {
7781
writer.write_all(self.value.as_bytes())
7882
}

src/raw_source.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,10 @@ impl Source for RawStringSource {
7676
None
7777
}
7878

79+
fn write_to_string(&self, string: &mut String) {
80+
string.push_str(self.0.as_ref());
81+
}
82+
7983
fn to_writer(&self, writer: &mut dyn std::io::Write) -> std::io::Result<()> {
8084
writer.write_all(self.0.as_bytes())
8185
}
@@ -218,6 +222,10 @@ impl Source for RawBufferSource {
218222
None
219223
}
220224

225+
fn write_to_string(&self, string: &mut String) {
226+
string.push_str(self.get_or_init_value_as_string());
227+
}
228+
221229
fn to_writer(&self, writer: &mut dyn std::io::Write) -> std::io::Result<()> {
222230
writer.write_all(&self.value)
223231
}

src/replace_source.rs

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@ use crate::{
1212
linear_map::LinearMap,
1313
object_pool::ObjectPool,
1414
source_content_lines::SourceContentLines,
15-
BoxSource, MapOptions, Mapping, OriginalLocation, Source, SourceExt,
16-
SourceMap, SourceValue,
15+
BoxSource, MapOptions, Mapping, OriginalLocation, OriginalSource, Source,
16+
SourceExt, SourceMap, SourceValue,
1717
};
1818

1919
/// Decorates a Source with replacements and insertions of source code,
@@ -244,6 +244,10 @@ impl Source for ReplaceSource {
244244
get_map(&ObjectPool::default(), chunks.as_ref(), options)
245245
}
246246

247+
fn write_to_string(&self, string: &mut String) {
248+
string.push_str(&self.source().into_string_lossy());
249+
}
250+
247251
fn to_writer(&self, writer: &mut dyn std::io::Write) -> std::io::Result<()> {
248252
writer.write_all(self.source().as_bytes())
249253
}
@@ -312,13 +316,17 @@ fn check_content_at_position(
312316
}
313317

314318
struct ReplaceSourceChunks<'a> {
319+
is_original_source: bool,
315320
chunks: Box<dyn Chunks + 'a>,
316321
replacements: &'a [Replacement],
317322
}
318323

319324
impl<'a> ReplaceSourceChunks<'a> {
320325
pub fn new(source: &'a ReplaceSource) -> Self {
326+
let is_original_source =
327+
source.inner.as_ref().as_any().is::<OriginalSource>();
321328
Self {
329+
is_original_source,
322330
chunks: source.inner.stream_chunks(),
323331
replacements: &source.replacements,
324332
}
@@ -378,6 +386,13 @@ impl Chunks for ReplaceSourceChunks<'_> {
378386
// webpack-sources also have this function, refer https://github.com/webpack/webpack-sources/blob/main/lib/ReplaceSource.js#L158
379387
let check_original_content =
380388
|source_index: u32, line: u32, column: u32, expected_chunk: &str| {
389+
// Performance optimization: Skip content validation for OriginalSourceChunks.
390+
// Since OriginalSourceChunks guarantees that the source content matches the actual source,
391+
// we can safely bypass the expensive content checking process.
392+
if self.is_original_source {
393+
return true;
394+
}
395+
381396
if let Some(Some(source_content)) =
382397
source_content_lines.borrow_mut().get_mut(&source_index)
383398
{

src/source.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,14 @@ pub trait Source:
132132
self.dyn_hash(state);
133133
}
134134

135+
/// Appends the source content to the provided string buffer.
136+
///
137+
/// This method efficiently writes the source content directly into an existing
138+
/// string buffer, avoiding additional memory allocations when the buffer has
139+
/// sufficient capacity. This is particularly useful for concatenating multiple
140+
/// sources or building larger strings incrementally.
141+
fn write_to_string(&self, string: &mut String);
142+
135143
/// Writes the source into a writer, preferably a `std::io::BufWriter<std::io::Write>`.
136144
fn to_writer(&self, writer: &mut dyn std::io::Write) -> std::io::Result<()>;
137145
}
@@ -157,6 +165,10 @@ impl Source for BoxSource {
157165
self.as_ref().map(object_pool, options)
158166
}
159167

168+
fn write_to_string(&self, string: &mut String) {
169+
self.as_ref().write_to_string(string)
170+
}
171+
160172
fn to_writer(&self, writer: &mut dyn std::io::Write) -> std::io::Result<()> {
161173
self.as_ref().to_writer(writer)
162174
}

src/source_map_source.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,10 @@ impl Source for SourceMapSource {
114114
get_map(object_pool, chunks.as_ref(), options)
115115
}
116116

117+
fn write_to_string(&self, string: &mut String) {
118+
string.push_str(self.value.as_ref());
119+
}
120+
117121
fn to_writer(&self, writer: &mut dyn std::io::Write) -> std::io::Result<()> {
118122
writer.write_all(self.value.as_bytes())
119123
}

tests/compat_source.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,10 @@ impl Source for CompatSource {
3535
self.1.clone()
3636
}
3737

38+
fn write_to_string(&self, string: &mut String) {
39+
string.push_str(self.0.as_ref())
40+
}
41+
3842
fn to_writer(&self, writer: &mut dyn std::io::Write) -> std::io::Result<()> {
3943
writer.write_all(self.0.as_bytes())
4044
}

0 commit comments

Comments
 (0)