Skip to main content

turbopack_core/source_map/
mod.rs

1use std::{
2    borrow::Cow,
3    io::Write,
4    ops::Deref,
5    sync::{Arc, LazyLock},
6};
7
8use anyhow::Result;
9use bincode::{
10    Decode, Encode,
11    de::Decoder,
12    enc::Encoder,
13    error::{DecodeError, EncodeError},
14};
15use bytes_str::BytesStr;
16use either::Either;
17use ref_cast::RefCast;
18use regex::Regex;
19use swc_sourcemap::{DecodedMap, SourceMap as RegularMap, SourceMapBuilder, SourceMapIndex};
20use turbo_rcstr::{RcStr, rcstr};
21use turbo_tasks::{ResolvedVc, TryJoinIterExt, ValueToStringRef, Vc};
22use turbo_tasks_fs::{
23    File, FileContent, FileSystem, FileSystemPath, VirtualFileSystem,
24    rope::{Rope, RopeBuilder},
25};
26
27use crate::{
28    SOURCE_URL_PROTOCOL, asset::AssetContent, source::Source,
29    source_map::utils::add_default_ignore_list, source_pos::SourcePos,
30    virtual_source::VirtualSource,
31};
32
33pub(crate) mod source_map_asset;
34pub mod structured;
35pub mod utils;
36
37pub use source_map_asset::SourceMapAsset;
38
39/// Represents an empty value in a u32 variable in the sourcemap crate.
40static SOURCEMAP_CRATE_NONE_U32: u32 = !0;
41
42/// Allows callers to generate source maps.
43#[turbo_tasks::value_trait]
44pub trait GenerateSourceMap {
45    /// Generates a usable source map, capable of both tracing and stringifying.
46    #[turbo_tasks::function]
47    fn generate_source_map(self: Vc<Self>) -> Vc<FileContent>;
48
49    /// Returns an individual section of the larger source map, if found.
50    #[turbo_tasks::function]
51    fn by_section(self: Vc<Self>, _section: RcStr) -> Vc<FileContent> {
52        FileContent::NotFound.cell()
53    }
54}
55
56/// Implements the source map specification as a [decoded][`DecodedMap`] sourcemap. A "decoded" map
57/// represents a source map as if it was came out of a JSON decode.
58#[turbo_tasks::value(shared, cell = "new", eq = "manual")]
59#[derive(Debug)]
60pub struct SourceMap {
61    /// A decoded source map contains no Vcs.
62    #[turbo_tasks(trace_ignore)]
63    map: Arc<CrateMapWrapper>,
64}
65impl Eq for SourceMap {}
66impl PartialEq for SourceMap {
67    fn eq(&self, other: &Self) -> bool {
68        Arc::ptr_eq(&self.map, &other.map)
69    }
70}
71
72#[turbo_tasks::value(transparent)]
73pub struct OptionSourceMap(Option<SourceMap>);
74
75#[turbo_tasks::value_impl]
76impl OptionSourceMap {
77    #[turbo_tasks::function]
78    pub fn none() -> Vc<Self> {
79        Vc::cell(None)
80    }
81}
82
83impl OptionSourceMap {
84    pub fn none_resolved() -> ResolvedVc<Self> {
85        ResolvedVc::cell(None)
86    }
87}
88
89/// A token represents a mapping in a source map.
90///
91/// It may either be Synthetic, meaning it was generated by some build tool and doesn't
92/// represent a location in a user-authored source file, or it is Original, meaning it represents a
93/// real location in source file.
94#[turbo_tasks::value]
95#[derive(Clone, Debug)]
96pub enum Token {
97    Synthetic(SyntheticToken),
98    Original(OriginalToken),
99}
100
101#[turbo_tasks::value]
102#[derive(Clone, Debug)]
103pub struct TokenWithSource {
104    pub token: Token,
105    pub source_content: Option<ResolvedVc<Box<dyn Source>>>,
106}
107
108/// A SyntheticToken represents a region of the generated file that was created
109/// by some build tool.
110#[turbo_tasks::value]
111#[derive(Clone, Debug)]
112pub struct SyntheticToken {
113    pub generated_line: u32,
114    pub generated_column: u32,
115    pub guessed_original_file: Option<RcStr>,
116}
117
118/// An OriginalToken represents a region of the generated file that exists in
119/// user-authored source file.
120#[turbo_tasks::value]
121#[derive(Clone, Debug)]
122pub struct OriginalToken {
123    pub generated_line: u32,
124    pub generated_column: u32,
125    pub original_file: RcStr,
126    pub original_line: u32,
127    pub original_column: u32,
128    pub name: Option<RcStr>,
129    /// Whether this token's source is in the sourcemap's `ignoreList`.
130    pub is_ignored: bool,
131}
132
133impl Token {
134    pub fn generated_line(&self) -> u32 {
135        match self {
136            Self::Original(t) => t.generated_line,
137            Self::Synthetic(t) => t.generated_line,
138        }
139    }
140
141    pub fn generated_column(&self) -> u32 {
142        match self {
143            Self::Original(t) => t.generated_column,
144            Self::Synthetic(t) => t.generated_column,
145        }
146    }
147
148    pub fn with_offset(&self, line_offset: u32, column_offset: u32) -> Self {
149        match self {
150            Self::Original(t) => Self::Original(OriginalToken {
151                generated_line: t.generated_line + line_offset,
152                generated_column: if t.generated_line == 0 {
153                    t.generated_column + column_offset
154                } else {
155                    t.generated_column
156                },
157                original_file: t.original_file.clone(),
158                original_line: t.original_line,
159                original_column: t.original_column,
160                name: t.name.clone(),
161                is_ignored: t.is_ignored,
162            }),
163            Self::Synthetic(t) => Self::Synthetic(SyntheticToken {
164                generated_line: t.generated_line + line_offset,
165                generated_column: if t.generated_line == 0 {
166                    t.generated_column + column_offset
167                } else {
168                    t.generated_column
169                },
170                guessed_original_file: t.guessed_original_file.clone(),
171            }),
172        }
173    }
174}
175
176impl From<swc_sourcemap::Token<'_>> for Token {
177    fn from(t: swc_sourcemap::Token) -> Self {
178        if t.has_source() {
179            Token::Original(OriginalToken {
180                generated_line: t.get_dst_line(),
181                generated_column: t.get_dst_col(),
182                original_file: RcStr::from(
183                    t.get_source()
184                        .expect("already checked token has source")
185                        .clone(),
186                ),
187                original_line: t.get_src_line(),
188                original_column: t.get_src_col(),
189                name: t.get_name().cloned().map(RcStr::from),
190                // Set to false initially; will be updated by
191                // lookup_token_and_source_internal which has access to the full
192                // source map's ignoreList.
193                is_ignored: false,
194            })
195        } else {
196            Token::Synthetic(SyntheticToken {
197                generated_line: t.get_dst_line(),
198                generated_column: t.get_dst_col(),
199                guessed_original_file: None,
200            })
201        }
202    }
203}
204
205impl TryInto<swc_sourcemap::RawToken> for Token {
206    type Error = std::num::ParseIntError;
207
208    fn try_into(self) -> Result<swc_sourcemap::RawToken, Self::Error> {
209        Ok(match self {
210            Self::Original(t) => swc_sourcemap::RawToken {
211                dst_col: t.generated_column,
212                dst_line: t.generated_line,
213                name_id: match t.name {
214                    None => SOURCEMAP_CRATE_NONE_U32,
215                    Some(name) => name.parse()?,
216                },
217                src_col: t.original_column,
218                src_line: t.original_line,
219                src_id: t.original_file.parse()?,
220                is_range: false,
221            },
222            Self::Synthetic(t) => swc_sourcemap::RawToken {
223                dst_col: t.generated_column,
224                dst_line: t.generated_line,
225                name_id: SOURCEMAP_CRATE_NONE_U32,
226                src_col: SOURCEMAP_CRATE_NONE_U32,
227                src_line: SOURCEMAP_CRATE_NONE_U32,
228                src_id: SOURCEMAP_CRATE_NONE_U32,
229                is_range: false,
230            },
231        })
232    }
233}
234
235impl SourceMap {
236    fn new_regular(map: RegularMap) -> Self {
237        Self::new_decoded(DecodedMap::Regular(map))
238    }
239
240    fn new_decoded(map: DecodedMap) -> Self {
241        SourceMap {
242            map: Arc::new(CrateMapWrapper(map)),
243        }
244    }
245
246    pub fn new_from_rope(content: &Rope) -> Result<Option<Self>> {
247        let Ok(map) = DecodedMap::from_reader(content.read()) else {
248            return Ok(None);
249        };
250        Ok(Some(SourceMap::new_decoded(map)))
251    }
252}
253
254#[turbo_tasks::value_impl]
255impl SourceMap {
256    /// This function should be used sparingly to reduce memory usage, only in cold code paths
257    /// (issue resolving, etc).
258    #[turbo_tasks::function]
259    pub async fn new_from_rope_cached(content: Vc<FileContent>) -> Result<Vc<OptionSourceMap>> {
260        let content = content.await?;
261        let Some(content) = content.as_content() else {
262            return Ok(OptionSourceMap::none());
263        };
264        Ok(Vc::cell(SourceMap::new_from_rope(content.content())?))
265    }
266}
267
268impl SourceMap {
269    pub fn to_source_map(&self) -> Arc<CrateMapWrapper> {
270        self.map.clone()
271    }
272}
273
274static EMPTY_SOURCE_MAP_ROPE: LazyLock<Rope> =
275    LazyLock::new(|| Rope::from(r#"{"version":3,"sources":[],"names":[],"mappings":"A"}"#));
276
277impl SourceMap {
278    /// A source map that contains no actual source location information (no
279    /// `sources`, no mappings that point into a source). This is used to tell
280    /// Chrome that the generated code starting at a particular offset is no
281    /// longer part of the previous section's mappings.
282    pub fn empty() -> Self {
283        let mut builder = SourceMapBuilder::new(None);
284        builder.add(0, 0, 0, 0, None, None, false);
285        SourceMap::new_regular(builder.into_sourcemap())
286    }
287
288    /// A source map that contains no actual source location information (no
289    /// `sources`, no mappings that point into a source). This is used to tell
290    /// Chrome that the generated code starting at a particular offset is no
291    /// longer part of the previous section's mappings.
292    pub fn empty_rope() -> Rope {
293        EMPTY_SOURCE_MAP_ROPE.clone()
294    }
295
296    pub fn sections_to_rope(
297        sections: impl IntoIterator<Item = (SourcePos, Rope)>,
298        debug_id: Option<RcStr>,
299    ) -> Rope {
300        let mut sections = sections.into_iter().peekable();
301
302        let mut first = sections.next();
303        if let Some((offset, map)) = &mut first
304            && sections.peek().is_none()
305            && *offset == (0, 0)
306            && debug_id.is_none()
307        {
308            // There is just a single sourcemap that starts at the beginning of the file.
309            return std::mem::take(map);
310        }
311
312        // My kingdom for a decent dedent macro with interpolation!
313        // NOTE: The empty `sources` array is technically incorrect, but there is a bug
314        // in Node.js that requires sectioned source maps to have a `sources` array.
315        let mut rope = RopeBuilder::from(
316            r#"{
317  "version": 3,
318  "sources": [],
319"#,
320        );
321        if let Some(debug_id) = debug_id {
322            writeln!(rope, r#"  "debugId": "{debug_id}","#).unwrap();
323        }
324        rope += "  \"sections\": [";
325
326        let mut first_section = true;
327        for (offset, section_map) in first.into_iter().chain(sections) {
328            if !first_section {
329                rope += ",";
330            }
331            first_section = false;
332
333            write!(
334                rope,
335                r#"
336    {{"offset": {{"line": {}, "column": {}}}, "map": "#,
337                offset.line, offset.column,
338            )
339            .unwrap();
340
341            rope += &section_map;
342
343            rope += "}";
344        }
345
346        rope += "]";
347
348        rope += "\n}";
349
350        rope.build()
351    }
352
353    /// Stringifies the source map into JSON bytes.
354    pub fn to_rope(&self) -> Result<Rope> {
355        let mut bytes = vec![];
356        self.map.0.to_writer(&mut bytes)?;
357        Ok(Rope::from(bytes))
358    }
359
360    /// Traces a generated line/column into an mapping token representing either
361    /// synthetic code or user-authored original code.
362    pub fn lookup_token(&self, line: u32, column: u32) -> Token {
363        let (token, _) = self.lookup_token_and_source_internal(line, column, true);
364        token
365    }
366
367    /// Traces a generated line/column into an mapping token representing either
368    /// synthetic code or user-authored original code.
369    pub async fn lookup_token_and_source(&self, line: u32, column: u32) -> Result<TokenWithSource> {
370        let (token, content) = self.lookup_token_and_source_internal(line, column, true);
371        Ok(TokenWithSource {
372            token,
373            source_content: match content {
374                Some(v) => Some(v.to_resolved().await?),
375                None => None,
376            },
377        })
378    }
379
380    pub async fn with_resolved_sources(&self, origin: FileSystemPath) -> Result<Self> {
381        async fn resolve_source(
382            source_request: BytesStr,
383            source_content: Option<BytesStr>,
384            origin: FileSystemPath,
385        ) -> Result<(BytesStr, BytesStr)> {
386            Ok(
387                if let Some(path) = origin.parent().try_join(&source_request) {
388                    let path_str = path.to_string_ref().await?;
389                    let source = format!("{SOURCE_URL_PROTOCOL}///{path_str}");
390                    let source_content = if let Some(source_content) = source_content {
391                        source_content
392                    } else if let FileContent::Content(file) = &*path.read().await? {
393                        let text = file.content().to_str()?;
394                        text.to_string().into()
395                    } else {
396                        format!("unable to read source {path_str}").into()
397                    };
398                    (source.into(), source_content)
399                } else {
400                    let origin_str = origin.to_string_ref().await?;
401                    static INVALID_REGEX: LazyLock<Regex> =
402                        LazyLock::new(|| Regex::new(r#"(?:^|/)(?:\.\.?(?:/|$))+"#).unwrap());
403                    let source = INVALID_REGEX
404                        .replace_all(&source_request, |s: &regex::Captures<'_>| {
405                            s[0].replace('.', "_")
406                        });
407                    let source = format!("{SOURCE_URL_PROTOCOL}///{origin_str}/{source}");
408                    let source_content = source_content.unwrap_or_else(|| {
409                        format!(
410                            "unable to access {source_request} in {origin_str} (it's leaving the \
411                             filesystem root)"
412                        )
413                        .into()
414                    });
415                    (source.into(), source_content)
416                },
417            )
418        }
419        async fn regular_map_with_resolved_sources(
420            map: &RegularMapWrapper,
421            origin: FileSystemPath,
422        ) -> Result<RegularMap> {
423            let map = &map.0;
424            let file = map.get_file().cloned();
425            let tokens = map.tokens().map(|t| t.get_raw_token()).collect();
426            let names = map.names().cloned().collect();
427            let count = map.get_source_count() as usize;
428            let sources = map.sources().cloned().collect::<Vec<_>>();
429            let source_contents = map
430                .source_contents()
431                .map(|s| s.cloned())
432                .collect::<Vec<_>>();
433            let mut new_sources = Vec::with_capacity(count);
434            let mut new_source_contents = Vec::with_capacity(count);
435            for (source, source_content) in sources.into_iter().zip(source_contents) {
436                let (source, source_content) =
437                    resolve_source(source, source_content, origin.clone()).await?;
438                new_sources.push(source);
439                new_source_contents.push(Some(source_content));
440            }
441            let mut map =
442                RegularMap::new(file, tokens, names, new_sources, Some(new_source_contents));
443
444            add_default_ignore_list(&mut map);
445
446            Ok(map)
447        }
448        async fn decoded_map_with_resolved_sources(
449            map: &CrateMapWrapper,
450            origin: FileSystemPath,
451        ) -> Result<CrateMapWrapper> {
452            Ok(CrateMapWrapper(match &map.0 {
453                DecodedMap::Regular(map) => {
454                    let map = RegularMapWrapper::ref_cast(map);
455                    DecodedMap::Regular(regular_map_with_resolved_sources(map, origin).await?)
456                }
457                DecodedMap::Index(map) => {
458                    let count = map.get_section_count() as usize;
459                    let file = map.get_file().cloned();
460                    let sections = map
461                        .sections()
462                        .filter_map(|section| {
463                            section
464                                .get_sourcemap()
465                                .map(|s| (section.get_offset(), CrateMapWrapper::ref_cast(s)))
466                        })
467                        .collect::<Vec<_>>();
468                    let sections = sections
469                        .into_iter()
470                        .map(|(offset, map)| {
471                            let origin = origin.clone();
472                            async move {
473                                Ok((
474                                    offset,
475                                    Box::pin(decoded_map_with_resolved_sources(
476                                        map,
477                                        origin.clone(),
478                                    ))
479                                    .await?,
480                                ))
481                            }
482                        })
483                        .try_join()
484                        .await?;
485                    let mut new_sections = Vec::with_capacity(count);
486                    for (offset, map) in sections {
487                        new_sections.push(swc_sourcemap::SourceMapSection::new(
488                            offset,
489                            // Urls are deprecated and we don't accept them
490                            None,
491                            Some(map.0),
492                        ));
493                    }
494                    DecodedMap::Index(SourceMapIndex::new(file, new_sections))
495                }
496                DecodedMap::Hermes(_) => {
497                    todo!("hermes source maps are not implemented");
498                }
499            }))
500        }
501
502        let map = Box::pin(decoded_map_with_resolved_sources(&self.map, origin)).await?;
503        Ok(Self::new_decoded(map.0))
504    }
505}
506
507#[turbo_tasks::function]
508fn sourcemap_content_fs_root() -> Vc<FileSystemPath> {
509    VirtualFileSystem::new_with_name(rcstr!("sourcemap-content")).root()
510}
511
512#[turbo_tasks::function]
513async fn sourcemap_content_source(path: RcStr, content: RcStr) -> Result<Vc<Box<dyn Source>>> {
514    let path = sourcemap_content_fs_root().await?.join(&path)?;
515    let content = AssetContent::file(FileContent::new(File::from(content)).cell());
516    Ok(Vc::upcast(VirtualSource::new(path, content)))
517}
518
519impl SourceMap {
520    fn lookup_token_and_source_internal(
521        &self,
522        line: u32,
523        column: u32,
524        need_source_content: bool,
525    ) -> (Token, Option<Vc<Box<dyn Source>>>) {
526        let mut content: Option<Vc<Box<dyn Source>>> = None;
527
528        let token: Token = {
529            let map = &self.map;
530
531            let tok = map.lookup_token(line, column);
532            let mut token = tok.map(Token::from).unwrap_or_else(|| {
533                Token::Synthetic(SyntheticToken {
534                    generated_line: line,
535                    generated_column: column,
536                    guessed_original_file: None,
537                })
538            });
539
540            if let Token::Synthetic(SyntheticToken {
541                guessed_original_file,
542                ..
543            }) = &mut token
544                && let DecodedMap::Regular(map) = &map.0
545                && map.get_source_count() == 1
546            {
547                let source = map.sources().next().unwrap().clone();
548                *guessed_original_file = Some(RcStr::from(source));
549            }
550
551            // Check ignoreList and resolve source content using the regular
552            // (possibly flattened) source map. For Index maps,
553            // SourceMapIndex::flatten() correctly transfers ignoreList entries
554            // from each section into the flattened map.
555            if let Some(flat_map) = map.as_regular_source_map() {
556                if let Token::Original(ref mut orig) = token
557                    && let Some(source_name) = tok.and_then(|t| t.get_source().cloned())
558                    && let Some(idx) = flat_map.sources().position(|s| *s == *source_name)
559                {
560                    orig.is_ignored = flat_map.ignore_list().any(|id| *id == idx as u32);
561                }
562
563                if need_source_content && content.is_none() {
564                    content = tok.and_then(|tok| {
565                        let src_id = tok.get_src_id();
566
567                        let name = flat_map.get_source(src_id);
568                        let content = flat_map.get_source_contents(src_id);
569
570                        let (name, content) = name.zip(content)?;
571                        Some(sourcemap_content_source(
572                            name.clone().into(),
573                            content.clone().into(),
574                        ))
575                    });
576                }
577            }
578
579            token
580        };
581
582        (token, content)
583    }
584}
585
586impl SourceMap {
587    pub fn tokens(&self) -> impl Iterator<Item = Token> + '_ {
588        let map = &self.map;
589
590        fn regular_map_to_tokens(
591            map: &RegularMap,
592            offset_line: u32,
593            offset_column: u32,
594        ) -> impl Iterator<Item = Token> + '_ {
595            map.tokens()
596                .map(move |t| Token::from(t).with_offset(offset_line, offset_column))
597        }
598
599        fn index_map_to_tokens(
600            map: &SourceMapIndex,
601            offset_line: u32,
602            offset_column: u32,
603        ) -> impl Iterator<Item = Token> + '_ {
604            map.sections().flat_map(move |section| {
605                let (line, col) = section.get_offset();
606                let offset_line = offset_line + line;
607                let offset_column = if line == 0 { offset_column + col } else { col };
608                if let Some(source_map) = section.get_sourcemap() {
609                    Either::Left(Box::new(decoded_map_to_tokens(
610                        source_map,
611                        offset_line,
612                        offset_column,
613                    )) as Box<dyn Iterator<Item = Token>>)
614                } else {
615                    Either::Right(std::iter::empty())
616                }
617            })
618        }
619
620        fn decoded_map_to_tokens(
621            map: &DecodedMap,
622            offset_line: u32,
623            offset_column: u32,
624        ) -> impl Iterator<Item = Token> + '_ {
625            match map {
626                DecodedMap::Regular(map) => {
627                    Either::Left(regular_map_to_tokens(map, offset_line, offset_column))
628                }
629                DecodedMap::Index(map) => {
630                    Either::Right(index_map_to_tokens(map, offset_line, offset_column))
631                }
632                DecodedMap::Hermes(_) => {
633                    todo!("hermes source maps are not implemented");
634                }
635            }
636        }
637
638        decoded_map_to_tokens(&map.0, 0, 0)
639    }
640}
641
642#[turbo_tasks::value_impl]
643impl GenerateSourceMap for SourceMap {
644    #[turbo_tasks::function]
645    fn generate_source_map(&self) -> Result<Vc<FileContent>> {
646        Ok(FileContent::Content(File::from(self.to_rope()?)).cell())
647    }
648}
649
650/// Wraps the DecodedMap struct so that it is Sync and Send.
651///
652/// # Safety
653///
654/// Must not use per line access to the SourceMap, as it is not thread safe.
655#[derive(Debug, RefCast)]
656#[repr(transparent)]
657pub struct CrateMapWrapper(DecodedMap);
658
659// Safety: DecodedMap contains a raw pointer, which isn't Send, which is
660// required to cache in a Vc. So, we have wrap it in 4 layers of cruft to do it.
661unsafe impl Send for CrateMapWrapper {}
662unsafe impl Sync for CrateMapWrapper {}
663
664/// Wraps the RegularMap struct so that it is Sync and Send.
665///
666/// # Safety
667///
668/// Must not use per line access to the SourceMap, as it is not thread safe.
669#[derive(Debug, RefCast)]
670#[repr(transparent)]
671pub struct RegularMapWrapper(RegularMap);
672
673// Safety: RegularMap contains a raw pointer, which isn't Send, which is
674// required to cache in a Vc. So, we have wrap it in 4 layers of cruft to do it.
675unsafe impl Send for RegularMapWrapper {}
676unsafe impl Sync for RegularMapWrapper {}
677
678impl CrateMapWrapper {
679    pub fn as_regular_source_map(&self) -> Option<Cow<'_, RegularMap>> {
680        match &self.0 {
681            DecodedMap::Regular(m) => Some(Cow::Borrowed(m)),
682            DecodedMap::Index(m) => m.flatten().map(Cow::Owned).ok(),
683            _ => None,
684        }
685    }
686}
687
688impl Deref for CrateMapWrapper {
689    type Target = DecodedMap;
690
691    fn deref(&self) -> &Self::Target {
692        &self.0
693    }
694}
695
696impl Encode for CrateMapWrapper {
697    fn encode<E: Encoder>(&self, encoder: &mut E) -> Result<(), EncodeError> {
698        let mut bytes = Vec::new();
699        self.0
700            .to_writer(&mut bytes)
701            .map_err(|e| EncodeError::OtherString(e.to_string()))?;
702        bytes.encode(encoder)
703    }
704}
705
706impl<Context> Decode<Context> for CrateMapWrapper {
707    fn decode<D: Decoder<Context = Context>>(decoder: &mut D) -> Result<Self, DecodeError> {
708        let bytes = Vec::<u8>::decode(decoder)?;
709        let map = DecodedMap::from_reader(&*bytes)
710            .map_err(|e| DecodeError::OtherString(e.to_string()))?;
711        Ok(CrateMapWrapper(map))
712    }
713}
714
715bincode::impl_borrow_decode!(CrateMapWrapper);