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_tasks::{ResolvedVc, Vc};
7use turbo_tasks_fs::{File, FileContent, FileSystemPath, rope::Rope};
8use turbopack_core::{
9    asset::{Asset, AssetContent},
10    output::{OutputAsset, OutputAssetsReference},
11    source::Source,
12    source_map::GenerateSourceMap,
13};
14
15use crate::parse::generate_js_source_map;
16
17/// An EcmaScript OutputAsset composed of one file, no parsing and no references. Includes a source
18/// map to the original file.
19#[turbo_tasks::value]
20pub struct SingleFileEcmascriptOutput {
21    output_path: FileSystemPath,
22    source_path: FileSystemPath,
23    source: ResolvedVc<Box<dyn Source>>,
24}
25
26#[turbo_tasks::value_impl]
27impl OutputAsset for SingleFileEcmascriptOutput {
28    #[turbo_tasks::function]
29    fn path(&self) -> Vc<FileSystemPath> {
30        self.output_path.clone().cell()
31    }
32}
33
34#[turbo_tasks::value_impl]
35impl OutputAssetsReference for SingleFileEcmascriptOutput {}
36
37#[turbo_tasks::value_impl]
38impl Asset for SingleFileEcmascriptOutput {
39    #[turbo_tasks::function]
40    fn content(&self) -> Vc<AssetContent> {
41        self.source.content()
42    }
43}
44
45#[turbo_tasks::value_impl]
46impl SingleFileEcmascriptOutput {
47    #[turbo_tasks::function]
48    pub fn new(
49        output_path: FileSystemPath,
50        source_path: FileSystemPath,
51        source: ResolvedVc<Box<dyn Source>>,
52    ) -> Vc<SingleFileEcmascriptOutput> {
53        SingleFileEcmascriptOutput {
54            output_path,
55            source_path,
56            source,
57        }
58        .cell()
59    }
60}
61
62#[turbo_tasks::value_impl]
63impl GenerateSourceMap for SingleFileEcmascriptOutput {
64    #[turbo_tasks::function]
65    pub async fn generate_source_map(&self) -> Result<Vc<FileContent>> {
66        let FileContent::Content(file) = &*self.source.content().file_content().await? else {
67            return Ok(FileContent::NotFound.cell());
68        };
69
70        let file_source = {
71            let mut s = String::new();
72            file.read().read_to_string(&mut s).await?;
73            s
74        };
75
76        let mut mappings = vec![];
77        // Start from 1 because 0 is reserved for dummy spans in SWC.
78        let mut pos: u32 = 1;
79        for (index, line) in file_source.split_inclusive('\n').enumerate() {
80            mappings.push((
81                BytePos(pos),
82                LineCol {
83                    line: index as u32,
84                    col: 0,
85                },
86            ));
87            pos += line.len() as u32;
88        }
89
90        let sm: Arc<SourceMap> = Default::default();
91        sm.new_source_file(
92            FileName::Custom(self.source_path.to_string()).into(),
93            file_source,
94        );
95
96        let map = generate_js_source_map(&*sm, mappings, None::<&Rope>, true, true)?;
97        Ok(FileContent::Content(File::from(map)).cell())
98    }
99}