Repository: vercel/next.js
File: crates/next-api/src/project_asset_hashes_manifest.rs (Rust)

Code before:
                } else {
                    Ok(None)
                }
            })
            .try_flat_join()
            .await?,
    ))
}

#[turbo_tasks::value_impl]
impl Asset for AssetHashesManifestAsset {
    #[turbo_tasks::function]
    async fn content(&self) -> Result<Vc<AssetContent>> {
        let entrypoint_groups = self.project.get_all_endpoint_groups(false).await?;

        let mut files = entrypoint_groups
            .iter()
            .map(|(_, EndpointGroup { primary, .. })| {
                if let &[entry] = &primary.as_slice() {
                    expand_outputs(endpoint_outputs(*entry.endpoint), self.asset_root.clone())
                } else {
                    let endpoints = Vc::cell(primary.iter().map(|entry| entry.endpoint).collect());
                    expand_outputs(endpoints_outputs(endpoints), self.asset_root.clone())
                }
            })
            .try_flat_join()
            .await?;
        // deduplicate shared assets across entrypoints
        files.sort_unstable_by(|(_, a), (_, b)| a.cmp(b));
        files.dedup_by(|(_, a), (_, b)| a == b);

        let asset_paths = files
            .into_iter()
            .map(async |(asset, path)| {
                Ok((
                    path,
                    asset
                        .content()
                        .content_hash(HashAlgorithm::Xxh3Hash64Hex)
                        .await?,
                ))
            })
            .try_join()
            .await?;

        struct Manifest<'a> {
            asset_paths: &'a Vec<(&'a RcStr, ReadRef<Option<RcStr>>)>,
        }

        impl serde::Serialize for Manifest<'_> {
            fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {

Review comment (performance):
It is more efficient if you collect all endpoint_outputs first into a single list of output assets. And then call expand_outputs on that list. This avoid walking shared subgraphs multiple times.

Code after:
        .collect::<Vec<_>>();

    let output_assets = expand_output_assets(
        output_assets
            .iter()
            .try_join()
            .await?
            .into_iter()
            .flatten()
            .map(|asset| ExpandOutputAssetsInput::Asset(*asset)),
        true,
    )
    .await?;

    let mut output_assets = output_assets
        .into_iter()
        .map(async |asset| {
            if let Some(path) = root.get_path_to(&*asset.path().await?) {
                Ok(Some((asset, RcStr::from(path))))
            } else {
                Ok(None)
            }
        })
        .try_flat_join()
        .await?;

    // Shared JS assets aren't duplicated here, but we have some duplicate OutputAssets with the
    // same path, e.g. a static image which exists twice, once with the server and then also with
    // the client chunking context.
    output_assets.sort_unstable_by(|(_, a), (_, b)| a.cmp(b));
    output_assets.dedup_by(|(_, a), (_, b)| a == b);

    Ok(Vc::cell(output_assets))
}

#[turbo_tasks::value_impl]
impl Asset for AssetHashesManifestAsset {
    #[turbo_tasks::function(root)]
    async fn content(&self) -> Result<Vc<AssetContent>> {
        let output_assets = expand_outputs(*self.project, self.asset_root.clone()).await?;

        let asset_paths = output_assets
            .into_iter()
            .map(async |(asset, path)| {
                Ok((
                    path,
                    asset.content_hash(HashAlgorithm::Xxh3Hash128Hex).await?,
                ))
            })
            .try_join()
            .await?;