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