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