Skip to main content

turbopack_ecmascript/
single_file_ecmascript_output.rs

1use std::sync::Arc;
2
3use anyhow::Result;
4use swc_core::common::{BytePos, FileName, LineCol, SourceMap};
5use tokio::io::AsyncReadExt;
6use turbo_rcstr::rcstr;
7use turbo_tasks::{ResolvedVc, ValueToStringRef, Vc};
8use turbo_tasks_fs::{File, FileContent, FileSystemPath, rope::Rope};
9use turbopack_core::{
10    asset::{Asset, AssetContent},
11    chunk::ChunkingContext,
12    output::{OutputAsset, OutputAssets, OutputAssetsReference, OutputAssetsWithReferenced},
13    source::Source,
14    source_map::{GenerateSourceMap, SourceMapAsset},
15};
16
17use crate::parse::generate_js_source_map;
18
19/// An EcmaScript OutputAsset composed of one file, no parsing and no references. Includes a source
20/// map to the original file.
21#[turbo_tasks::value]
22pub struct SingleFileEcmascriptOutput {
23    chunking_context: ResolvedVc<Box<dyn ChunkingContext>>,
24    source: ResolvedVc<Box<dyn Source>>,
25}
26
27#[turbo_tasks::value_impl]
28impl SingleFileEcmascriptOutput {
29    #[turbo_tasks::function]
30    async fn source_map(self: Vc<Self>) -> Result<Vc<SourceMapAsset>> {
31        let this = self.await?;
32        Ok(SourceMapAsset::new(
33            *this.chunking_context,
34            this.source.ident(),
35            Vc::upcast(self),
36        ))
37    }
38}
39
40#[turbo_tasks::value_impl]
41impl OutputAsset for SingleFileEcmascriptOutput {
42    #[turbo_tasks::function]
43    fn path(&self) -> Vc<FileSystemPath> {
44        self.chunking_context.chunk_path(
45            Some(Vc::upcast(*self.source)),
46            self.source.ident(),
47            None,
48            rcstr!(".js"),
49        )
50    }
51}
52
53#[turbo_tasks::value_impl]
54impl Asset for SingleFileEcmascriptOutput {
55    #[turbo_tasks::function]
56    fn content(&self) -> Vc<AssetContent> {
57        self.source.content()
58    }
59}
60
61#[turbo_tasks::value_impl]
62impl SingleFileEcmascriptOutput {
63    #[turbo_tasks::function]
64    pub fn new(
65        chunking_context: ResolvedVc<Box<dyn ChunkingContext>>,
66        source: ResolvedVc<Box<dyn Source>>,
67    ) -> Vc<SingleFileEcmascriptOutput> {
68        SingleFileEcmascriptOutput {
69            source,
70            chunking_context,
71        }
72        .cell()
73    }
74}
75
76#[turbo_tasks::value_impl]
77impl GenerateSourceMap for SingleFileEcmascriptOutput {
78    #[turbo_tasks::function]
79    pub async fn generate_source_map(&self) -> Result<Vc<FileContent>> {
80        let FileContent::Content(file) = &*self.source.content().file_content().await? else {
81            return Ok(FileContent::NotFound.cell());
82        };
83
84        let file_source = {
85            let mut s = String::new();
86            file.read().read_to_string(&mut s).await?;
87            s
88        };
89
90        let mut mappings = vec![];
91        // Start from 1 because 0 is reserved for dummy spans in SWC.
92        let mut pos: u32 = 1;
93        for (index, line) in file_source.split_inclusive('\n').enumerate() {
94            mappings.push((
95                BytePos(pos),
96                LineCol {
97                    line: index as u32,
98                    col: 0,
99                },
100            ));
101            pos += line.len() as u32;
102        }
103
104        let source_path = self
105            .source
106            .ident()
107            .await?
108            .path
109            .to_string_ref()
110            .await?
111            .to_string();
112
113        let sm: Arc<SourceMap> = Default::default();
114        sm.new_source_file(FileName::Custom(source_path).into(), file_source);
115
116        let map = generate_js_source_map(
117            &*sm,
118            mappings,
119            None::<&Rope>,
120            true,
121            true,
122            Default::default(),
123        )?;
124        Ok(FileContent::Content(File::from(map)).cell())
125    }
126}
127
128#[turbo_tasks::value_impl]
129impl OutputAssetsReference for SingleFileEcmascriptOutput {
130    #[turbo_tasks::function]
131    async fn references(self: Vc<Self>) -> Result<Vc<OutputAssetsWithReferenced>> {
132        let this = self.await?;
133
134        let include_source_map = *this
135            .chunking_context
136            .reference_chunk_source_maps(Vc::upcast(self))
137            .await?;
138
139        let references = if include_source_map {
140            Vc::cell(vec![ResolvedVc::upcast(
141                self.source_map().to_resolved().await?,
142            )])
143        } else {
144            OutputAssets::empty()
145        };
146
147        Ok(OutputAssetsWithReferenced::from_assets(references))
148    }
149}