1use std::fmt::Write;
2
3use anyhow::Result;
4use once_cell::sync::Lazy;
5use regex::Regex;
6use serde::{Deserialize, Serialize};
7use turbo_rcstr::RcStr;
8use turbo_tasks::{
9 NonLocalValue, ReadRef, ResolvedVc, TaskInput, ValueToString, Vc, trace::TraceRawVcs,
10};
11use turbo_tasks_fs::FileSystemPath;
12use turbo_tasks_hash::{DeterministicHash, Xxh3Hash64Hasher, encode_hex, hash_xxh3_hash64};
13
14use crate::resolve::ModulePart;
15
16#[derive(
18 Clone,
19 TaskInput,
20 Hash,
21 Debug,
22 DeterministicHash,
23 Eq,
24 PartialEq,
25 TraceRawVcs,
26 Serialize,
27 Deserialize,
28 NonLocalValue,
29)]
30pub struct Layer {
31 name: RcStr,
32 user_friendly_name: Option<RcStr>,
33}
34
35impl Layer {
36 pub fn new(name: RcStr) -> Self {
37 debug_assert!(!name.is_empty());
38 Self {
39 name,
40 user_friendly_name: None,
41 }
42 }
43 pub fn new_with_user_friendly_name(name: RcStr, user_friendly_name: RcStr) -> Self {
44 debug_assert!(!name.is_empty());
45 debug_assert!(!user_friendly_name.is_empty());
46 Self {
47 name,
48 user_friendly_name: Some(user_friendly_name),
49 }
50 }
51
52 pub fn user_friendly_name(&self) -> &RcStr {
54 self.user_friendly_name.as_ref().unwrap_or(&self.name)
55 }
56
57 pub fn name(&self) -> &RcStr {
58 &self.name
59 }
60}
61
62#[turbo_tasks::value]
63#[derive(Clone, Debug, Hash, TaskInput)]
64pub struct AssetIdent {
65 pub path: FileSystemPath,
67 pub query: RcStr,
70 pub fragment: RcStr,
73 pub assets: Vec<(RcStr, ResolvedVc<AssetIdent>)>,
75 pub modifiers: Vec<RcStr>,
77 pub parts: Vec<ModulePart>,
79 pub layer: Option<Layer>,
81 pub content_type: Option<RcStr>,
83}
84
85impl AssetIdent {
86 pub fn new(ident: AssetIdent) -> Vc<Self> {
87 AssetIdent::new_inner(ReadRef::new_owned(ident))
88 }
89
90 pub fn add_modifier(&mut self, modifier: RcStr) {
91 debug_assert!(!modifier.is_empty(), "modifiers cannot be empty.");
92 self.modifiers.push(modifier);
93 }
94
95 pub fn add_asset(&mut self, key: RcStr, asset: ResolvedVc<AssetIdent>) {
96 self.assets.push((key, asset));
97 }
98
99 pub async fn rename_as_ref(&mut self, pattern: &str) -> Result<()> {
100 let root = self.path.root().await?;
101 let path = self.path.clone();
102 self.path = root.join(&pattern.replace('*', &path.path))?;
103 Ok(())
104 }
105}
106
107#[turbo_tasks::value_impl]
108impl AssetIdent {
109 #[turbo_tasks::function]
110 fn new_inner(ident: ReadRef<AssetIdent>) -> Vc<Self> {
111 debug_assert!(
112 ident.query.is_empty() || ident.query.starts_with("?"),
113 "query should be empty or start with a `?`"
114 );
115 debug_assert!(
116 ident.fragment.is_empty() || ident.fragment.starts_with("#"),
117 "query should be empty or start with a `?`"
118 );
119 ReadRef::cell(ident)
120 }
121
122 #[turbo_tasks::function]
124 pub fn from_path(path: FileSystemPath) -> Vc<Self> {
125 Self::new(AssetIdent {
126 path,
127 query: RcStr::default(),
128 fragment: RcStr::default(),
129 assets: Vec::new(),
130 modifiers: Vec::new(),
131 parts: Vec::new(),
132 layer: None,
133 content_type: None,
134 })
135 }
136
137 #[turbo_tasks::function]
138 pub fn with_query(&self, query: RcStr) -> Vc<Self> {
139 let mut this = self.clone();
140 this.query = query;
141 Self::new(this)
142 }
143
144 #[turbo_tasks::function]
145 pub fn with_fragment(&self, fragment: RcStr) -> Vc<Self> {
146 let mut this = self.clone();
147 this.fragment = fragment;
148 Self::new(this)
149 }
150
151 #[turbo_tasks::function]
152 pub fn with_modifier(&self, modifier: RcStr) -> Vc<Self> {
153 let mut this = self.clone();
154 this.add_modifier(modifier);
155 Self::new(this)
156 }
157
158 #[turbo_tasks::function]
159 pub fn with_part(&self, part: ModulePart) -> Vc<Self> {
160 let mut this = self.clone();
161 this.parts.push(part);
162 Self::new(this)
163 }
164
165 #[turbo_tasks::function]
166 pub fn with_path(&self, path: FileSystemPath) -> Vc<Self> {
167 let mut this = self.clone();
168 this.path = path;
169 Self::new(this)
170 }
171
172 #[turbo_tasks::function]
173 pub fn with_layer(&self, layer: Layer) -> Vc<Self> {
174 let mut this = self.clone();
175 this.layer = Some(layer);
176 Self::new(this)
177 }
178
179 #[turbo_tasks::function]
180 pub fn with_content_type(&self, content_type: RcStr) -> Vc<Self> {
181 let mut this = self.clone();
182 this.content_type = Some(content_type);
183 Self::new(this)
184 }
185
186 #[turbo_tasks::function]
187 pub fn with_asset(&self, key: RcStr, asset: ResolvedVc<AssetIdent>) -> Vc<Self> {
188 let mut this = self.clone();
189 this.add_asset(key, asset);
190 Self::new(this)
191 }
192
193 #[turbo_tasks::function]
194 pub async fn rename_as(&self, pattern: RcStr) -> Result<Vc<Self>> {
195 let mut this = self.clone();
196 this.rename_as_ref(&pattern).await?;
197 Ok(Self::new(this))
198 }
199
200 #[turbo_tasks::function]
201 pub fn path(&self) -> Vc<FileSystemPath> {
202 self.path.clone().cell()
203 }
204
205 #[turbo_tasks::function]
210 pub async fn output_name(
211 &self,
212 context_path: FileSystemPath,
213 prefix: Option<RcStr>,
214 expected_extension: RcStr,
215 ) -> Result<Vc<RcStr>> {
216 debug_assert!(
217 expected_extension.starts_with("."),
218 "the extension should include the leading '.', got '{expected_extension}'"
219 );
220 let path = &self.path;
225 let mut name = if let Some(inner) = context_path.get_path_to(path) {
226 clean_separators(inner)
227 } else {
228 clean_separators(&self.path.value_to_string().await?)
229 };
230 let removed_extension = name.ends_with(&*expected_extension);
231 if removed_extension {
232 name.truncate(name.len() - expected_extension.len());
233 }
234 let mut name = clean_additional_extensions(&name);
238 if let Some(prefix) = prefix {
239 name = format!("{prefix}-{name}");
240 }
241
242 let default_modifier = match expected_extension.as_str() {
243 ".js" => Some("ecmascript"),
244 ".css" => Some("css"),
245 _ => None,
246 };
247
248 let mut hasher = Xxh3Hash64Hasher::new();
249 let mut has_hash = false;
250 let AssetIdent {
251 path: _,
252 query,
253 fragment,
254 assets,
255 modifiers,
256 parts,
257 layer,
258 content_type,
259 } = self;
260 if !query.is_empty() {
261 0_u8.deterministic_hash(&mut hasher);
262 query.deterministic_hash(&mut hasher);
263 has_hash = true;
264 }
265 if !fragment.is_empty() {
266 1_u8.deterministic_hash(&mut hasher);
267 fragment.deterministic_hash(&mut hasher);
268 has_hash = true;
269 }
270 for (key, ident) in assets.iter() {
271 2_u8.deterministic_hash(&mut hasher);
272 key.deterministic_hash(&mut hasher);
273 ident.to_string().await?.deterministic_hash(&mut hasher);
274 has_hash = true;
275 }
276 for modifier in modifiers.iter() {
277 if let Some(default_modifier) = default_modifier
278 && *modifier == default_modifier
279 {
280 continue;
281 }
282 3_u8.deterministic_hash(&mut hasher);
283 modifier.deterministic_hash(&mut hasher);
284 has_hash = true;
285 }
286 for part in parts.iter() {
287 4_u8.deterministic_hash(&mut hasher);
288 match part {
289 ModulePart::Evaluation => {
290 1_u8.deterministic_hash(&mut hasher);
291 }
292 ModulePart::Export(export) => {
293 2_u8.deterministic_hash(&mut hasher);
294 export.deterministic_hash(&mut hasher);
295 }
296 ModulePart::RenamedExport {
297 original_export,
298 export,
299 } => {
300 3_u8.deterministic_hash(&mut hasher);
301 original_export.deterministic_hash(&mut hasher);
302 export.deterministic_hash(&mut hasher);
303 }
304 ModulePart::RenamedNamespace { export } => {
305 4_u8.deterministic_hash(&mut hasher);
306 export.deterministic_hash(&mut hasher);
307 }
308 ModulePart::Internal(id) => {
309 5_u8.deterministic_hash(&mut hasher);
310 id.deterministic_hash(&mut hasher);
311 }
312 ModulePart::Locals => {
313 6_u8.deterministic_hash(&mut hasher);
314 }
315 ModulePart::Exports => {
316 7_u8.deterministic_hash(&mut hasher);
317 }
318 ModulePart::Facade => {
319 8_u8.deterministic_hash(&mut hasher);
320 }
321 }
322
323 has_hash = true;
324 }
325 if let Some(layer) = layer {
326 5_u8.deterministic_hash(&mut hasher);
327 layer.deterministic_hash(&mut hasher);
328 has_hash = true;
329 }
330 if let Some(content_type) = content_type {
331 6_u8.deterministic_hash(&mut hasher);
332 content_type.deterministic_hash(&mut hasher);
333 has_hash = true;
334 }
335
336 if has_hash {
337 let hash = encode_hex(hasher.finish());
338 let truncated_hash = &hash[..8];
339 write!(name, "_{truncated_hash}")?;
340 }
341
342 let mut i = 0;
345 static NODE_MODULES: &str = "_node_modules_";
346 if let Some(j) = name.rfind(NODE_MODULES) {
347 i = j + NODE_MODULES.len();
348 }
349 const MAX_FILENAME: usize = 80;
350 if name.len() - i > MAX_FILENAME {
351 i = name.len() - MAX_FILENAME;
352 if let Some(j) = name[i..].find('_')
353 && j < 20
354 {
355 i += j + 1;
356 }
357 }
358 if i > 0 {
359 let hash = encode_hex(hash_xxh3_hash64(&name.as_bytes()[..i]));
360 let truncated_hash = &hash[..5];
361 name = format!("{}_{}", truncated_hash, &name[i..]);
362 }
363 if !removed_extension {
367 name += "._";
368 }
369 name += &expected_extension;
370 Ok(Vc::cell(name.into()))
371 }
372}
373
374#[turbo_tasks::value_impl]
375impl ValueToString for AssetIdent {
376 #[turbo_tasks::function]
377 async fn to_string(&self) -> Result<Vc<RcStr>> {
378 let mut s = self.path.value_to_string().owned().await?.into_owned();
379
380 s.push_str(&self.query);
382 s.push_str(&self.fragment);
384
385 if !self.assets.is_empty() {
386 s.push_str(" {");
387
388 for (i, (key, asset)) in self.assets.iter().enumerate() {
389 if i > 0 {
390 s.push(',');
391 }
392
393 let asset_str = asset.to_string().await?;
394 write!(s, " {key} => {asset_str:?}")?;
395 }
396
397 s.push_str(" }");
398 }
399
400 if let Some(layer) = &self.layer {
401 write!(s, " [{}]", layer.name)?;
402 }
403
404 if !self.modifiers.is_empty() {
405 s.push_str(" (");
406
407 for (i, modifier) in self.modifiers.iter().enumerate() {
408 if i > 0 {
409 s.push_str(", ");
410 }
411
412 s.push_str(modifier);
413 }
414
415 s.push(')');
416 }
417
418 if let Some(content_type) = &self.content_type {
419 write!(s, " <{content_type}>")?;
420 }
421
422 if !self.parts.is_empty() {
423 for part in self.parts.iter() {
424 if !matches!(part, ModulePart::Facade) {
425 write!(s, " <{part}>")?;
428 }
429 }
430 }
431
432 Ok(Vc::cell(s.into()))
433 }
434}
435
436fn clean_separators(s: &str) -> String {
437 static SEPARATOR_REGEX: Lazy<Regex> = Lazy::new(|| Regex::new(r"[/#?]").unwrap());
438 SEPARATOR_REGEX.replace_all(s, "_").to_string()
439}
440
441fn clean_additional_extensions(s: &str) -> String {
442 s.replace('.', "_")
443}