1use std::io::Write;
2
3use anyhow::{Result, bail};
4use turbo_rcstr::{RcStr, rcstr};
5use turbo_tasks::{ResolvedVc, TryJoinIterExt, Vc, fxindexmap};
6use turbo_tasks_fs::{
7 self, File, FileContent, FileSystemPath, FileSystemPathOption, rope::RopeBuilder,
8};
9use turbopack_core::{
10 asset::{Asset, AssetContent},
11 chunk::{ChunkData, ChunkingContext, ChunksData},
12 context::AssetContext,
13 ident::AssetIdent,
14 module::Module,
15 output::{OutputAsset, OutputAssets},
16 proxied_asset::ProxiedAsset,
17 reference_type::{EntryReferenceSubType, ReferenceType},
18 source::Source,
19 virtual_source::VirtualSource,
20};
21use turbopack_ecmascript::{chunk::EcmascriptChunkData, utils::StringifyJs};
22
23use crate::{embed_js::next_js_file_path, util::get_asset_path_from_pathname};
24
25#[turbo_tasks::function]
26pub async fn create_page_loader_entry_module(
27 client_context: Vc<Box<dyn AssetContext>>,
28 entry_asset: Vc<Box<dyn Source>>,
29 pathname: RcStr,
30) -> Result<Vc<Box<dyn Module>>> {
31 let mut result = RopeBuilder::default();
32 writeln!(result, "const PAGE_PATH = {};\n", StringifyJs(&pathname))?;
33
34 let page_loader_path = next_js_file_path(rcstr!("entry/page-loader.ts"))
35 .owned()
36 .await?;
37 let base_code = page_loader_path.read();
38 if let FileContent::Content(base_file) = &*base_code.await? {
39 result += base_file.content()
40 } else {
41 bail!("required file `entry/page-loader.ts` not found");
42 }
43
44 let file = File::from(result.build());
45
46 let virtual_source = Vc::upcast(VirtualSource::new(
47 page_loader_path,
48 AssetContent::file(file.into()),
49 ));
50
51 let module = client_context
52 .process(
53 entry_asset,
54 ReferenceType::Entry(EntryReferenceSubType::Page),
55 )
56 .module()
57 .to_resolved()
58 .await?;
59
60 let module = client_context
61 .process(
62 virtual_source,
63 ReferenceType::Internal(ResolvedVc::cell(fxindexmap! {
64 rcstr!("PAGE") => module,
65 })),
66 )
67 .module();
68 Ok(module)
69}
70
71#[turbo_tasks::value(shared)]
72pub struct PageLoaderAsset {
73 pub server_root: FileSystemPath,
74 pub pathname: RcStr,
75 pub rebase_prefix_path: ResolvedVc<FileSystemPathOption>,
76 pub page_chunks: ResolvedVc<OutputAssets>,
77 pub chunking_context: ResolvedVc<Box<dyn ChunkingContext>>,
78 pub use_fixed_path: bool,
79}
80
81#[turbo_tasks::value_impl]
82impl PageLoaderAsset {
83 #[turbo_tasks::function]
84 pub fn new(
85 server_root: FileSystemPath,
86 pathname: RcStr,
87 rebase_prefix_path: ResolvedVc<FileSystemPathOption>,
88 page_chunks: ResolvedVc<OutputAssets>,
89 chunking_context: ResolvedVc<Box<dyn ChunkingContext>>,
90 use_fixed_path: bool,
91 ) -> Vc<Self> {
92 Self {
93 server_root,
94 pathname,
95 rebase_prefix_path,
96 page_chunks,
97 chunking_context,
98 use_fixed_path,
99 }
100 .cell()
101 }
102
103 #[turbo_tasks::function]
104 async fn chunks_data(
105 &self,
106 rebase_prefix_path: Vc<FileSystemPathOption>,
107 ) -> Result<Vc<ChunksData>> {
108 let mut chunks = self.page_chunks;
109
110 if let Some(rebase_path) = &*rebase_prefix_path.await? {
113 let root_path = rebase_path.root().owned().await?;
114 let rebased = chunks
115 .await?
116 .iter()
117 .map(|&chunk| {
118 let root_path = root_path.clone();
119
120 async move {
121 Vc::upcast::<Box<dyn OutputAsset>>(ProxiedAsset::new(
122 *chunk,
123 FileSystemPath::rebase(
124 chunk.path().owned().await?,
125 rebase_path.clone(),
126 root_path.clone(),
127 )
128 .owned()
129 .await?,
130 ))
131 .to_resolved()
132 .await
133 }
134 })
135 .try_join()
136 .await?;
137 chunks = ResolvedVc::cell(rebased);
138 };
139
140 Ok(ChunkData::from_assets(self.server_root.clone(), *chunks))
141 }
142}
143
144impl PageLoaderAsset {
145 async fn ident_for_path(&self) -> Result<Vc<AssetIdent>> {
146 let rebase_prefix_path = self.rebase_prefix_path.await?;
147 let root = rebase_prefix_path.as_ref().unwrap_or(&self.server_root);
148 Ok(AssetIdent::from_path(root.join(&format!(
149 "static/chunks/pages{}",
150 get_asset_path_from_pathname(&self.pathname, ".js")
151 ))?)
152 .with_modifier(rcstr!("page loader asset")))
153 }
154}
155
156#[turbo_tasks::value_impl]
157impl OutputAsset for PageLoaderAsset {
158 #[turbo_tasks::function]
159 async fn path(self: Vc<Self>) -> Result<Vc<FileSystemPath>> {
160 let this = self.await?;
161 let ident = this.ident_for_path().await?;
162 if this.use_fixed_path {
163 Ok(ident.path())
168 } else {
169 Ok(this
170 .chunking_context
171 .chunk_path(Some(Vc::upcast(self)), ident, None, rcstr!(".js")))
172 }
173 }
174
175 #[turbo_tasks::function]
176 async fn references(self: Vc<Self>) -> Result<Vc<OutputAssets>> {
177 let chunks = self.await?.page_chunks.await?;
178
179 let mut references = Vec::with_capacity(chunks.len());
180 for &chunk in chunks.iter() {
181 references.push(chunk);
182 }
183
184 for chunk_data in &*self.chunks_data(FileSystemPathOption::none()).await? {
187 references.extend(chunk_data.references().await?.iter().copied());
188 }
189
190 Ok(Vc::cell(references))
191 }
192}
193
194#[turbo_tasks::value_impl]
195impl Asset for PageLoaderAsset {
196 #[turbo_tasks::function]
197 async fn content(self: Vc<Self>) -> Result<Vc<AssetContent>> {
198 let this = &*self.await?;
199
200 let chunks_data = self.chunks_data(*this.rebase_prefix_path).await?;
201 let chunks_data = chunks_data.iter().try_join().await?;
202 let chunks_data: Vec<_> = chunks_data
203 .iter()
204 .map(|chunk_data| EcmascriptChunkData::new(chunk_data))
205 .collect();
206
207 let content = format!(
208 "__turbopack_load_page_chunks__({}, {:#})\n",
209 StringifyJs(&this.pathname),
210 StringifyJs(&chunks_data)
211 );
212
213 Ok(AssetContent::file(File::from(content).into()))
214 }
215}