From 13f8fcbb6b7754469012a8cf7853fac542bf91a3 Mon Sep 17 00:00:00 2001 From: Will Binns-Smith Date: Wed, 9 Oct 2024 21:55:37 -0700 Subject: [PATCH] =?UTF-8?q?[Turbopack]=20Implement=20support=20for=20webpa?= =?UTF-8?q?ck=E2=80=99s=20`stats.json`=20(#70996)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This allows bundle analyzer tools to visualize and explore the output of Next.js apps built with Turbopack. Like Turbopack’s support for manifest files, this creates individual stats files per entry point and merges them into a single stats file for the app. To do: - [x] Make opt-in and build-only - [x] Apply to pages router Test Plan: `TURBOPACK_BUILD=1 TURBOPACK=1 pnpm build && pnpm dlx webpack-bundle-analyzer .next/server/webpack-stats. json .` --- Cargo.lock | 1 + crates/next-api/Cargo.toml | 1 + crates/next-api/src/app.rs | 59 +++++-- crates/next-api/src/lib.rs | 1 + crates/next-api/src/pages.rs | 29 +++- crates/next-api/src/project.rs | 7 + crates/next-api/src/webpack_stats.rs | 156 ++++++++++++++++++ .../next/src/server/dev/turbopack-utils.ts | 11 ++ .../server/dev/turbopack/manifest-loader.ts | 95 +++++++++++ packages/next/src/shared/lib/constants.ts | 1 + turbopack/crates/turbo-tasks-fs/src/lib.rs | 18 +- .../crates/turbo-tasks/src/primitives.rs | 1 + .../turbopack-browser/src/ecmascript/chunk.rs | 12 +- .../turbopack-browser/src/ecmascript/mod.rs | 1 + turbopack/crates/turbopack-core/src/asset.rs | 9 + .../turbopack-core/src/chunk/chunk_group.rs | 4 +- .../crates/turbopack-core/src/chunk/mod.rs | 14 +- turbopack/crates/turbopack-core/src/output.rs | 4 + .../turbopack-ecmascript/src/chunk/mod.rs | 14 +- 19 files changed, 401 insertions(+), 37 deletions(-) create mode 100644 crates/next-api/src/webpack_stats.rs diff --git a/Cargo.lock b/Cargo.lock index 0ce961a6814f7..0f213cb1af56f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4069,6 +4069,7 @@ dependencies = [ "futures", "indexmap 1.9.3", "next-core", + "regex", "serde", "serde_json", "shadow-rs", diff --git a/crates/next-api/Cargo.toml b/crates/next-api/Cargo.toml index 7871972543b01..a28c9ce8ca118 100644 --- a/crates/next-api/Cargo.toml +++ b/crates/next-api/Cargo.toml @@ -17,6 +17,7 @@ anyhow = { workspace = true, features = ["backtrace"] } futures = { workspace = true } indexmap = { workspace = true } next-core = { workspace = true } +regex = { workspace = true } serde = { workspace = true } serde_json = { workspace = true } shadow-rs = { workspace = true } diff --git a/crates/next-api/src/app.rs b/crates/next-api/src/app.rs index f56f487ad1c66..3c90ceded6f86 100644 --- a/crates/next-api/src/app.rs +++ b/crates/next-api/src/app.rs @@ -1,5 +1,5 @@ use anyhow::{Context, Result}; -use indexmap::{IndexMap, IndexSet}; +use indexmap::{indexset, IndexMap, IndexSet}; use next_core::{ all_assets_from_entries, app_segment_config::NextSegmentConfig, @@ -81,6 +81,7 @@ use crate::{ project::Project, route::{AppPageRoute, Endpoint, Route, Routes, WrittenEndpoint}, server_actions::create_server_actions_manifest, + webpack_stats::generate_webpack_stats, }; #[turbo_tasks::value] @@ -831,8 +832,8 @@ impl AppEndpoint { let server_path = node_root.join("server".into()); - let mut server_assets = vec![]; - let mut client_assets = vec![]; + let mut server_assets = indexset![]; + let mut client_assets = indexset![]; // assets to add to the middleware manifest (to be loaded in the edge runtime). let mut middleware_assets = vec![]; @@ -854,7 +855,7 @@ impl AppEndpoint { let mut client_shared_chunks_paths = vec![]; for chunk in client_shared_chunk_group.assets.await?.iter().copied() { - client_assets.push(chunk); + client_assets.insert(chunk); let chunk_path = chunk.ident().path().await?; if chunk_path.extension_ref() == Some("js") { @@ -997,7 +998,7 @@ impl AppEndpoint { File::from(serde_json::to_string_pretty(&app_build_manifest)?).into(), ), )); - server_assets.push(app_build_manifest_output); + server_assets.insert(app_build_manifest_output); // polyfill-nomodule.js is a pre-compiled asset distributed as part of next, // load it as a RawModule. @@ -1014,7 +1015,29 @@ impl AppEndpoint { .context("failed to resolve client-relative path to polyfill")? .into(); let polyfill_client_paths = vec![polyfill_client_path]; - client_assets.push(Vc::upcast(polyfill_output_asset)); + client_assets.insert(Vc::upcast(polyfill_output_asset)); + + if *this + .app_project + .project() + .should_create_webpack_stats() + .await? + { + let webpack_stats = + generate_webpack_stats(app_entry.original_name.clone(), &client_assets) + .await?; + let stats_output: Vc> = + Vc::upcast(VirtualOutputAsset::new( + node_root.join( + format!("server/app{manifest_path_prefix}/webpack-stats.json",) + .into(), + ), + AssetContent::file( + File::from(serde_json::to_string_pretty(&webpack_stats)?).into(), + ), + )); + server_assets.insert(Vc::upcast(stats_output)); + } let build_manifest = BuildManifest { root_main_files: client_shared_chunks_paths, @@ -1029,7 +1052,7 @@ impl AppEndpoint { File::from(serde_json::to_string_pretty(&build_manifest)?).into(), ), )); - server_assets.push(build_manifest_output); + server_assets.insert(build_manifest_output); let entry_manifest = ClientReferenceManifest::build_output( node_root, @@ -1042,7 +1065,7 @@ impl AppEndpoint { this.app_project.project().next_config(), runtime, ); - server_assets.push(entry_manifest); + server_assets.insert(entry_manifest); if runtime == NextRuntime::Edge { middleware_assets.push(entry_manifest); @@ -1095,7 +1118,7 @@ impl AppEndpoint { ))) } - let client_assets = OutputAssets::new(client_assets); + let client_assets = OutputAssets::new(client_assets.iter().cloned().collect::>()); let next_font_manifest_output = create_font_manifest( this.app_project.project().client_root(), @@ -1108,7 +1131,7 @@ impl AppEndpoint { true, ) .await?; - server_assets.push(next_font_manifest_output); + server_assets.insert(next_font_manifest_output); let endpoint_output = match runtime { NextRuntime::Edge => { @@ -1139,7 +1162,7 @@ impl AppEndpoint { Vc::upcast(chunking_context), ) .await?; - server_assets.push(manifest); + server_assets.insert(manifest); evaluatable_assets.push(loader); } @@ -1230,12 +1253,12 @@ impl AppEndpoint { .cell(), ), )); - server_assets.push(middleware_manifest_v2); + server_assets.insert(middleware_manifest_v2); // create app paths manifest let app_paths_manifest_output = create_app_paths_manifest(node_root, &app_entry.original_name, entry_file)?; - server_assets.push(app_paths_manifest_output); + server_assets.insert(app_paths_manifest_output); // create react-loadable-manifest for next/dynamic let mut dynamic_import_modules = collect_next_dynamic_imports( @@ -1267,7 +1290,7 @@ impl AppEndpoint { AppEndpointOutput::Edge { files, - server_assets: Vc::cell(server_assets), + server_assets: Vc::cell(server_assets.iter().cloned().collect::>()), client_assets, } } @@ -1292,7 +1315,7 @@ impl AppEndpoint { Vc::upcast(chunking_context), ) .await?; - server_assets.push(manifest); + server_assets.insert(manifest); evaluatable_assets.push(loader); } @@ -1377,7 +1400,7 @@ impl AppEndpoint { } .instrument(tracing::trace_span!("server node entrypoint")) .await?); - server_assets.push(rsc_chunk); + server_assets.insert(rsc_chunk); let app_paths_manifest_output = create_app_paths_manifest( node_root, @@ -1388,7 +1411,7 @@ impl AppEndpoint { .context("RSC chunk path should be within app paths manifest directory")? .into(), )?; - server_assets.push(app_paths_manifest_output); + server_assets.insert(app_paths_manifest_output); // create react-loadable-manifest for next/dynamic let availability_info = Value::new(AvailabilityInfo::Root); @@ -1422,7 +1445,7 @@ impl AppEndpoint { AppEndpointOutput::NodeJs { rsc_chunk, - server_assets: Vc::cell(server_assets), + server_assets: Vc::cell(server_assets.iter().cloned().collect::>()), client_assets, } } diff --git a/crates/next-api/src/lib.rs b/crates/next-api/src/lib.rs index 063d0ccb7c6ea..9cdd878ee7b13 100644 --- a/crates/next-api/src/lib.rs +++ b/crates/next-api/src/lib.rs @@ -18,6 +18,7 @@ pub mod project; pub mod route; mod server_actions; mod versioned_content_map; +mod webpack_stats; // Declare build-time information variables generated in build.rs shadow_rs::shadow!(build); diff --git a/crates/next-api/src/pages.rs b/crates/next-api/src/pages.rs index 4fdfe4a86c79b..1a4f71d591492 100644 --- a/crates/next-api/src/pages.rs +++ b/crates/next-api/src/pages.rs @@ -71,6 +71,7 @@ use crate::{ }, project::Project, route::{Endpoint, Route, Routes, WrittenEndpoint}, + webpack_stats::generate_webpack_stats, }; #[turbo_tasks::value] @@ -1050,16 +1051,18 @@ impl PageEndpoint { }; let pathname = this.pathname.await?; - let original_name = this.original_name.await?; + let original_name = &*this.original_name.await?; let client_assets = OutputAssets::new(client_assets); + let manifest_path_prefix = get_asset_prefix_from_pathname(&pathname); + let node_root = this.pages_project.project().node_root(); let next_font_manifest_output = create_font_manifest( this.pages_project.project().client_root(), - this.pages_project.project().node_root(), + node_root, this.pages_project.pages_dir(), - &original_name, - &get_asset_prefix_from_pathname(&pathname), + original_name, + &manifest_path_prefix, &pathname, client_assets, false, @@ -1067,6 +1070,24 @@ impl PageEndpoint { .await?; server_assets.push(next_font_manifest_output); + if *this + .pages_project + .project() + .should_create_webpack_stats() + .await? + { + let webpack_stats = + generate_webpack_stats(original_name.to_owned(), &client_assets.await?).await?; + let stats_output: Vc> = Vc::upcast(VirtualOutputAsset::new( + node_root + .join(format!("server/pages{manifest_path_prefix}/webpack-stats.json",).into()), + AssetContent::file( + File::from(serde_json::to_string_pretty(&webpack_stats)?).into(), + ), + )); + server_assets.push(Vc::upcast(stats_output)); + } + let page_output = match *ssr_chunk.await? { SsrChunk::NodeJs { entry, diff --git a/crates/next-api/src/project.rs b/crates/next-api/src/project.rs index c9461722b1892..0a91d003814ac 100644 --- a/crates/next-api/src/project.rs +++ b/crates/next-api/src/project.rs @@ -618,6 +618,13 @@ impl Project { self.js_config } + #[turbo_tasks::function] + pub(super) async fn should_create_webpack_stats(&self) -> Result> { + Ok(Vc::cell( + self.env.read("TURBOPACK_STATS".into()).await?.is_some(), + )) + } + #[turbo_tasks::function] pub(super) async fn execution_context(self: Vc) -> Result> { let node_root = self.node_root(); diff --git a/crates/next-api/src/webpack_stats.rs b/crates/next-api/src/webpack_stats.rs new file mode 100644 index 0000000000000..a5e223dee2c43 --- /dev/null +++ b/crates/next-api/src/webpack_stats.rs @@ -0,0 +1,156 @@ +use anyhow::Result; +use indexmap::{IndexMap, IndexSet}; +use serde::Serialize; +use turbo_tasks::{RcStr, Vc}; +use turbopack_browser::ecmascript::EcmascriptDevChunk; +use turbopack_core::{ + chunk::{Chunk, ChunkItem}, + output::OutputAsset, +}; + +pub async fn generate_webpack_stats<'a, I>( + entry_name: RcStr, + entry_assets: I, +) -> Result +where + I: IntoIterator>>, +{ + let mut assets = vec![]; + let mut chunks = vec![]; + let mut chunk_items: IndexMap>, IndexSet> = IndexMap::new(); + let mut modules = vec![]; + for asset in entry_assets { + let path = normalize_client_path(&asset.ident().path().await?.path); + + let Some(asset_len) = *asset.size_bytes().await? else { + continue; + }; + + if let Some(chunk) = Vc::try_resolve_downcast_type::(*asset).await? { + let chunk_ident = normalize_client_path(&chunk.ident().path().await?.path); + chunks.push(WebpackStatsChunk { + size: asset_len, + files: vec![chunk_ident.clone().into()], + id: chunk_ident.clone().into(), + ..Default::default() + }); + + for item in chunk.chunk().chunk_items().await? { + // let name = + chunk_items + .entry(*item) + .or_default() + .insert(chunk_ident.clone().into()); + } + } + + assets.push(WebpackStatsAsset { + ty: "asset".into(), + name: path.clone().into(), + chunks: vec![path.into()], + size: asset_len, + ..Default::default() + }); + } + + for (chunk_item, chunks) in chunk_items { + let size = *chunk_item.content_ident().path().read().len().await?; + let path = chunk_item.asset_ident().path().await?.path.clone(); + modules.push(WebpackStatsModule { + name: path.clone(), + id: path.clone(), + chunks: chunks.into_iter().collect(), + size, + }); + } + + let mut entrypoints = IndexMap::new(); + entrypoints.insert( + entry_name.clone(), + WebpackStatsEntrypoint { + name: entry_name.clone(), + chunks: chunks.iter().map(|c| c.id.clone()).collect(), + assets: assets + .iter() + .map(|a| WebpackStatsEntrypointAssets { + name: a.name.clone(), + }) + .collect(), + }, + ); + + Ok(WebpackStats { + assets, + entrypoints, + chunks, + modules, + }) +} + +fn normalize_client_path(path: &str) -> String { + let next_re = regex::Regex::new(r"^_next/").unwrap(); + next_re.replace(path, ".next/").into() +} + +#[derive(Serialize, Clone, Debug, Default)] +#[serde(rename_all = "camelCase")] +pub struct WebpackStatsAssetInfo {} + +#[derive(Serialize, Debug, Default)] +#[serde(rename_all = "camelCase")] +pub struct WebpackStatsAsset { + #[serde(rename = "type")] + pub ty: RcStr, + pub name: RcStr, + pub info: WebpackStatsAssetInfo, + pub size: u64, + pub emitted: bool, + pub compared_for_emit: bool, + pub cached: bool, + pub chunks: Vec, +} + +#[derive(Serialize, Debug, Default)] +#[serde(rename_all = "camelCase")] +pub struct WebpackStatsChunk { + pub rendered: bool, + pub initial: bool, + pub entry: bool, + pub recorded: bool, + pub id: RcStr, + pub size: u64, + pub hash: RcStr, + pub files: Vec, +} + +#[derive(Serialize, Debug)] +#[serde(rename_all = "camelCase")] +pub struct WebpackStatsModule { + pub name: RcStr, + pub id: RcStr, + pub chunks: Vec, + pub size: Option, +} + +#[derive(Serialize, Debug)] +#[serde(rename_all = "camelCase")] +pub struct WebpackStatsEntrypointAssets { + pub name: RcStr, +} + +#[derive(Serialize, Debug)] +#[serde(rename_all = "camelCase")] +pub struct WebpackStatsEntrypoint { + pub name: RcStr, + pub chunks: Vec, + pub assets: Vec, +} + +#[derive(Serialize, Debug)] +#[serde(rename_all = "camelCase")] +pub struct WebpackStats { + pub assets: Vec, + pub entrypoints: IndexMap, + pub chunks: Vec, + pub modules: Vec, +} diff --git a/packages/next/src/server/dev/turbopack-utils.ts b/packages/next/src/server/dev/turbopack-utils.ts index b5491988f8405..9c175d08cc202 100644 --- a/packages/next/src/server/dev/turbopack-utils.ts +++ b/packages/next/src/server/dev/turbopack-utils.ts @@ -368,6 +368,8 @@ export async function handleRouteType({ hooks?: HandleRouteTypeHooks // dev }) { + const shouldCreateWebpackStats = process.env.TURBOPACK_STATS != null + switch (route.type) { case 'page': { const clientKey = getEntryKey('pages', 'client', page) @@ -422,6 +424,10 @@ export async function handleRouteType({ await manifestLoader.loadFontManifest(page, 'pages') await manifestLoader.loadLoadableManifest(page, 'pages') + if (shouldCreateWebpackStats) { + await manifestLoader.loadWebpackStats(page, 'pages') + } + await manifestLoader.writeManifests({ devRewrites, productionRewrites, @@ -559,6 +565,11 @@ export async function handleRouteType({ await manifestLoader.loadActionManifest(page) await manifestLoader.loadLoadableManifest(page, 'app') await manifestLoader.loadFontManifest(page, 'app') + + if (shouldCreateWebpackStats) { + await manifestLoader.loadWebpackStats(page, 'app') + } + await manifestLoader.writeManifests({ devRewrites, productionRewrites, diff --git a/packages/next/src/server/dev/turbopack/manifest-loader.ts b/packages/next/src/server/dev/turbopack/manifest-loader.ts index 36ade3f3015f1..a82c0a5490ef1 100644 --- a/packages/next/src/server/dev/turbopack/manifest-loader.ts +++ b/packages/next/src/server/dev/turbopack/manifest-loader.ts @@ -2,6 +2,7 @@ import type { EdgeFunctionDefinition, MiddlewareManifest, } from '../../../build/webpack/plugins/middleware-plugin' +import type { StatsAsset, StatsChunk, StatsChunkGroup, StatsModule, StatsCompilation as WebpackStats } from 'webpack' import type { BuildManifest } from '../../get-page-files' import type { AppBuildManifest } from '../../../build/webpack/plugins/app-build-manifest-plugin' import type { PagesManifest } from '../../../build/webpack/plugins/pages-manifest-plugin' @@ -22,6 +23,7 @@ import { REACT_LOADABLE_MANIFEST, SERVER_REFERENCE_MANIFEST, TURBOPACK_CLIENT_MIDDLEWARE_MANIFEST, + WEBPACK_STATS, } from '../../../shared/lib/constants' import { join, posix } from 'path' import { readFile } from 'fs/promises' @@ -60,6 +62,7 @@ async function readPartialManifest( | typeof BUILD_MANIFEST | typeof APP_BUILD_MANIFEST | typeof PAGES_MANIFEST + | typeof WEBPACK_STATS | typeof APP_PATHS_MANIFEST | `${typeof SERVER_REFERENCE_MANIFEST}.json` | `${typeof NEXT_FONT_MANIFEST}.json` @@ -109,6 +112,7 @@ export class TurbopackManifestLoader { private middlewareManifests: Map = new Map() private pagesManifests: Map = new Map() + private webpackStats: Map = new Map() private encryptionKey: string private readonly distDir: string @@ -137,6 +141,7 @@ export class TurbopackManifestLoader { this.loadableManifests.delete(key) this.middlewareManifests.delete(key) this.pagesManifests.delete(key) + this.webpackStats.delete(key) } async loadActionManifest(pageName: string): Promise { @@ -267,6 +272,21 @@ export class TurbopackManifestLoader { ) } + private async writeWebpackStats(): Promise { + const webpackStats = this.mergeWebpackStats( + this.webpackStats.values() + ) + const path = join( + this.distDir, + 'server', + WEBPACK_STATS + ) + deleteCache(path) + await writeFileAtomic( + path, + JSON.stringify(webpackStats, null, 2) + ) + } async loadBuildManifest( pageName: string, @@ -278,6 +298,77 @@ export class TurbopackManifestLoader { ) } + async loadWebpackStats( + pageName: string, + type: 'app' | 'pages' = 'pages' + ): Promise { + this.webpackStats.set( + getEntryKey(type, 'client', pageName), + await readPartialManifest(this.distDir, WEBPACK_STATS, pageName, type) + ) + } + + private mergeWebpackStats(statsFiles: Iterable): WebpackStats { + const entrypoints: Record = {}; + const assets: Map = new Map() + const chunks: Map = new Map() + const modules: Map = new Map() + + for (const statsFile of statsFiles) { + if (statsFile.entrypoints) { + for (const [k, v] of Object.entries( + statsFile.entrypoints + )) { + if (!entrypoints[k]) { + entrypoints[k] = v + } + } + } + + if (statsFile.assets) { + for (const asset of statsFile.assets) { + if (!assets.has(asset.name)) { + assets.set(asset.name, asset) + } + } + } + + if (statsFile.chunks) { + for (const chunk of statsFile.chunks) { + if (!chunks.has(chunk.name)) { + chunks.set(chunk.name, chunk) + } + } + } + + if (statsFile.modules) { + for (const module of statsFile.modules) { + const id = module.id; + if (id != null) { + // Merge the chunk list for the module. This can vary across endpoints. + const existing = modules.get(id); + if (existing == null) { + modules.set(id, module) + } else if (module.chunks != null && existing.chunks != null) { + for (const chunk of module.chunks) { + if (!existing.chunks.includes(chunk)) { + existing.chunks.push(chunk) + } + } + } + } + } + } + } + + return { + entrypoints, + assets: [...assets.values()], + chunks: [...chunks.values()], + modules: [...modules.values()], + } + } + private mergeBuildManifests(manifests: Iterable) { const manifest: Partial & Pick = { pages: { @@ -673,5 +764,9 @@ export class TurbopackManifestLoader { await this.writeClientMiddlewareManifest() await this.writeNextFontManifest() await this.writePagesManifest() + + if (process.env.TURBOPACK_STATS != null) { + await this.writeWebpackStats() + } } } diff --git a/packages/next/src/shared/lib/constants.ts b/packages/next/src/shared/lib/constants.ts index 41251f0682616..c1b6926434e60 100644 --- a/packages/next/src/shared/lib/constants.ts +++ b/packages/next/src/shared/lib/constants.ts @@ -29,6 +29,7 @@ export const PHASE_DEVELOPMENT_SERVER = 'phase-development-server' export const PHASE_TEST = 'phase-test' export const PHASE_INFO = 'phase-info' export const PAGES_MANIFEST = 'pages-manifest.json' +export const WEBPACK_STATS = 'webpack-stats.json' export const APP_PATHS_MANIFEST = 'app-paths-manifest.json' export const APP_PATH_ROUTES_MANIFEST = 'app-path-routes-manifest.json' export const BUILD_MANIFEST = 'build-manifest.json' diff --git a/turbopack/crates/turbo-tasks-fs/src/lib.rs b/turbopack/crates/turbo-tasks-fs/src/lib.rs index f000757d464eb..8cdb819cdd95d 100644 --- a/turbopack/crates/turbo-tasks-fs/src/lib.rs +++ b/turbopack/crates/turbo-tasks-fs/src/lib.rs @@ -28,13 +28,9 @@ use std::{ borrow::Cow, cmp::min, collections::HashSet, - fmt::{ - Debug, Display, Formatter, Write as _, {self}, - }, + fmt::{self, Debug, Display, Formatter, Write as _}, fs::FileType, - io::{ - BufRead, ErrorKind, {self}, - }, + io::{self, BufRead, ErrorKind}, mem::take, path::{Path, PathBuf, MAIN_SEPARATOR}, sync::Arc, @@ -1856,6 +1852,8 @@ mod mime_option_serde { #[turbo_tasks::value(shared)] #[derive(Debug, Clone, Default)] pub struct FileMeta { + // Size of the file + // len: u64, permissions: Permissions, #[serde(with = "mime_option_serde")] #[turbo_tasks(trace_ignore)] @@ -1995,6 +1993,14 @@ impl FileContent { #[turbo_tasks::value_impl] impl FileContent { + #[turbo_tasks::function] + pub async fn len(self: Vc) -> Result>> { + Ok(Vc::cell(match &*self.await? { + FileContent::Content(file) => Some(file.content.len() as u64), + FileContent::NotFound => None, + })) + } + #[turbo_tasks::function] pub async fn parse_json(self: Vc) -> Result> { let this = self.await?; diff --git a/turbopack/crates/turbo-tasks/src/primitives.rs b/turbopack/crates/turbo-tasks/src/primitives.rs index a49fbeb5fa369..1be86461db572 100644 --- a/turbopack/crates/turbo-tasks/src/primitives.rs +++ b/turbopack/crates/turbo-tasks/src/primitives.rs @@ -14,6 +14,7 @@ __turbo_tasks_internal_primitive!(Option); __turbo_tasks_internal_primitive!(Option); __turbo_tasks_internal_primitive!(Vec); __turbo_tasks_internal_primitive!(Option); +__turbo_tasks_internal_primitive!(Option); __turbo_tasks_internal_primitive!(bool); __turbo_tasks_internal_primitive!(u8); __turbo_tasks_internal_primitive!(u16); diff --git a/turbopack/crates/turbopack-browser/src/ecmascript/chunk.rs b/turbopack/crates/turbopack-browser/src/ecmascript/chunk.rs index 6d4671107d3be..f19725fecfaf9 100644 --- a/turbopack/crates/turbopack-browser/src/ecmascript/chunk.rs +++ b/turbopack/crates/turbopack-browser/src/ecmascript/chunk.rs @@ -16,7 +16,7 @@ use crate::{ecmascript::content::EcmascriptDevChunkContent, BrowserChunkingConte /// Development Ecmascript chunk. #[turbo_tasks::value(shared)] -pub(crate) struct EcmascriptDevChunk { +pub struct EcmascriptDevChunk { chunking_context: Vc, chunk: Vc, } @@ -73,6 +73,11 @@ impl EcmascriptDevChunk { this.chunk.chunk_content(), )) } + + #[turbo_tasks::function] + pub fn chunk(&self) -> Result>> { + Ok(Vc::upcast(self.chunk)) + } } #[turbo_tasks::value_impl] @@ -83,6 +88,11 @@ impl OutputAsset for EcmascriptDevChunk { AssetIdent::from_path(self.chunking_context.chunk_path(ident, ".js".into())) } + #[turbo_tasks::function] + fn size_bytes(self: Vc) -> Vc> { + self.own_content().content().len() + } + #[turbo_tasks::function] async fn references(self: Vc) -> Result> { let this = self.await?; diff --git a/turbopack/crates/turbopack-browser/src/ecmascript/mod.rs b/turbopack/crates/turbopack-browser/src/ecmascript/mod.rs index a819a6d13264e..f0092aafd48c7 100644 --- a/turbopack/crates/turbopack-browser/src/ecmascript/mod.rs +++ b/turbopack/crates/turbopack-browser/src/ecmascript/mod.rs @@ -7,4 +7,5 @@ pub(crate) mod merged; pub(crate) mod update; pub(crate) mod version; +pub use chunk::EcmascriptDevChunk; pub use content::EcmascriptDevChunkContent; diff --git a/turbopack/crates/turbopack-core/src/asset.rs b/turbopack/crates/turbopack-core/src/asset.rs index dddcb3b674653..6e3784ce9abdf 100644 --- a/turbopack/crates/turbopack-core/src/asset.rs +++ b/turbopack/crates/turbopack-core/src/asset.rs @@ -64,6 +64,15 @@ impl AssetContent { } } + #[turbo_tasks::function] + pub async fn len(self: Vc) -> Result>> { + let this = self.await?; + match &*this { + AssetContent::File(content) => Ok(content.len()), + AssetContent::Redirect { .. } => Ok(Vc::cell(None)), + } + } + #[turbo_tasks::function] pub async fn parse_json_with_comments(self: Vc) -> Result> { let this = self.await?; diff --git a/turbopack/crates/turbopack-core/src/chunk/chunk_group.rs b/turbopack/crates/turbopack-core/src/chunk/chunk_group.rs index b2b3f5dec36dc..55849babe79b9 100644 --- a/turbopack/crates/turbopack-core/src/chunk/chunk_group.rs +++ b/turbopack/crates/turbopack-core/src/chunk/chunk_group.rs @@ -20,7 +20,7 @@ pub struct MakeChunkGroupResult { /// Creates a chunk group from a set of entries. pub async fn make_chunk_group( chunking_context: Vc>, - entries: impl IntoIterator>>, + chunk_group_entries: impl IntoIterator>>, availability_info: AvailabilityInfo, ) -> Result { let ChunkContentResult { @@ -30,7 +30,7 @@ pub async fn make_chunk_group( forward_edges_inherit_async, local_back_edges_inherit_async, available_async_modules_back_edges_inherit_async, - } = chunk_content(chunking_context, entries, availability_info).await?; + } = chunk_content(chunking_context, chunk_group_entries, availability_info).await?; // Find all local chunk items that are self async let self_async_children = chunk_items diff --git a/turbopack/crates/turbopack-core/src/chunk/mod.rs b/turbopack/crates/turbopack-core/src/chunk/mod.rs index be6728ea47cf2..5e4d96fd224ef 100644 --- a/turbopack/crates/turbopack-core/src/chunk/mod.rs +++ b/turbopack/crates/turbopack-core/src/chunk/mod.rs @@ -125,6 +125,10 @@ pub trait Chunk: Asset { fn references(self: Vc) -> Vc { OutputAssets::empty() } + + fn chunk_items(self: Vc) -> Vc { + ChunkItems(vec![]).cell() + } } /// Aggregated information about a chunk content that can be used by the runtime @@ -201,10 +205,10 @@ pub struct ChunkContentResult { pub async fn chunk_content( chunking_context: Vc>, - entries: impl IntoIterator>>, + chunk_entries: impl IntoIterator>>, availability_info: AvailabilityInfo, ) -> Result { - chunk_content_internal_parallel(chunking_context, entries, availability_info).await + chunk_content_internal_parallel(chunking_context, chunk_entries, availability_info).await } #[derive(Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, TraceRawVcs, Debug)] @@ -565,10 +569,10 @@ impl Visit for ChunkContentVisit { async fn chunk_content_internal_parallel( chunking_context: Vc>, - entries: impl IntoIterator>>, + chunk_entries: impl IntoIterator>>, availability_info: AvailabilityInfo, ) -> Result { - let root_edges = entries + let root_edges = chunk_entries .into_iter() .map(|entry| async move { let entry = entry.resolve().await?; @@ -719,7 +723,7 @@ pub fn round_chunk_item_size(size: usize) -> usize { } #[turbo_tasks::value(transparent)] -pub struct ChunkItems(Vec>>); +pub struct ChunkItems(pub Vec>>); #[turbo_tasks::value] pub struct AsyncModuleInfo { diff --git a/turbopack/crates/turbopack-core/src/output.rs b/turbopack/crates/turbopack-core/src/output.rs index ad6797d82e686..674383bd334c5 100644 --- a/turbopack/crates/turbopack-core/src/output.rs +++ b/turbopack/crates/turbopack-core/src/output.rs @@ -20,6 +20,10 @@ pub trait OutputAsset: Asset { fn references(self: Vc) -> Vc { OutputAssets::empty() } + + fn size_bytes(self: Vc) -> Vc> { + Vc::cell(None) + } } #[turbo_tasks::value(transparent)] diff --git a/turbopack/crates/turbopack-ecmascript/src/chunk/mod.rs b/turbopack/crates/turbopack-ecmascript/src/chunk/mod.rs index ccfcc77c527c9..7e09c2b555d3f 100644 --- a/turbopack/crates/turbopack-ecmascript/src/chunk/mod.rs +++ b/turbopack/crates/turbopack-ecmascript/src/chunk/mod.rs @@ -11,7 +11,7 @@ use turbo_tasks::{RcStr, Value, ValueToString, Vc}; use turbo_tasks_fs::FileSystem; use turbopack_core::{ asset::{Asset, AssetContent}, - chunk::{Chunk, ChunkItem, ChunkingContext, ModuleIds}, + chunk::{Chunk, ChunkItem, ChunkItems, ChunkingContext, ModuleIds}, ident::AssetIdent, introspect::{ module::IntrospectableModule, @@ -137,6 +137,18 @@ impl Chunk for EcmascriptChunk { let content = self.content.await?; Ok(Vc::cell(content.referenced_output_assets.clone())) } + + #[turbo_tasks::function] + async fn chunk_items(&self) -> Result> { + let EcmascriptChunkContent { chunk_items, .. } = &*self.content.await?; + Ok(ChunkItems( + chunk_items + .iter() + .map(|(item, _)| Vc::upcast(*item)) + .collect(), + ) + .cell()) + } } #[turbo_tasks::value_impl]