turbopack_core/
code_builder.rs1use std::{
2 cmp::min,
3 io::{BufRead, Result as IoResult, Write},
4 ops,
5 sync::Arc,
6};
7
8use anyhow::Result;
9use bincode::{Decode, Encode};
10use tracing::instrument;
11use turbo_rcstr::RcStr;
12use turbo_tasks::{ResolvedVc, Vc};
13use turbo_tasks_fs::{
14 File, FileContent,
15 rope::{Rope, RopeBuilder},
16};
17use turbo_tasks_hash::hash_xxh3_hash64;
18
19use crate::{
20 debug_id::generate_debug_id,
21 output::OutputAsset,
22 source_map::{GenerateSourceMap, SourceMap, SourceMapAsset},
23 source_pos::SourcePos,
24};
25
26pub type Mapping = (usize, Option<Rope>);
28
29#[turbo_tasks::value(shared, serialization = "hash")]
31#[derive(Debug, Clone, Encode, Decode)]
32pub struct Code {
33 code: Rope,
34 mappings: Arc<Vec<Mapping>>,
35 should_generate_debug_id: bool,
36}
37
38#[turbo_tasks::value(transparent)]
39#[derive(Debug, Clone)]
40pub struct PersistedCode(Code);
41
42#[turbo_tasks::value_impl]
43impl PersistedCode {
44 #[turbo_tasks::function]
45 pub async fn to_code(self: Vc<Self>) -> Result<Vc<Code>> {
46 Ok(self.owned().await?.cell())
48 }
49}
50
51impl Code {
52 pub fn source_code(&self) -> &Rope {
53 &self.code
54 }
55
56 pub fn has_source_map(&self) -> bool {
58 !self.mappings.is_empty()
59 }
60 pub fn should_generate_debug_id(&self) -> bool {
62 self.should_generate_debug_id
63 }
64
65 pub fn into_source_code(self) -> Rope {
67 self.code
68 }
69
70 pub fn cell_persisted(self) -> ResolvedVc<PersistedCode> {
73 PersistedCode(self).resolved_cell()
74 }
75
76 pub async fn to_rope_with_magic_comments(
78 self: Vc<Self>,
79 source_map_path_fn: impl FnOnce() -> Vc<SourceMapAsset>,
80 ) -> Result<Rope> {
81 let code = self.await?;
82 Ok(
83 if code.has_source_map() || code.should_generate_debug_id() {
84 let mut rope_builder = RopeBuilder::default();
85 let debug_id = self.debug_id().await?;
86 if let Some(debug_id) = &*debug_id {
99 const GLOBALTHIS_EXPR: &str = r#""undefined"!=typeof globalThis?globalThis:"undefined"!=typeof global?global:"undefined"!=typeof window?window:"undefined"!=typeof self?self:{}"#;
102 const GLOBAL_VAR_NAME: &str = "_debugIds";
103 writeln!(
104 rope_builder,
105 r#";!function(){{try {{ var e={GLOBALTHIS_EXPR},n=(new e.Error).stack;n&&((e.{GLOBAL_VAR_NAME}|| (e.{GLOBAL_VAR_NAME}={{}}))[n]="{debug_id}")}}catch(e){{}}}}();"#,
106 )?;
107 }
108
109 rope_builder.concat(&code.code);
110 rope_builder.push_static_bytes(b"\n");
111 if let Some(debug_id) = &*debug_id {
113 write!(rope_builder, "\n//# debugId={}", debug_id)?;
114 }
115
116 if code.has_source_map() {
117 let source_map_path = source_map_path_fn().path().await?;
118 write!(
119 rope_builder,
120 "\n//# sourceMappingURL={}",
121 urlencoding::encode(source_map_path.file_name())
122 )?;
123 }
124 rope_builder.build()
125 } else {
126 code.code.clone()
127 },
128 )
129 }
130}
131
132pub struct CodeBuilder {
134 code: RopeBuilder,
135 mappings: Option<Vec<Mapping>>,
136 should_generate_debug_id: bool,
137}
138
139impl Default for CodeBuilder {
140 fn default() -> Self {
141 Self {
142 code: RopeBuilder::default(),
143 mappings: Some(Vec::new()),
144 should_generate_debug_id: false,
145 }
146 }
147}
148
149impl CodeBuilder {
150 pub fn new(collect_mappings: bool, should_generate_debug_id: bool) -> Self {
151 Self {
152 code: RopeBuilder::default(),
153 mappings: collect_mappings.then(Vec::new),
154 should_generate_debug_id,
155 }
156 }
157
158 fn push_static_bytes(&mut self, code: &'static [u8]) {
162 self.push_map(None);
163 self.code.push_static_bytes(code);
164 }
165
166 pub fn push_source(&mut self, code: &Rope, map: Option<Rope>) {
170 self.push_map(map);
171 self.code += code;
172 }
173
174 pub fn push_code(&mut self, prebuilt: &Code) {
179 if let Some((index, _)) = prebuilt.mappings.first() {
180 if *index > 0 {
181 self.push_map(None);
185 }
186
187 let len = self.code.len();
188 if let Some(mappings) = self.mappings.as_mut() {
189 mappings.extend(
190 prebuilt
191 .mappings
192 .iter()
193 .map(|(index, map)| (index + len, map.clone())),
194 );
195 }
196 } else {
197 self.push_map(None);
198 }
199
200 self.code += &prebuilt.code;
201 }
202
203 fn push_map(&mut self, map: Option<Rope>) {
209 let Some(mappings) = self.mappings.as_mut() else {
210 return;
211 };
212 if map.is_none() && matches!(mappings.last(), None | Some((_, None))) {
213 return;
215 }
216
217 debug_assert!(
218 map.is_some() || !mappings.is_empty(),
219 "the first mapping is never a None"
220 );
221 mappings.push((self.code.len(), map));
222 }
223
224 pub fn has_source_map(&self) -> bool {
226 self.mappings
227 .as_ref()
228 .is_some_and(|mappings| !mappings.is_empty())
229 }
230
231 pub fn build(self) -> Code {
232 Code {
233 code: self.code.build(),
234 mappings: Arc::new(self.mappings.unwrap_or_default()),
235 should_generate_debug_id: self.should_generate_debug_id,
236 }
237 }
238}
239
240impl ops::AddAssign<&'static str> for CodeBuilder {
241 fn add_assign(&mut self, rhs: &'static str) {
242 self.push_static_bytes(rhs.as_bytes());
243 }
244}
245
246impl ops::AddAssign<&'static str> for &mut CodeBuilder {
247 fn add_assign(&mut self, rhs: &'static str) {
248 self.push_static_bytes(rhs.as_bytes());
249 }
250}
251
252impl Write for CodeBuilder {
253 fn write(&mut self, bytes: &[u8]) -> IoResult<usize> {
254 self.push_map(None);
255 self.code.write(bytes)
256 }
257
258 fn flush(&mut self) -> IoResult<()> {
259 self.code.flush()
260 }
261}
262
263impl From<Code> for CodeBuilder {
264 fn from(code: Code) -> Self {
265 let mut builder = CodeBuilder::default();
266 builder.push_code(&code);
267 builder
268 }
269}
270
271#[turbo_tasks::value_impl]
272impl GenerateSourceMap for Code {
273 #[turbo_tasks::function]
282 pub async fn generate_source_map(self: ResolvedVc<Self>) -> Result<Vc<FileContent>> {
283 let debug_id = self.debug_id().owned().await?;
284 Ok(FileContent::Content(File::from(self.await?.generate_source_map_ref(debug_id))).cell())
285 }
286}
287
288#[turbo_tasks::value(transparent)]
289pub struct OptionDebugId(Option<RcStr>);
290
291#[turbo_tasks::value_impl]
292impl Code {
293 #[turbo_tasks::function]
295 pub fn source_code_hash(&self) -> Vc<u64> {
296 let code = self;
297 let hash = hash_xxh3_hash64(code.source_code());
298 Vc::cell(hash)
299 }
300
301 #[turbo_tasks::function]
302 pub fn debug_id(&self) -> Vc<OptionDebugId> {
303 Vc::cell(if self.should_generate_debug_id {
304 Some(generate_debug_id(self.source_code()))
305 } else {
306 None
307 })
308 }
309}
310
311impl Code {
312 #[instrument(level = "trace", name = "Code::generate_source_map", skip_all)]
314 pub fn generate_source_map_ref(&self, debug_id: Option<RcStr>) -> Rope {
315 debug_assert!(debug_id.is_none() || self.should_generate_debug_id);
318 let mut pos = SourcePos::new(if debug_id.is_some() { 1 } else { 0 });
321
322 let mut last_byte_pos = 0;
323
324 let mut sections = Vec::with_capacity(self.mappings.len());
325 let mut read = self.code.read();
326 for (byte_pos, map) in self.mappings.iter() {
327 let mut want = byte_pos - last_byte_pos;
328 while want > 0 {
329 let buf = read.fill_buf().unwrap();
331 debug_assert!(!buf.is_empty());
332
333 let end = min(want, buf.len());
334 pos.update(&buf[0..end]);
335
336 read.consume(end);
337 want -= end;
338 }
339 last_byte_pos = *byte_pos;
340
341 if let Some(map) = map {
342 sections.push((pos, map.clone()))
343 } else {
344 if pos.column != 0
346 && read
347 .fill_buf()
348 .unwrap()
349 .first()
350 .is_some_and(|&b| b != b'\n')
351 {
352 sections.push((pos, SourceMap::empty_rope()));
353 }
354 }
355 }
356
357 if sections.len() == 1
358 && sections[0].0.line == 0
359 && sections[0].0.column == 0
360 && debug_id.is_none()
361 {
362 sections.into_iter().next().unwrap().1
363 } else {
364 SourceMap::sections_to_rope(sections, debug_id)
365 }
366 }
367}