next_core/
page_loader.rs

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 we are provided a prefix path, we need to rewrite our chunk paths to
104        // remove that prefix.
105        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        // We don't need to strip the client relative prefix, because we won't be using
164        // these reference paths with `__turbopack_load__`.
165        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}