next_core/
page_loader.rs

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