Skip to content

Commit bac3b9b

Browse files
authored
fix: cached source (#94)
1 parent 237eb7b commit bac3b9b

File tree

3 files changed

+198
-30
lines changed

3 files changed

+198
-30
lines changed

src/cached_source.rs

Lines changed: 171 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use std::{
44
sync::{Arc, OnceLock},
55
};
66

7-
use dashmap::DashMap;
7+
use dashmap::{mapref::entry::Entry, DashMap};
88
use rustc_hash::FxHasher;
99

1010
use crate::{
@@ -50,8 +50,9 @@ use crate::{
5050
/// ```
5151
pub struct CachedSource<T> {
5252
inner: Arc<T>,
53-
cached_buffer: OnceLock<Arc<Vec<u8>>>,
54-
cached_source: OnceLock<Arc<str>>,
53+
cached_buffer: Arc<OnceLock<Vec<u8>>>,
54+
cached_source: Arc<OnceLock<Arc<str>>>,
55+
cached_size: Arc<OnceLock<usize>>,
5556
cached_maps:
5657
Arc<DashMap<MapOptions, Option<SourceMap>, BuildHasherDefault<FxHasher>>>,
5758
}
@@ -63,6 +64,7 @@ impl<T> CachedSource<T> {
6364
inner: Arc::new(inner),
6465
cached_buffer: Default::default(),
6566
cached_source: Default::default(),
67+
cached_size: Default::default(),
6668
cached_maps: Default::default(),
6769
}
6870
}
@@ -82,14 +84,25 @@ impl<T: Source + Hash + PartialEq + Eq + 'static> Source for CachedSource<T> {
8284
}
8385

8486
fn buffer(&self) -> Cow<[u8]> {
85-
let cached = self
86-
.cached_buffer
87-
.get_or_init(|| self.inner.buffer().to_vec().into());
87+
let cached = self.cached_buffer.get_or_init(|| {
88+
let source = self.cached_source.get();
89+
match source {
90+
Some(source) => source.as_bytes().to_vec(),
91+
None => self.inner.buffer().to_vec(),
92+
}
93+
});
8894
Cow::Borrowed(cached)
8995
}
9096

9197
fn size(&self) -> usize {
92-
self.inner.size()
98+
let cached = self.cached_size.get_or_init(|| {
99+
let source = self.cached_source.get();
100+
match source {
101+
Some(source) => source.len(),
102+
None => self.inner.size(),
103+
}
104+
});
105+
*cached
93106
}
94107

95108
fn map(&self, options: &MapOptions) -> Option<SourceMap> {
@@ -117,27 +130,32 @@ impl<T: Source + Hash + PartialEq + Eq + 'static> StreamChunks
117130
on_source: crate::helpers::OnSource,
118131
on_name: crate::helpers::OnName,
119132
) -> crate::helpers::GeneratedInfo {
120-
if self.cached_maps.contains_key(options) {
121-
let source = self.source();
122-
if let Some(map) = &self.map(options) {
123-
return stream_chunks_of_source_map(
124-
&source, map, on_chunk, on_source, on_name, options,
125-
);
126-
} else {
127-
return stream_chunks_of_raw_source(
128-
&source, options, on_chunk, on_source, on_name,
133+
let cached_map = self.cached_maps.entry(options.clone());
134+
match cached_map {
135+
Entry::Occupied(entry) => {
136+
let source = self.source();
137+
if let Some(map) = entry.get() {
138+
stream_chunks_of_source_map(
139+
&source, map, on_chunk, on_source, on_name, options,
140+
)
141+
} else {
142+
stream_chunks_of_raw_source(
143+
&source, options, on_chunk, on_source, on_name,
144+
)
145+
}
146+
}
147+
Entry::Vacant(entry) => {
148+
let (generated_info, map) = stream_and_get_source_and_map(
149+
self.original(),
150+
options,
151+
on_chunk,
152+
on_source,
153+
on_name,
129154
);
155+
entry.insert(map);
156+
generated_info
130157
}
131158
}
132-
let (generated_info, map) = stream_and_get_source_and_map(
133-
self.original(),
134-
options,
135-
on_chunk,
136-
on_source,
137-
on_name,
138-
);
139-
self.cached_maps.insert(options.clone(), map);
140-
generated_info
141159
}
142160
}
143161

@@ -147,6 +165,7 @@ impl<T: Source> Clone for CachedSource<T> {
147165
inner: self.inner.clone(),
148166
cached_buffer: self.cached_buffer.clone(),
149167
cached_source: self.cached_source.clone(),
168+
cached_size: self.cached_size.clone(),
150169
cached_maps: self.cached_maps.clone(),
151170
}
152171
}
@@ -182,8 +201,11 @@ impl<T: std::fmt::Debug> std::fmt::Debug for CachedSource<T> {
182201

183202
#[cfg(test)]
184203
mod tests {
204+
use std::borrow::Borrow;
205+
185206
use crate::{
186-
ConcatSource, RawSource, SourceExt, SourceMapSource, WithoutOriginalOptions,
207+
ConcatSource, OriginalSource, RawSource, SourceExt, SourceMapSource,
208+
WithoutOriginalOptions,
187209
};
188210

189211
use super::*;
@@ -208,4 +230,127 @@ mod tests {
208230
let map = source.map(&Default::default()).unwrap();
209231
assert_eq!(map.mappings(), ";;AACA");
210232
}
233+
234+
#[test]
235+
fn should_allow_to_store_and_share_cached_data() {
236+
let original = OriginalSource::new("Hello World", "test.txt");
237+
let source = CachedSource::new(original);
238+
let clone = source.clone();
239+
240+
// fill up cache
241+
let map_options = MapOptions::default();
242+
source.source();
243+
source.buffer();
244+
source.size();
245+
source.map(&map_options);
246+
247+
assert_eq!(clone.cached_source.get().unwrap().borrow(), source.source());
248+
assert_eq!(
249+
*clone.cached_buffer.get().unwrap(),
250+
source.buffer().to_vec()
251+
);
252+
assert_eq!(*clone.cached_size.get().unwrap(), source.size());
253+
assert_eq!(
254+
*clone.cached_maps.get(&map_options).unwrap().value(),
255+
source.map(&map_options)
256+
);
257+
}
258+
259+
#[test]
260+
fn should_return_the_correct_size_for_binary_files() {
261+
let source = OriginalSource::new(
262+
String::from_utf8(vec![0; 256]).unwrap(),
263+
"file.wasm",
264+
);
265+
let cached_source = CachedSource::new(source);
266+
267+
assert_eq!(cached_source.size(), 256);
268+
assert_eq!(cached_source.size(), 256);
269+
}
270+
271+
#[test]
272+
fn should_return_the_correct_size_for_cached_binary_files() {
273+
let source = OriginalSource::new(
274+
String::from_utf8(vec![0; 256]).unwrap(),
275+
"file.wasm",
276+
);
277+
let cached_source = CachedSource::new(source);
278+
279+
cached_source.source();
280+
assert_eq!(cached_source.size(), 256);
281+
assert_eq!(cached_source.size(), 256);
282+
}
283+
284+
#[test]
285+
fn should_return_the_correct_size_for_text_files() {
286+
let source = OriginalSource::new("TestTestTest", "file.js");
287+
let cached_source = CachedSource::new(source);
288+
289+
assert_eq!(cached_source.size(), 12);
290+
assert_eq!(cached_source.size(), 12);
291+
}
292+
293+
#[test]
294+
fn should_return_the_correct_size_for_cached_text_files() {
295+
let source = OriginalSource::new("TestTestTest", "file.js");
296+
let cached_source = CachedSource::new(source);
297+
298+
cached_source.source();
299+
assert_eq!(cached_source.size(), 12);
300+
assert_eq!(cached_source.size(), 12);
301+
}
302+
303+
#[test]
304+
fn should_produce_correct_output_for_cached_raw_source() {
305+
let map_options = MapOptions {
306+
columns: true,
307+
final_source: true,
308+
};
309+
310+
let source = RawSource::from("Test\nTest\nTest\n");
311+
let mut on_chunk_count = 0;
312+
let mut on_source_count = 0;
313+
let mut on_name_count = 0;
314+
let generated_info = source.stream_chunks(
315+
&map_options,
316+
&mut |_chunk, _mapping| {
317+
on_chunk_count += 1;
318+
},
319+
&mut |_source_index, _source, _source_content| {
320+
on_source_count += 1;
321+
},
322+
&mut |_name_index, _name| {
323+
on_name_count += 1;
324+
},
325+
);
326+
327+
let cached_source = CachedSource::new(source);
328+
cached_source.stream_chunks(
329+
&map_options,
330+
&mut |_chunk, _mapping| {},
331+
&mut |_source_index, _source, _source_content| {},
332+
&mut |_name_index, _name| {},
333+
);
334+
335+
let mut cached_on_chunk_count = 0;
336+
let mut cached_on_source_count = 0;
337+
let mut cached_on_name_count = 0;
338+
let cached_generated_info = cached_source.stream_chunks(
339+
&map_options,
340+
&mut |_chunk, _mapping| {
341+
cached_on_chunk_count += 1;
342+
},
343+
&mut |_source_index, _source, _source_content| {
344+
cached_on_source_count += 1;
345+
},
346+
&mut |_name_index, _name| {
347+
cached_on_name_count += 1;
348+
},
349+
);
350+
351+
assert_eq!(on_chunk_count, cached_on_chunk_count);
352+
assert_eq!(on_source_count, cached_on_source_count);
353+
assert_eq!(on_name_count, cached_on_name_count);
354+
assert_eq!(generated_info, cached_generated_info);
355+
}
211356
}

src/helpers.rs

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ pub fn stream_chunks_default<S: Source>(
113113
}
114114

115115
/// `GeneratedSourceInfo` abstraction, see [webpack-sources GeneratedSourceInfo](https://github.com/webpack/webpack-sources/blob/9f98066311d53a153fdc7c633422a1d086528027/lib/helpers/getGeneratedSourceInfo.js)
116-
#[derive(Debug)]
116+
#[derive(Debug, PartialEq, Eq)]
117117
pub struct GeneratedInfo {
118118
/// Generated line
119119
pub generated_line: u32,
@@ -519,11 +519,15 @@ pub fn get_generated_source_info(source: &str) -> GeneratedInfo {
519519

520520
pub fn stream_chunks_of_raw_source(
521521
source: &str,
522-
_options: &MapOptions,
522+
options: &MapOptions,
523523
on_chunk: OnChunk,
524524
_on_source: OnSource,
525525
_on_name: OnName,
526526
) -> GeneratedInfo {
527+
if options.final_source {
528+
return get_generated_source_info(source);
529+
}
530+
527531
let mut line = 1;
528532
let mut last_line = None;
529533
for l in split_into_lines(source) {
@@ -1465,7 +1469,7 @@ pub fn stream_and_get_source_and_map<S: StreamChunks>(
14651469
on_source: OnSource,
14661470
on_name: OnName,
14671471
) -> (GeneratedInfo, Option<SourceMap>) {
1468-
let mut mappings = vec![];
1472+
let mut mappings = Vec::new();
14691473
let mut sources: Vec<Cow<'static, str>> = Vec::new();
14701474
let mut sources_content: Vec<Cow<'static, str>> = Vec::new();
14711475
let mut names: Vec<Cow<'static, str>> = Vec::new();

src/source_map_source.rs

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -183,7 +183,8 @@ impl StreamChunks for SourceMapSource {
183183
#[cfg(test)]
184184
mod tests {
185185
use crate::{
186-
ConcatSource, OriginalSource, RawSource, ReplaceSource, SourceExt,
186+
CachedSource, ConcatSource, OriginalSource, RawSource, ReplaceSource,
187+
SourceExt,
187188
};
188189

189190
use super::*;
@@ -421,6 +422,24 @@ mod tests {
421422
map.mappings(),
422423
"AAAA;AAAA;ACAA,ICAA,EDAA,ECAA,EFAA;AEAA,EFAA;ACAA",
423424
);
425+
426+
macro_rules! test_cached {
427+
($s:expr, $fn:expr) => {{
428+
let c = CachedSource::new($s.clone());
429+
let o = $fn(&$s);
430+
let a = $fn(&c);
431+
assert_eq!(a, o);
432+
let b = $fn(&c);
433+
assert_eq!(b, o);
434+
}};
435+
}
436+
437+
test_cached!(source, |s: &dyn Source| s.source().to_string());
438+
test_cached!(source, |s: &dyn Source| s.map(&MapOptions::default()));
439+
test_cached!(source, |s: &dyn Source| s.map(&MapOptions {
440+
columns: false,
441+
final_source: true
442+
}));
424443
}
425444

426445
#[test]

0 commit comments

Comments
 (0)