turbopack_ecmascript/
single_file_ecmascript_output.rs1use 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#[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 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}