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