diff --git a/apps/bundle-analyzer/app/page.tsx b/apps/bundle-analyzer/app/page.tsx
index 4811c03ba4fc..d7333dc72908 100644
--- a/apps/bundle-analyzer/app/page.tsx
+++ b/apps/bundle-analyzer/app/page.tsx
@@ -89,6 +89,7 @@ export default function Home() {
size: number
server?: boolean
client?: boolean
+ traced?: boolean
} | null>(null)
const [searchQuery, setSearchQuery] = useState('')
@@ -265,6 +266,9 @@ export default function Home() {
{hoveredNodeInfo.server && (
server
)}
+ {hoveredNodeInfo.traced && (
+ traced
+ )}
)}
>
diff --git a/apps/bundle-analyzer/components/import-chain.tsx b/apps/bundle-analyzer/components/import-chain.tsx
index cf101fbd19e8..25ba500c5f64 100644
--- a/apps/bundle-analyzer/components/import-chain.tsx
+++ b/apps/bundle-analyzer/components/import-chain.tsx
@@ -11,6 +11,7 @@ import {
Server,
Globe,
MessageCircleQuestion,
+ Package,
} from 'lucide-react'
import { useMemo, useState } from 'react'
import type {
@@ -52,6 +53,7 @@ interface DependentInfo {
sourceIndex: number | undefined
ident: string
isAsync: boolean
+ isTraced: boolean
depth: number
}
@@ -219,6 +221,7 @@ export function ImportChain({
.map((index: number) => ({
index,
async: false,
+ traced: false,
depth: depthMap.get(index) ?? Infinity,
})),
...modulesData
@@ -226,6 +229,15 @@ export function ImportChain({
.map((index: number) => ({
index,
async: true,
+ traced: false,
+ depth: depthMap.get(index) ?? Infinity,
+ })),
+ ...modulesData
+ .tracedModuleDependents(currentModuleIndex)
+ .map((index: number) => ({
+ index,
+ async: false,
+ traced: true,
depth: depthMap.get(index) ?? Infinity,
})),
]
@@ -243,7 +255,7 @@ export function ImportChain({
// Build info for each dependent
const dependentsInfo: DependentInfo[] = validDependents.map(
- ({ index: moduleIndex, async: isAsync, depth }) => {
+ ({ index: moduleIndex, async: isAsync, traced: isTraced, depth }) => {
const sourceIndex = getSourceIndexFromModuleIndex(moduleIndex)
let ident = modulesData.module(moduleIndex)?.ident || ''
return {
@@ -251,6 +263,7 @@ export function ImportChain({
sourceIndex,
ident,
isAsync,
+ isTraced,
depth,
}
}
@@ -376,6 +389,11 @@ export function ImportChain({
(async)
)}
+ {currentItemInfo?.isTraced && (
+
+ (traced)
+
+ )}
{index > 0 ? (
) : undefined}
@@ -406,19 +424,7 @@ export function ImportChain({
{currentItemInfo?.isAsync &&
}
- {!level.layer ? (
-
-
-
- ) : /app/.test(level.layer || '') ? (
-
-
-
- ) : (
-
-
-
- )}
+
@@ -536,3 +542,31 @@ export function ImportChain({
)
}
+
+function LayerIcon({ layer }: { layer: string | undefined }) {
+ if (!layer || layer === 'external') {
+ return (
+
+
+
+ )
+ } else if (layer.includes('app')) {
+ return (
+
+
+
+ )
+ } else if (layer === 'externals-tracing') {
+ return (
+
+ )
+ } else {
+ return (
+
+
+
+ )
+ }
+}
diff --git a/apps/bundle-analyzer/components/treemap-visualizer.tsx b/apps/bundle-analyzer/components/treemap-visualizer.tsx
index 1f0190011985..5f6f0a2d137c 100644
--- a/apps/bundle-analyzer/components/treemap-visualizer.tsx
+++ b/apps/bundle-analyzer/components/treemap-visualizer.tsx
@@ -947,6 +947,7 @@ export function TreemapVisualizer({
size: node.size,
server: node.server,
client: node.client,
+ traced: node.traced,
}
if (node.type === 'directory') {
diff --git a/apps/bundle-analyzer/components/ui/badge.tsx b/apps/bundle-analyzer/components/ui/badge.tsx
index 85cca5f7fc83..e5c74fb30862 100644
--- a/apps/bundle-analyzer/components/ui/badge.tsx
+++ b/apps/bundle-analyzer/components/ui/badge.tsx
@@ -19,6 +19,8 @@ const badgeVariants = cva(
'border-transparent bg-blue-50 dark:bg-blue-950 text-blue-700 dark:text-blue-300 ring-1 ring-inset ring-blue-700/10 dark:ring-blue-300/20',
server:
'border-transparent bg-purple-50 dark:bg-purple-950 text-purple-700 dark:text-purple-300 ring-1 ring-inset ring-purple-700/10 dark:ring-purple-300/20',
+ traced:
+ 'border-transparent bg-grey-50 dark:bg-grey-950 text-grey-700 dark:text-grey-300 ring-1 ring-inset ring-grey-700/10 dark:ring-grey-300/20',
polyfill:
'border-transparent bg-polyfill/10 dark:bg-polyfill/30 text-polyfill dark:text-polyfill-foreground ring-1 ring-inset ring-polyfill/20',
},
diff --git a/apps/bundle-analyzer/lib/analyze-data.ts b/apps/bundle-analyzer/lib/analyze-data.ts
index bdd8b7e41693..98a4a955d11b 100644
--- a/apps/bundle-analyzer/lib/analyze-data.ts
+++ b/apps/bundle-analyzer/lib/analyze-data.ts
@@ -48,8 +48,10 @@ interface ModulesDataHeader {
modules: AnalyzeModule[]
module_dependents: EdgesDataReference
async_module_dependents: EdgesDataReference
+ traced_module_dependents: EdgesDataReference
module_dependencies: EdgesDataReference
async_module_dependencies: EdgesDataReference
+ traced_module_dependencies: EdgesDataReference
}
/**
@@ -167,6 +169,13 @@ export class ModulesData {
)
}
+ tracedModuleDependents(index: ModuleIndex): ModuleIndex[] {
+ return this.readEdgesDataAtIndex(
+ this.modulesHeader.traced_module_dependents,
+ index
+ )
+ }
+
moduleDependencies(index: ModuleIndex): ModuleIndex[] {
return this.readEdgesDataAtIndex(
this.modulesHeader.module_dependencies,
@@ -181,6 +190,13 @@ export class ModulesData {
)
}
+ tracedModuleDependencies(index: ModuleIndex): ModuleIndex[] {
+ return this.readEdgesDataAtIndex(
+ this.modulesHeader.traced_module_dependencies,
+ index
+ )
+ }
+
getRawModulesHeader(): ModulesDataHeader {
return this.modulesHeader
}
@@ -447,10 +463,15 @@ export class AnalyzeData {
client = true
} else if (outputFile.filename.startsWith('[project]/')) {
traced = true
+ server = true
} else {
server = true
}
- if (outputFile.filename.endsWith('.js')) {
+ if (
+ outputFile.filename.endsWith('.js') ||
+ outputFile.filename.endsWith('.mjs') ||
+ outputFile.filename.endsWith('.cjs')
+ ) {
js = true
} else if (outputFile.filename.endsWith('.css')) {
css = true
diff --git a/apps/bundle-analyzer/lib/module-graph.ts b/apps/bundle-analyzer/lib/module-graph.ts
index cfaa53486b14..98760b3f13e1 100644
--- a/apps/bundle-analyzer/lib/module-graph.ts
+++ b/apps/bundle-analyzer/lib/module-graph.ts
@@ -117,6 +117,14 @@ export function computeModuleDepthMap(
}
}
+ // Process traced dependencies
+ const tracedDependencies = modulesData.tracedModuleDependencies(moduleIndex)
+ for (const depIndex of tracedDependencies) {
+ if (!depthMap.has(depIndex)) {
+ depthMap.set(depIndex, newDepth)
+ }
+ }
+
i++
// Check if we need to process the next delayed queue to insert its items into the depth map
diff --git a/apps/bundle-analyzer/lib/treemap-layout.ts b/apps/bundle-analyzer/lib/treemap-layout.ts
index 6a39d7dc34a7..228a295b9839 100644
--- a/apps/bundle-analyzer/lib/treemap-layout.ts
+++ b/apps/bundle-analyzer/lib/treemap-layout.ts
@@ -15,6 +15,7 @@ export interface LayoutNodeInfo {
size: number
server?: boolean
client?: boolean
+ traced?: boolean
}
export interface LayoutNode extends LayoutNodeInfo {
@@ -25,7 +26,6 @@ export interface LayoutNode extends LayoutNodeInfo {
titleBarHeight?: number
children?: LayoutNode[]
itemCount?: number
- traced?: boolean
js?: boolean
css?: boolean
json?: boolean
@@ -112,7 +112,6 @@ function computeTreemapLayoutFromAnalyzeInternal(
foldedPath: string,
rect: LayoutRect,
metadata: SourceMetadata[],
- filterSource: ((sourceIndex: SourceIndex) => boolean) | undefined,
sizeMode: SizeMode
): LayoutNode {
const source = analyzeData.source(sourceIndex)
@@ -139,7 +138,6 @@ function computeTreemapLayoutFromAnalyzeInternal(
foldedPath + source.path,
rect,
metadata,
- filterSource,
sizeMode
)
}
@@ -253,7 +251,6 @@ function computeTreemapLayoutFromAnalyzeInternal(
'',
childRects[i],
metadata,
- filterSource,
sizeMode
)
)
@@ -288,7 +285,6 @@ export function computeTreemapLayoutFromAnalyze(
'',
rect,
metadata,
- filterSource,
sizeMode
)
}
diff --git a/apps/bundle-analyzer/next.config.mjs b/apps/bundle-analyzer/next.config.mjs
index a04884622e9e..f4b0f78f8482 100644
--- a/apps/bundle-analyzer/next.config.mjs
+++ b/apps/bundle-analyzer/next.config.mjs
@@ -1,7 +1,18 @@
+const developmentRewrites = () => {
+ return [
+ {
+ source: '/data/:path*',
+ destination: 'http://localhost:4000/data/:path*',
+ },
+ ]
+}
+
/** @type {import('next').NextConfig} */
const nextConfig = {
output: 'export',
distDir: 'dist',
+ rewrites:
+ process.env.NODE_ENV === 'development' ? developmentRewrites : undefined,
}
export default nextConfig
diff --git a/crates/next-api/src/analyze.rs b/crates/next-api/src/analyze.rs
index 920d60c2bbc4..46a5b554b202 100644
--- a/crates/next-api/src/analyze.rs
+++ b/crates/next-api/src/analyze.rs
@@ -2,7 +2,9 @@ use std::{borrow::Cow, io::Write};
use anyhow::Result;
use byteorder::{BE, WriteBytesExt};
-use rustc_hash::FxHashMap;
+use either::Either;
+use next_core::app_structure::FileSystemPathVec;
+use rustc_hash::{FxHashMap, FxHashSet};
use serde::Serialize;
use turbo_rcstr::RcStr;
use turbo_tasks::{
@@ -12,18 +14,17 @@ use turbo_tasks_fs::{
File, FileContent, FileSystemPath,
rope::{Rope, RopeBuilder},
};
-use turbopack_analyze::split_chunk::split_output_asset_into_parts;
+use turbopack_analyze::split_chunk::{split_output_asset_into_parts, split_traced_file_into_parts};
use turbopack_core::{
SOURCE_URL_PROTOCOL,
asset::{Asset, AssetContent},
- chunk::ChunkingType,
+ chunk::{ChunkingType, TracedMode},
module::Module,
+ module_graph::{GraphTraversalAction, ModuleGraph},
output::{OutputAsset, OutputAssets, OutputAssetsReference},
reference::all_assets_from_entries,
};
-use crate::route::ModuleGraphs;
-
pub struct EdgesData {
pub offsets: Vec
,
pub data: Vec,
@@ -114,9 +115,13 @@ struct ModulesDataHeader {
/// Edges from modules to modules
pub async_module_dependents: EdgesDataReference,
/// Edges from modules to modules
+ pub traced_module_dependents: EdgesDataReference,
+ /// Edges from modules to modules
pub module_dependencies: EdgesDataReference,
/// Edges from modules to modules
pub async_module_dependencies: EdgesDataReference,
+ /// Edges from modules to modules
+ pub traced_module_dependencies: EdgesDataReference,
}
struct AnalyzeOutputFileBuilder {
@@ -134,8 +139,10 @@ struct AnalyzeModuleBuilder {
module: AnalyzeModule,
dependencies: FxIndexSet,
async_dependencies: FxIndexSet,
+ traced_dependencies: FxIndexSet,
dependents: FxIndexSet,
async_dependents: FxIndexSet,
+ traced_dependents: FxIndexSet,
}
struct AnalyzeDataBuilder {
@@ -299,8 +306,10 @@ impl ModulesDataBuilder {
module: AnalyzeModule { ident, path },
dependencies: FxIndexSet::default(),
async_dependencies: FxIndexSet::default(),
+ traced_dependencies: FxIndexSet::default(),
dependents: FxIndexSet::default(),
async_dependents: FxIndexSet::default(),
+ traced_dependents: FxIndexSet::default(),
});
(&mut self.modules[index as usize], index)
}
@@ -316,6 +325,11 @@ impl ModulesDataBuilder {
.iter()
.map(|s| s.async_dependencies.iter().copied().collect())
.collect();
+ let traced_module_dependencies_vecs: Vec> = self
+ .modules
+ .iter()
+ .map(|s| s.traced_dependencies.iter().copied().collect())
+ .collect();
let module_dependents_vecs: Vec> = self
.modules
.iter()
@@ -326,11 +340,18 @@ impl ModulesDataBuilder {
.iter()
.map(|s| s.async_dependents.iter().copied().collect())
.collect();
+ let traced_module_dependents_vecs: Vec> = self
+ .modules
+ .iter()
+ .map(|s| s.traced_dependents.iter().copied().collect())
+ .collect();
let module_dependencies = EdgesData::from_iterator(&module_dependencies_vecs);
let async_module_dependencies = EdgesData::from_iterator(&async_module_dependencies_vecs);
+ let traced_module_dependencies = EdgesData::from_iterator(&traced_module_dependencies_vecs);
let module_dependents = EdgesData::from_iterator(&module_dependents_vecs);
let async_module_dependents = EdgesData::from_iterator(&async_module_dependents_vecs);
+ let traced_module_dependents = EdgesData::from_iterator(&traced_module_dependents_vecs);
let mut binary_section = EdgesDataSectionBuilder::new();
@@ -338,8 +359,10 @@ impl ModulesDataBuilder {
modules: self.modules.into_iter().map(|s| s.module).collect(),
module_dependents: binary_section.add_edges(&module_dependents),
async_module_dependents: binary_section.add_edges(&async_module_dependents),
+ traced_module_dependents: binary_section.add_edges(&traced_module_dependents),
module_dependencies: binary_section.add_edges(&module_dependencies),
async_module_dependencies: binary_section.add_edges(&async_module_dependencies),
+ traced_module_dependencies: binary_section.add_edges(&traced_module_dependencies),
};
let header_json = serde_json::to_vec(&header).unwrap();
@@ -366,8 +389,23 @@ pub async fn combine_output_assets(
Ok(Vc::cell(combined))
}
+/// Merges two sets of traced modules into one. Used to combine per-route traced
+/// modules with shared modules (e.g. `_app`, `_document`) at report generation time.
+#[turbo_tasks::function]
+pub async fn combine_traced_files(
+ primary: Vc,
+ extra: Vc,
+) -> Result> {
+ let mut combined: Vec = primary.await?.iter().cloned().collect();
+ combined.extend(extra.await?.iter().cloned());
+ Ok(Vc::cell(combined))
+}
+
#[turbo_tasks::function]
-pub async fn analyze_output_assets(output_assets: Vc) -> Result> {
+pub async fn analyze_output_assets(
+ output_assets: Vc,
+ traced_files: Vc,
+) -> Result> {
let output_assets = all_assets_from_entries(output_assets);
let mut builder = AnalyzeDataBuilder::new();
@@ -376,19 +414,44 @@ pub async fn analyze_output_assets(output_assets: Vc) -> Result Either::Left(asset.path().await?),
+ Either::Right(path) => Either::Right(path),
+ };
+ let path = match &file_system_path {
+ Either::Left(path) => &path.path,
+ Either::Right(path) => &path.path,
+ };
+ if path.ends_with(".map") || path.ends_with(".nft.json") {
// Skip source maps.
continue;
}
- let output_file_index = builder.add_output_file(AnalyzeOutputFile { filename });
- let chunk_parts = split_output_asset_into_parts(*asset).await?;
+ let filename = match &file_system_path {
+ Either::Left(path) => path.to_string_ref().await?,
+ Either::Right(path) => path.to_string_ref().await?,
+ };
+
+ let output_file_index = builder.add_output_file(AnalyzeOutputFile {
+ filename: filename.clone(),
+ });
+ let chunk_parts = match asset {
+ Either::Left(asset) => split_output_asset_into_parts(*asset).await?,
+ Either::Right(path) => split_traced_file_into_parts(path).await?,
+ };
for chunk_part in &chunk_parts {
let decoded_source = urlencoding::decode(&chunk_part.source)?;
let source = if let Some(stripped) = decoded_source.strip_prefix(&prefix) {
Cow::Borrowed(stripped)
+ } else if decoded_source.starts_with("[project]/") {
+ decoded_source
} else {
Cow::Owned(format!(
"[project]/{}",
@@ -396,11 +459,12 @@ pub async fn analyze_output_assets(output_assets: Vc) -> Result) -> Result) -> Result> {
+pub async fn analyze_module_graphs(module_graph: Vc) -> Result> {
let mut builder = ModulesDataBuilder::new();
let mut all_modules = FxIndexSet::default();
let mut all_edges = FxIndexSet::default();
let mut all_async_edges = FxIndexSet::default();
- for module_graph in module_graphs.await? {
- let module_graph = module_graph.await?;
- module_graph.traverse_edges_unordered(|parent, node| {
- if let Some((parent_node, reference)) = parent {
- all_modules.insert(parent_node);
- all_modules.insert(node);
- match reference.chunking_type {
- ChunkingType::Async => {
- all_async_edges.insert((parent_node, node));
- }
- _ => {
- all_edges.insert((parent_node, node));
- }
+ let mut all_traced_edges = FxIndexSet::default();
+ let mut traced_modules = FxHashSet::default();
+
+ let module_graph = module_graph.await?;
+ module_graph.traverse_edges_dfs(
+ module_graph.graphs.iter().flat_map(|g| g.entry_modules()),
+ &mut (),
+ |parent, node, _| {
+ all_modules.insert(node);
+ let Some((parent_node, reference)) = parent else {
+ return Ok(GraphTraversalAction::Continue);
+ };
+
+ // ChunkingType::Traced{TracedMode::Entry} => target is always traced
+ // ChunkingType::Traced{TracedMode::Transitive}=> target only traced if parent is traced
+ // ChunkingType::* => target only traced if parent is traced
+ if matches!(
+ reference.chunking_type,
+ ChunkingType::Traced {
+ mode: TracedMode::Entry
+ }
+ ) || traced_modules.contains(&parent_node)
+ {
+ traced_modules.insert(node);
+ all_traced_edges.insert((parent_node, node));
+ return Ok(GraphTraversalAction::Continue);
+ };
+
+ match reference.chunking_type {
+ ChunkingType::Async => {
+ all_async_edges.insert((parent_node, node));
+ }
+ _ => {
+ all_edges.insert((parent_node, node));
}
}
- Ok(())
- })?;
- }
+ Ok(GraphTraversalAction::Continue)
+ },
+ |_, _, _| Ok(()),
+ true,
+ )?;
type ModulePair = (ResolvedVc>, ResolvedVc>);
async fn mapper((from, to): ModulePair) -> Result