turbopack_core/
code_builder.rs1use std::{
2 cmp::min,
3 io::{BufRead, Result as IoResult, Write},
4 ops,
5};
6
7use anyhow::Result;
8use tracing::instrument;
9use turbo_rcstr::RcStr;
10use turbo_tasks::{ResolvedVc, Vc};
11use turbo_tasks_fs::{
12 File, FileContent,
13 rope::{Rope, RopeBuilder},
14};
15use turbo_tasks_hash::hash_xxh3_hash64;
16
17use crate::{
18 debug_id::generate_debug_id,
19 output::OutputAsset,
20 source_map::{GenerateSourceMap, SourceMap, SourceMapAsset},
21 source_pos::SourcePos,
22};
23
24pub type Mapping = (usize, Option<Rope>);
26
27#[turbo_tasks::value(shared)]
29#[derive(Debug, Clone)]
30pub struct Code {
31 code: Rope,
32 mappings: Vec<Mapping>,
33 should_generate_debug_id: bool,
34}
35
36impl Code {
37 pub fn source_code(&self) -> &Rope {
38 &self.code
39 }
40
41 pub fn has_source_map(&self) -> bool {
43 !self.mappings.is_empty()
44 }
45 pub fn should_generate_debug_id(&self) -> bool {
47 self.should_generate_debug_id
48 }
49
50 pub fn into_source_code(self) -> Rope {
52 self.code
53 }
54
55 pub async fn to_rope_with_magic_comments(
57 self: Vc<Self>,
58 source_map_path_fn: impl FnOnce() -> Vc<SourceMapAsset>,
59 ) -> Result<Rope> {
60 let code = self.await?;
61 Ok(
62 if code.has_source_map() || code.should_generate_debug_id() {
63 let mut rope_builder = RopeBuilder::default();
64 let debug_id = self.debug_id().await?;
65 if let Some(debug_id) = &*debug_id {
78 const GLOBALTHIS_EXPR: &str = r#""undefined"!=typeof globalThis?globalThis:"undefined"!=typeof global?global:"undefined"!=typeof window?window:"undefined"!=typeof self?self:{}"#;
81 const GLOBAL_VAR_NAME: &str = "_debugIds";
82 writeln!(
83 rope_builder,
84 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){{}}}}();"#,
85 )?;
86 }
87
88 rope_builder.concat(&code.code);
89 rope_builder.push_static_bytes(b"\n");
90 if let Some(debug_id) = &*debug_id {
92 write!(rope_builder, "\n//# debugId={}", debug_id)?;
93 }
94
95 if code.has_source_map() {
96 let source_map_path = source_map_path_fn().path().await?;
97 write!(
98 rope_builder,
99 "\n//# sourceMappingURL={}",
100 urlencoding::encode(source_map_path.file_name())
101 )?;
102 }
103 rope_builder.build()
104 } else {
105 code.code.clone()
106 },
107 )
108 }
109}
110
111pub struct CodeBuilder {
113 code: RopeBuilder,
114 mappings: Option<Vec<Mapping>>,
115 should_generate_debug_id: bool,
116}
117
118impl Default for CodeBuilder {
119 fn default() -> Self {
120 Self {
121 code: RopeBuilder::default(),
122 mappings: Some(Vec::new()),
123 should_generate_debug_id: false,
124 }
125 }
126}
127
128impl CodeBuilder {
129 pub fn new(collect_mappings: bool, should_generate_debug_id: bool) -> Self {
130 Self {
131 code: RopeBuilder::default(),
132 mappings: collect_mappings.then(Vec::new),
133 should_generate_debug_id,
134 }
135 }
136
137 fn push_static_bytes(&mut self, code: &'static [u8]) {
141 self.push_map(None);
142 self.code.push_static_bytes(code);
143 }
144
145 pub fn push_source(&mut self, code: &Rope, map: Option<Rope>) {
149 self.push_map(map);
150 self.code += code;
151 }
152
153 pub fn push_code(&mut self, prebuilt: &Code) {
158 if let Some((index, _)) = prebuilt.mappings.first() {
159 if *index > 0 {
160 self.push_map(None);
164 }
165
166 let len = self.code.len();
167 if let Some(mappings) = self.mappings.as_mut() {
168 mappings.extend(
169 prebuilt
170 .mappings
171 .iter()
172 .map(|(index, map)| (index + len, map.clone())),
173 );
174 }
175 } else {
176 self.push_map(None);
177 }
178
179 self.code += &prebuilt.code;
180 }
181
182 fn push_map(&mut self, map: Option<Rope>) {
188 let Some(mappings) = self.mappings.as_mut() else {
189 return;
190 };
191 if map.is_none() && matches!(mappings.last(), None | Some((_, None))) {
192 return;
194 }
195
196 debug_assert!(
197 map.is_some() || !mappings.is_empty(),
198 "the first mapping is never a None"
199 );
200 mappings.push((self.code.len(), map));
201 }
202
203 pub fn has_source_map(&self) -> bool {
205 self.mappings
206 .as_ref()
207 .is_some_and(|mappings| !mappings.is_empty())
208 }
209
210 pub fn build(self) -> Code {
211 Code {
212 code: self.code.build(),
213 mappings: self.mappings.unwrap_or_default(),
214 should_generate_debug_id: self.should_generate_debug_id,
215 }
216 }
217}
218
219impl ops::AddAssign<&'static str> for CodeBuilder {
220 fn add_assign(&mut self, rhs: &'static str) {
221 self.push_static_bytes(rhs.as_bytes());
222 }
223}
224
225impl ops::AddAssign<&'static str> for &mut CodeBuilder {
226 fn add_assign(&mut self, rhs: &'static str) {
227 self.push_static_bytes(rhs.as_bytes());
228 }
229}
230
231impl Write for CodeBuilder {
232 fn write(&mut self, bytes: &[u8]) -> IoResult<usize> {
233 self.push_map(None);
234 self.code.write(bytes)
235 }
236
237 fn flush(&mut self) -> IoResult<()> {
238 self.code.flush()
239 }
240}
241
242impl From<Code> for CodeBuilder {
243 fn from(code: Code) -> Self {
244 let mut builder = CodeBuilder::default();
245 builder.push_code(&code);
246 builder
247 }
248}
249
250#[turbo_tasks::value_impl]
251impl GenerateSourceMap for Code {
252 #[turbo_tasks::function]
261 pub async fn generate_source_map(self: ResolvedVc<Self>) -> Result<Vc<FileContent>> {
262 let debug_id = self.debug_id().owned().await?;
263 Ok(FileContent::Content(File::from(self.await?.generate_source_map_ref(debug_id))).cell())
264 }
265}
266
267#[turbo_tasks::value(transparent)]
268pub struct OptionDebugId(Option<RcStr>);
269
270#[turbo_tasks::value_impl]
271impl Code {
272 #[turbo_tasks::function]
274 pub fn source_code_hash(&self) -> Vc<u64> {
275 let code = self;
276 let hash = hash_xxh3_hash64(code.source_code());
277 Vc::cell(hash)
278 }
279
280 #[turbo_tasks::function]
281 pub fn debug_id(&self) -> Vc<OptionDebugId> {
282 Vc::cell(if self.should_generate_debug_id {
283 Some(generate_debug_id(self.source_code()))
284 } else {
285 None
286 })
287 }
288}
289
290impl Code {
291 #[instrument(level = "trace", name = "Code::generate_source_map", skip_all)]
293 pub fn generate_source_map_ref(&self, debug_id: Option<RcStr>) -> Rope {
294 debug_assert!(debug_id.is_none() || self.should_generate_debug_id);
297 let mut pos = SourcePos::new(if debug_id.is_some() { 1 } else { 0 });
300
301 let mut last_byte_pos = 0;
302
303 let mut sections = Vec::with_capacity(self.mappings.len());
304 let mut read = self.code.read();
305 for (byte_pos, map) in &self.mappings {
306 let mut want = byte_pos - last_byte_pos;
307 while want > 0 {
308 let buf = read.fill_buf().unwrap();
310 debug_assert!(!buf.is_empty());
311
312 let end = min(want, buf.len());
313 pos.update(&buf[0..end]);
314
315 read.consume(end);
316 want -= end;
317 }
318 last_byte_pos = *byte_pos;
319
320 if let Some(map) = map {
321 sections.push((pos, map.clone()))
322 } else {
323 if pos.column != 0
325 && read
326 .fill_buf()
327 .unwrap()
328 .first()
329 .is_some_and(|&b| b != b'\n')
330 {
331 sections.push((pos, SourceMap::empty_rope()));
332 }
333 }
334 }
335
336 if sections.len() == 1
337 && sections[0].0.line == 0
338 && sections[0].0.column == 0
339 && debug_id.is_none()
340 {
341 sections.into_iter().next().unwrap().1
342 } else {
343 SourceMap::sections_to_rope(sections, debug_id)
344 }
345 }
346}