1use std::fmt::Write;
2
3use anyhow::Result;
4use once_cell::sync::Lazy;
5use regex::Regex;
6use turbo_rcstr::RcStr;
7use turbo_tasks::{ResolvedVc, Value, ValueToString, Vc};
8use turbo_tasks_fs::FileSystemPath;
9use turbo_tasks_hash::{DeterministicHash, Xxh3Hash64Hasher, encode_hex, hash_xxh3_hash64};
10
11use crate::resolve::ModulePart;
12
13#[turbo_tasks::value(serialization = "auto_for_input")]
14#[derive(Clone, Debug, Hash)]
15pub struct AssetIdent {
16 pub path: ResolvedVc<FileSystemPath>,
18 pub query: ResolvedVc<RcStr>,
20 pub fragment: Option<ResolvedVc<RcStr>>,
22 pub assets: Vec<(ResolvedVc<RcStr>, ResolvedVc<AssetIdent>)>,
24 pub modifiers: Vec<ResolvedVc<RcStr>>,
26 pub parts: Vec<ModulePart>,
28 pub layer: Option<ResolvedVc<RcStr>>,
30 pub content_type: Option<RcStr>,
32}
33
34impl AssetIdent {
35 pub fn add_modifier(&mut self, modifier: ResolvedVc<RcStr>) {
36 self.modifiers.push(modifier);
37 }
38
39 pub fn add_asset(&mut self, key: ResolvedVc<RcStr>, asset: ResolvedVc<AssetIdent>) {
40 self.assets.push((key, asset));
41 }
42
43 pub async fn rename_as_ref(&mut self, pattern: &str) -> Result<()> {
44 let root = self.path.root();
45 let path = self.path.await?;
46 self.path = root
47 .join(pattern.replace('*', &path.path).into())
48 .to_resolved()
49 .await?;
50 Ok(())
51 }
52}
53
54#[turbo_tasks::value_impl]
55impl ValueToString for AssetIdent {
56 #[turbo_tasks::function]
57 async fn to_string(&self) -> Result<Vc<RcStr>> {
58 let mut s = self.path.to_string().owned().await?.into_owned();
59
60 let query = self.query.await?;
61 if !query.is_empty() {
62 write!(s, "?{}", &*query)?;
63 }
64
65 if let Some(fragment) = &self.fragment {
66 write!(s, "#{}", fragment.await?)?;
67 }
68
69 if !self.assets.is_empty() {
70 s.push_str(" {");
71
72 for (i, (key, asset)) in self.assets.iter().enumerate() {
73 if i > 0 {
74 s.push(',');
75 }
76
77 let key_str = key.await?;
78 let asset_str = asset.to_string().await?;
79 write!(s, " {key_str} => {asset_str:?}")?;
80 }
81
82 s.push_str(" }");
83 }
84
85 if let Some(layer) = &self.layer {
86 write!(s, " [{}]", layer.await?)?;
87 }
88
89 if !self.modifiers.is_empty() {
90 s.push_str(" (");
91
92 for (i, modifier) in self.modifiers.iter().enumerate() {
93 if i > 0 {
94 s.push_str(", ");
95 }
96
97 s.push_str(&modifier.await?);
98 }
99
100 s.push(')');
101 }
102
103 if let Some(content_type) = &self.content_type {
104 write!(s, " <{content_type}>")?;
105 }
106
107 if !self.parts.is_empty() {
108 for part in self.parts.iter() {
109 if !matches!(part, ModulePart::Facade) {
110 write!(s, " <{part}>")?;
113 }
114 }
115 }
116
117 Ok(Vc::cell(s.into()))
118 }
119}
120
121#[turbo_tasks::value_impl]
122impl AssetIdent {
123 #[turbo_tasks::function]
124 pub fn new(ident: Value<AssetIdent>) -> Vc<Self> {
125 ident.into_value().cell()
126 }
127
128 #[turbo_tasks::function]
130 pub fn from_path(path: ResolvedVc<FileSystemPath>) -> Vc<Self> {
131 Self::new(Value::new(AssetIdent {
132 path,
133 query: ResolvedVc::cell(RcStr::default()),
134 fragment: None,
135 assets: Vec::new(),
136 modifiers: Vec::new(),
137 parts: Vec::new(),
138 layer: None,
139 content_type: None,
140 }))
141 }
142
143 #[turbo_tasks::function]
144 pub fn with_query(&self, query: ResolvedVc<RcStr>) -> Vc<Self> {
145 let mut this = self.clone();
146 this.query = query;
147 Self::new(Value::new(this))
148 }
149
150 #[turbo_tasks::function]
151 pub fn with_modifier(&self, modifier: ResolvedVc<RcStr>) -> Vc<Self> {
152 let mut this = self.clone();
153 this.add_modifier(modifier);
154 Self::new(Value::new(this))
155 }
156
157 #[turbo_tasks::function]
158 pub fn with_part(&self, part: ModulePart) -> Vc<Self> {
159 let mut this = self.clone();
160 this.parts.push(part);
161 Self::new(Value::new(this))
162 }
163
164 #[turbo_tasks::function]
165 pub fn with_path(&self, path: ResolvedVc<FileSystemPath>) -> Vc<Self> {
166 let mut this = self.clone();
167 this.path = path;
168 Self::new(Value::new(this))
169 }
170
171 #[turbo_tasks::function]
172 pub fn with_layer(&self, layer: ResolvedVc<RcStr>) -> Vc<Self> {
173 let mut this = self.clone();
174 this.layer = Some(layer);
175 Self::new(Value::new(this))
176 }
177
178 #[turbo_tasks::function]
179 pub fn with_content_type(&self, content_type: RcStr) -> Vc<Self> {
180 let mut this = self.clone();
181 this.content_type = Some(content_type);
182 Self::new(Value::new(this))
183 }
184
185 #[turbo_tasks::function]
186 pub async fn rename_as(&self, pattern: RcStr) -> Result<Vc<Self>> {
187 let mut this = self.clone();
188 this.rename_as_ref(&pattern).await?;
189 Ok(Self::new(Value::new(this)))
190 }
191
192 #[turbo_tasks::function]
193 pub fn path(&self) -> Vc<FileSystemPath> {
194 *self.path
195 }
196
197 #[turbo_tasks::function]
198 pub fn query(&self) -> Vc<RcStr> {
199 *self.query
200 }
201
202 #[turbo_tasks::function]
207 pub async fn output_name(
208 &self,
209 context_path: Vc<FileSystemPath>,
210 expected_extension: RcStr,
211 ) -> Result<Vc<RcStr>> {
212 let path = &*self.path.await?;
217 let mut name = if let Some(inner) = context_path.await?.get_path_to(path) {
218 clean_separators(inner)
219 } else {
220 clean_separators(&self.path.to_string().await?)
221 };
222 let removed_extension = name.ends_with(&*expected_extension);
223 if removed_extension {
224 name.truncate(name.len() - expected_extension.len());
225 }
226 let mut name = clean_additional_extensions(&name);
230
231 let default_modifier = match expected_extension.as_str() {
232 ".js" => Some("ecmascript"),
233 ".css" => Some("css"),
234 _ => None,
235 };
236
237 let mut hasher = Xxh3Hash64Hasher::new();
238 let mut has_hash = false;
239 let AssetIdent {
240 path: _,
241 query,
242 fragment,
243 assets,
244 modifiers,
245 parts,
246 layer,
247 content_type,
248 } = self;
249 let query = query.await?;
250 if !query.is_empty() {
251 0_u8.deterministic_hash(&mut hasher);
252 query.deterministic_hash(&mut hasher);
253 has_hash = true;
254 }
255 if let Some(fragment) = fragment {
256 1_u8.deterministic_hash(&mut hasher);
257 fragment.await?.deterministic_hash(&mut hasher);
258 has_hash = true;
259 }
260 for (key, ident) in assets.iter() {
261 2_u8.deterministic_hash(&mut hasher);
262 key.await?.deterministic_hash(&mut hasher);
263 ident.to_string().await?.deterministic_hash(&mut hasher);
264 has_hash = true;
265 }
266 for modifier in modifiers.iter() {
267 let modifier = modifier.await?;
268 if let Some(default_modifier) = default_modifier {
269 if *modifier == default_modifier {
270 continue;
271 }
272 }
273 3_u8.deterministic_hash(&mut hasher);
274 modifier.deterministic_hash(&mut hasher);
275 has_hash = true;
276 }
277 for part in parts.iter() {
278 4_u8.deterministic_hash(&mut hasher);
279 match part {
280 ModulePart::Evaluation => {
281 1_u8.deterministic_hash(&mut hasher);
282 }
283 ModulePart::Export(export) => {
284 2_u8.deterministic_hash(&mut hasher);
285 export.deterministic_hash(&mut hasher);
286 }
287 ModulePart::RenamedExport {
288 original_export,
289 export,
290 } => {
291 3_u8.deterministic_hash(&mut hasher);
292 original_export.deterministic_hash(&mut hasher);
293 export.deterministic_hash(&mut hasher);
294 }
295 ModulePart::RenamedNamespace { export } => {
296 4_u8.deterministic_hash(&mut hasher);
297 export.deterministic_hash(&mut hasher);
298 }
299 ModulePart::Internal(id) => {
300 5_u8.deterministic_hash(&mut hasher);
301 id.deterministic_hash(&mut hasher);
302 }
303 ModulePart::Locals => {
304 6_u8.deterministic_hash(&mut hasher);
305 }
306 ModulePart::Exports => {
307 7_u8.deterministic_hash(&mut hasher);
308 }
309 ModulePart::Facade => {
310 8_u8.deterministic_hash(&mut hasher);
311 }
312 }
313
314 has_hash = true;
315 }
316 if let Some(layer) = layer {
317 5_u8.deterministic_hash(&mut hasher);
318 layer.await?.deterministic_hash(&mut hasher);
319 has_hash = true;
320 }
321 if let Some(content_type) = content_type {
322 6_u8.deterministic_hash(&mut hasher);
323 content_type.deterministic_hash(&mut hasher);
324 has_hash = true;
325 }
326
327 if has_hash {
328 let hash = encode_hex(hasher.finish());
329 let truncated_hash = &hash[..8];
330 write!(name, "_{truncated_hash}")?;
331 }
332
333 let mut i = 0;
336 static NODE_MODULES: &str = "_node_modules_";
337 if let Some(j) = name.rfind(NODE_MODULES) {
338 i = j + NODE_MODULES.len();
339 }
340 const MAX_FILENAME: usize = 80;
341 if name.len() - i > MAX_FILENAME {
342 i = name.len() - MAX_FILENAME;
343 if let Some(j) = name[i..].find('_') {
344 if j < 20 {
345 i += j + 1;
346 }
347 }
348 }
349 if i > 0 {
350 let hash = encode_hex(hash_xxh3_hash64(&name.as_bytes()[..i]));
351 let truncated_hash = &hash[..5];
352 name = format!("{}_{}", truncated_hash, &name[i..]);
353 }
354 if !removed_extension {
358 name += "._";
359 }
360 name += &expected_extension;
361 Ok(Vc::cell(name.into()))
362 }
363}
364
365fn clean_separators(s: &str) -> String {
366 static SEPARATOR_REGEX: Lazy<Regex> = Lazy::new(|| Regex::new(r"[/#?]").unwrap());
367 SEPARATOR_REGEX.replace_all(s, "_").to_string()
368}
369
370fn clean_additional_extensions(s: &str) -> String {
371 s.replace('.', "_")
372}