next_core/next_manifests/
client_reference_manifest.rs1use anyhow::Result;
2use either::Either;
3use indoc::formatdoc;
4use itertools::Itertools;
5use rustc_hash::FxHashMap;
6use serde::{Deserialize, Serialize};
7use tracing::Instrument;
8use turbo_rcstr::RcStr;
9use turbo_tasks::{
10 FxIndexSet, ReadRef, ResolvedVc, TaskInput, TryFlatJoinIterExt, TryJoinIterExt, ValueToString,
11 Vc, trace::TraceRawVcs,
12};
13use turbo_tasks_fs::{File, FileSystemPath};
14use turbopack_core::{
15 asset::{Asset, AssetContent},
16 chunk::{ChunkingContext, ModuleChunkItemIdExt, ModuleId as TurbopackModuleId},
17 module_graph::async_module_info::AsyncModulesInfo,
18 output::OutputAsset,
19 virtual_output::VirtualOutputAsset,
20};
21use turbopack_ecmascript::utils::StringifyJs;
22
23use super::{ClientReferenceManifest, CssResource, ManifestNode, ManifestNodeEntry, ModuleId};
24use crate::{
25 mode::NextMode,
26 next_app::ClientReferencesChunks,
27 next_client_reference::{ClientReferenceGraphResult, ClientReferenceType},
28 next_config::NextConfig,
29 next_manifests::encode_uri_component::encode_uri_component,
30 util::NextRuntime,
31};
32
33#[derive(TaskInput, Clone, Hash, Debug, PartialEq, Eq, Serialize, Deserialize, TraceRawVcs)]
34pub struct ClientReferenceManifestOptions {
35 pub node_root: ResolvedVc<FileSystemPath>,
36 pub client_relative_path: ResolvedVc<FileSystemPath>,
37 pub entry_name: RcStr,
38 pub client_references: ResolvedVc<ClientReferenceGraphResult>,
39 pub client_references_chunks: ResolvedVc<ClientReferencesChunks>,
40 pub client_chunking_context: ResolvedVc<Box<dyn ChunkingContext>>,
41 pub ssr_chunking_context: Option<ResolvedVc<Box<dyn ChunkingContext>>>,
42 pub async_module_info: ResolvedVc<AsyncModulesInfo>,
43 pub next_config: ResolvedVc<NextConfig>,
44 pub runtime: NextRuntime,
45 pub mode: NextMode,
46}
47
48#[turbo_tasks::value_impl]
49impl ClientReferenceManifest {
50 #[turbo_tasks::function]
51 pub async fn build_output(
52 options: ClientReferenceManifestOptions,
53 ) -> Result<Vc<Box<dyn OutputAsset>>> {
54 let ClientReferenceManifestOptions {
55 node_root,
56 client_relative_path,
57 entry_name,
58 client_references,
59 client_references_chunks,
60 client_chunking_context,
61 ssr_chunking_context,
62 async_module_info,
63 next_config,
64 runtime,
65 mode,
66 } = options;
67 let span = tracing::info_span!(
68 "ClientReferenceManifest build output",
69 entry_name = display(&entry_name)
70 );
71 async move {
72 let mut entry_manifest: ClientReferenceManifest = Default::default();
73 let mut references = FxIndexSet::default();
74 let chunk_suffix_path = next_config.chunk_suffix_path().await?;
75 let prefix_path = next_config
76 .computed_asset_prefix()
77 .await?
78 .as_ref()
79 .map(|p| p.clone())
80 .unwrap_or_default();
81 let suffix_path = chunk_suffix_path
82 .as_ref()
83 .map(|p| p.to_string())
84 .unwrap_or("".into());
85
86 entry_manifest.module_loading.cross_origin = next_config
90 .await?
91 .cross_origin
92 .as_ref()
93 .map(|p| p.to_owned());
94 let ClientReferencesChunks {
95 client_component_client_chunks,
96 layout_segment_client_chunks,
97 client_component_ssr_chunks,
98 } = &*client_references_chunks.await?;
99 let client_relative_path = &*client_relative_path.await?;
100 let node_root_ref = &*node_root.await?;
101
102 let client_references_ecmascript = client_references
103 .await?
104 .client_references
105 .iter()
106 .map(async |r| {
107 Ok(match r.ty() {
108 ClientReferenceType::EcmascriptClientReference(r) => Some((r, r.await?)),
109 ClientReferenceType::CssClientReference(_) => None,
110 })
111 })
112 .try_flat_join()
113 .await?;
114
115 let async_modules = async_module_info
116 .is_async_multiple(Vc::cell(
117 client_references_ecmascript
118 .iter()
119 .flat_map(|(r, r_val)| {
120 [
121 ResolvedVc::upcast(*r),
122 ResolvedVc::upcast(r_val.client_module),
123 ResolvedVc::upcast(r_val.ssr_module),
124 ]
125 })
126 .collect(),
127 ))
128 .await?;
129
130 async fn cached_chunk_paths(
131 cache: &mut FxHashMap<ResolvedVc<Box<dyn OutputAsset>>, ReadRef<FileSystemPath>>,
132 chunks: impl Iterator<Item = ResolvedVc<Box<dyn OutputAsset>>>,
133 ) -> Result<
134 impl Iterator<Item = (ResolvedVc<Box<dyn OutputAsset>>, ReadRef<FileSystemPath>)>,
135 > {
136 let results = chunks
137 .into_iter()
138 .map(|chunk| (chunk, cache.get(&chunk).cloned()))
139 .map(async |(chunk, path)| {
140 Ok(if let Some(path) = path {
141 (chunk, Either::Left(path))
142 } else {
143 (chunk, Either::Right(chunk.path().await?))
144 })
145 })
146 .try_join()
147 .await?;
148
149 for (chunk, path) in &results {
150 if let Either::Right(path) = path {
151 cache.insert(*chunk, path.clone());
152 }
153 }
154 Ok(results.into_iter().map(|(chunk, path)| match path {
155 Either::Left(path) => (chunk, path),
156 Either::Right(path) => (chunk, path),
157 }))
158 }
159 let mut client_chunk_path_cache: FxHashMap<
160 ResolvedVc<Box<dyn OutputAsset>>,
161 ReadRef<FileSystemPath>,
162 > = FxHashMap::default();
163 let mut ssr_chunk_path_cache: FxHashMap<
164 ResolvedVc<Box<dyn OutputAsset>>,
165 ReadRef<FileSystemPath>,
166 > = FxHashMap::default();
167
168 for (client_reference_module, client_reference_module_ref) in
169 client_references_ecmascript
170 {
171 let app_client_reference_ty =
172 ClientReferenceType::EcmascriptClientReference(client_reference_module);
173
174 let server_path = client_reference_module_ref.server_ident.to_string().await?;
175 let client_module = client_reference_module_ref.client_module;
176 let client_chunk_item_id = client_module
177 .chunk_item_id(*ResolvedVc::upcast(client_chunking_context))
178 .await?;
179
180 let (client_chunks_paths, client_is_async) =
181 if let Some((client_chunks, _client_availability_info)) =
182 client_component_client_chunks.get(&app_client_reference_ty)
183 {
184 let client_chunks = client_chunks.await?;
185 references.extend(client_chunks.iter());
186 let client_chunks_paths = cached_chunk_paths(
187 &mut client_chunk_path_cache,
188 client_chunks.iter().copied(),
189 )
190 .await?;
191
192 let chunk_paths = client_chunks_paths
193 .filter_map(|(_, chunk_path)| {
194 client_relative_path
195 .get_path_to(&chunk_path)
196 .map(ToString::to_string)
197 })
198 .filter(|path| path.ends_with(".js"))
201 .map(|path| {
202 format!(
203 "{}{}{}",
204 prefix_path,
205 path.split('/').map(encode_uri_component).format("/"),
206 suffix_path
207 )
208 })
209 .map(RcStr::from)
210 .collect::<Vec<_>>();
211
212 let is_async = async_modules.contains(&ResolvedVc::upcast(client_module));
213
214 (chunk_paths, is_async)
215 } else {
216 (Vec::new(), false)
217 };
218
219 if let Some(ssr_chunking_context) = ssr_chunking_context {
220 let ssr_module = client_reference_module_ref.ssr_module;
221 let ssr_chunk_item_id = ssr_module
222 .chunk_item_id(*ResolvedVc::upcast(ssr_chunking_context))
223 .await?;
224
225 let rsc_chunk_item_id = client_reference_module
226 .chunk_item_id(*ResolvedVc::upcast(ssr_chunking_context))
227 .await?;
228
229 let (ssr_chunks_paths, ssr_is_async) = if runtime == NextRuntime::Edge {
230 (Vec::new(), false)
235 } else if let Some((ssr_chunks, _ssr_availability_info)) =
236 client_component_ssr_chunks.get(&app_client_reference_ty)
237 {
238 let ssr_chunks = ssr_chunks.await?;
239 references.extend(ssr_chunks.iter());
240
241 let ssr_chunks_paths = cached_chunk_paths(
242 &mut ssr_chunk_path_cache,
243 ssr_chunks.iter().copied(),
244 )
245 .await?;
246 let chunk_paths = ssr_chunks_paths
247 .filter_map(|(_, chunk_path)| {
248 node_root_ref
249 .get_path_to(&chunk_path)
250 .map(ToString::to_string)
251 })
252 .map(RcStr::from)
253 .collect::<Vec<_>>();
254
255 let is_async = async_modules.contains(&ResolvedVc::upcast(ssr_module));
256
257 (chunk_paths, is_async)
258 } else {
259 (Vec::new(), false)
260 };
261
262 let rsc_is_async = if runtime == NextRuntime::Edge {
263 false
264 } else {
265 async_modules.contains(&ResolvedVc::upcast(client_reference_module))
266 };
267
268 entry_manifest.client_modules.module_exports.insert(
269 get_client_reference_module_key(&server_path, "*"),
270 ManifestNodeEntry {
271 name: "*".into(),
272 id: (&*client_chunk_item_id).into(),
273 chunks: client_chunks_paths,
274 r#async: client_is_async || ssr_is_async,
279 },
280 );
281
282 let mut ssr_manifest_node = ManifestNode::default();
283 ssr_manifest_node.module_exports.insert(
284 "*".into(),
285 ManifestNodeEntry {
286 name: "*".into(),
287 id: (&*ssr_chunk_item_id).into(),
288 chunks: ssr_chunks_paths,
289 r#async: client_is_async || ssr_is_async,
291 },
292 );
293
294 let mut rsc_manifest_node = ManifestNode::default();
295 rsc_manifest_node.module_exports.insert(
296 "*".into(),
297 ManifestNodeEntry {
298 name: "*".into(),
299 id: (&*rsc_chunk_item_id).into(),
300 chunks: vec![],
301 r#async: rsc_is_async,
302 },
303 );
304
305 match runtime {
306 NextRuntime::NodeJs => {
307 entry_manifest
308 .ssr_module_mapping
309 .insert((&*client_chunk_item_id).into(), ssr_manifest_node);
310 entry_manifest
311 .rsc_module_mapping
312 .insert((&*client_chunk_item_id).into(), rsc_manifest_node);
313 }
314 NextRuntime::Edge => {
315 entry_manifest
316 .edge_ssr_module_mapping
317 .insert((&*client_chunk_item_id).into(), ssr_manifest_node);
318 entry_manifest
319 .edge_rsc_module_mapping
320 .insert((&*client_chunk_item_id).into(), rsc_manifest_node);
321 }
322 }
323 }
324 }
325
326 for (server_component, client_chunks) in layout_segment_client_chunks.iter() {
328 let server_component_name = server_component
329 .server_path()
330 .with_extension("".into())
331 .to_string()
332 .owned()
333 .await?;
334 let mut entry_css_files_with_chunk = Vec::new();
335 let entry_js_files = entry_manifest
336 .entry_js_files
337 .entry(server_component_name.clone())
338 .or_default();
339
340 let client_chunks = &client_chunks.await?;
341 let client_chunks_with_path =
342 cached_chunk_paths(&mut client_chunk_path_cache, client_chunks.iter().copied())
343 .await?;
344
345 for (chunk, chunk_path) in client_chunks_with_path {
346 if let Some(path) = client_relative_path.get_path_to(&chunk_path) {
347 let path = path.into();
350 if chunk_path.extension_ref() == Some("css") {
351 entry_css_files_with_chunk.push((path, chunk));
352 } else {
353 entry_js_files.insert(path);
354 }
355 }
356 }
357
358 let inlined = next_config.await?.experimental.inline_css.unwrap_or(false)
359 && mode.is_production();
360 let entry_css_files_vec = entry_css_files_with_chunk
361 .into_iter()
362 .map(async |(path, chunk)| {
363 let content = if inlined {
364 if let Some(content_file) =
365 chunk.content().file_content().await?.as_content()
366 {
367 Some(content_file.content().to_str()?.into())
368 } else {
369 Some("".into())
370 }
371 } else {
372 None
373 };
374 Ok(CssResource {
375 path,
376 inlined,
377 content,
378 })
379 })
380 .try_join()
381 .await?;
382
383 let entry_css_files = entry_manifest
384 .entry_css_files
385 .entry(server_component_name)
386 .or_default();
387 entry_css_files.extend(entry_css_files_vec);
388 }
389
390 let client_reference_manifest_json = serde_json::to_string(&entry_manifest).unwrap();
391
392 let normalized_manifest_entry = entry_name.replace("%5F", "_");
398 Ok(Vc::upcast(VirtualOutputAsset::new_with_references(
399 node_root.join(
400 format!("server/app{normalized_manifest_entry}_client-reference-manifest.js",)
401 .into(),
402 ),
403 AssetContent::file(
404 File::from(formatdoc! {
405 r#"
406 globalThis.__RSC_MANIFEST = globalThis.__RSC_MANIFEST || {{}};
407 globalThis.__RSC_MANIFEST[{entry_name}] = {manifest}
408 "#,
409 entry_name = StringifyJs(&normalized_manifest_entry),
410 manifest = &client_reference_manifest_json
411 })
412 .into(),
413 ),
414 Vc::cell(references.into_iter().collect()),
415 )))
416 }
417 .instrument(span)
418 .await
419 }
420}
421
422impl From<&TurbopackModuleId> for ModuleId {
423 fn from(module_id: &TurbopackModuleId) -> Self {
424 match module_id {
425 TurbopackModuleId::String(string) => ModuleId::String(string.clone()),
426 TurbopackModuleId::Number(number) => ModuleId::Number(*number as _),
427 }
428 }
429}
430
431pub fn get_client_reference_module_key(server_path: &str, export_name: &str) -> RcStr {
433 if export_name == "*" {
434 server_path.into()
435 } else {
436 format!("{server_path}#{export_name}").into()
437 }
438}