Skip to content

Commit 2c3387f

Browse files
committed
next analyze: tag polyfill modules through to UI
1 parent 3676d72 commit 2c3387f

File tree

12 files changed

+242
-20
lines changed

12 files changed

+242
-20
lines changed

apps/bundle-analyzer/app/globals.css

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@
3232
--sidebar-accent-foreground: oklch(0.205 0 0);
3333
--sidebar-border: oklch(0.922 0 0);
3434
--sidebar-ring: oklch(0.708 0 0);
35+
--polyfill: #de2670;
36+
--polyfill-foreground: #fce7f3;
3537
}
3638

3739
.dark {
@@ -62,6 +64,8 @@
6264
--sidebar-accent-foreground: oklch(0.985 0 0);
6365
--sidebar-border: oklch(0.269 0 0);
6466
--sidebar-ring: oklch(0.439 0 0);
67+
--polyfill: #de2670;
68+
--polyfill-foreground: #7c1d3f;
6569
}
6670

6771
@theme inline {
@@ -98,6 +102,8 @@
98102
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
99103
--color-sidebar-border: var(--sidebar-border);
100104
--color-sidebar-ring: var(--sidebar-ring);
105+
--color-polyfill: var(--polyfill);
106+
--color-polyfill-foreground: var(--polyfill-foreground);
101107
}
102108

103109
@layer base {

apps/bundle-analyzer/app/page.tsx

Lines changed: 39 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ import { Input } from '@/components/ui/input'
1414
import { Skeleton, TreemapSkeleton } from '@/components/ui/skeleton'
1515
import { ToggleGroup, ToggleGroupItem } from '@/components/ui/toggle-group'
1616
import { AnalyzeData, ModulesData } from '@/lib/analyze-data'
17+
import { SpecialModule } from '@/lib/types'
18+
import { getSpecialModuleType } from '@/lib/utils'
1719

1820
function formatBytes(bytes: number): string {
1921
if (bytes === 0) return '0 B'
@@ -137,6 +139,11 @@ export default function Home() {
137139
const isAnyLoading = isAnalyzeLoading || isModulesLoading
138140
const rootSourceIndex = getRootSourceIndex(analyzeData)
139141

142+
const specialModuleType = getSpecialModuleType(
143+
analyzeData,
144+
selectedSourceIndex
145+
)
146+
140147
return (
141148
<main
142149
className="h-screen flex flex-col bg-background"
@@ -289,12 +296,39 @@ export default function Home() {
289296
{selectedSourceIndex != null &&
290297
analyzeData.source(selectedSourceIndex) && (
291298
<>
292-
<p className="text-xs text-muted-foreground mt-2">
293-
Output Size:{' '}
294-
{formatBytes(
295-
analyzeData.getSourceOutputSize(selectedSourceIndex)
299+
<dl className="space-y-2">
300+
<div>
301+
<dt className="text-xs text-muted-foreground inline">
302+
Output Size:{' '}
303+
</dt>
304+
<dd className="text-xs text-muted-foreground inline">
305+
{formatBytes(
306+
analyzeData.getSourceOutputSize(
307+
selectedSourceIndex
308+
)
309+
)}
310+
</dd>
311+
</div>
312+
{(specialModuleType === SpecialModule.POLYFILL_MODULE ||
313+
specialModuleType ===
314+
SpecialModule.POLYFILL_NOMODULE) && (
315+
<div className="flex items-center gap-2">
316+
<dt className="inline-flex items-center rounded-md bg-pink-50 dark:bg-pink-900/30 px-2 py-1 text-xs font-medium text-pink-800 dark:text-pink-300 ring-1 ring-inset ring-pink-800/10 dark:ring-pink-300/20 shrink-0">
317+
Polyfill
318+
</dt>
319+
<dd className="text-xs text-muted-foreground">
320+
Next.js built-in polyfills
321+
{specialModuleType ===
322+
SpecialModule.POLYFILL_NOMODULE ? (
323+
<>
324+
. <pre>polyfill-nomodule.js</pre> is only sent
325+
to legacy browsers.
326+
</>
327+
) : null}
328+
</dd>
329+
</div>
296330
)}
297-
</p>
331+
</dl>
298332
{modulesData && (
299333
<ImportChain
300334
key={selectedSourceIndex}

apps/bundle-analyzer/components/treemap-visualizer.tsx

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
'use client'
22

3-
import { darken, lighten } from 'polished'
3+
import { darken, lighten, readableColor } from 'polished'
44
import type React from 'react'
55
import { useEffect, useMemo, useRef, useState } from 'react'
66
import type { AnalyzeData } from '@/lib/analyze-data'
@@ -9,6 +9,7 @@ import {
99
type LayoutNode,
1010
type LayoutNodeInfo,
1111
} from '@/lib/treemap-layout'
12+
import { SpecialModule } from '@/lib/types'
1213

1314
interface TreemapVisualizerProps {
1415
analyzeData: AnalyzeData
@@ -23,6 +24,8 @@ interface TreemapVisualizerProps {
2324
onHoveredNodeChangeDelayed?: (nodeInfo: LayoutNodeInfo | null) => void
2425
searchQuery?: string
2526
filterSource?: (sourceIndex: number) => boolean
27+
isModulePolyfillChunk?: (sourceIndex: number) => boolean
28+
isNoModulePolyfillChunk?: (sourceIndex: number) => boolean
2629
}
2730

2831
function getFileColor(node: {
@@ -33,8 +36,13 @@ function getFileColor(node: {
3336
server?: boolean
3437
client?: boolean
3538
traced?: boolean
39+
specialModuleType: SpecialModule | null
3640
}): string {
37-
const { js, css, json, asset, client, traced } = node
41+
const { js, css, json, asset, client, traced, specialModuleType } = node
42+
43+
if (isPolyfill(specialModuleType)) {
44+
return '#DE2670'
45+
}
3846

3947
let color = '#9ca3af' // gray-400 default
4048
if (js) color = '#0068d6'
@@ -54,6 +62,13 @@ function getFileColor(node: {
5462
return color
5563
}
5664

65+
function isPolyfill(specialModuleType: SpecialModule | null): boolean {
66+
return (
67+
specialModuleType === SpecialModule.POLYFILL_MODULE ||
68+
specialModuleType === SpecialModule.POLYFILL_NOMODULE
69+
)
70+
}
71+
5772
function findNodeAtPosition(
5873
node: LayoutNode,
5974
x: number,
@@ -320,7 +335,8 @@ function drawTreemap(
320335
ctx.strokeRect(rect.x, rect.y, rect.width, rect.height)
321336

322337
if (rect.width > 60 && rect.height > 30) {
323-
ctx.fillStyle = colors.text
338+
const textColor = readableColor(color)
339+
ctx.fillStyle = textColor
324340
ctx.font = '12px sans-serif'
325341
ctx.textAlign = 'center'
326342
ctx.textBaseline = 'middle'
@@ -516,6 +532,7 @@ function wrapLayoutWithAncestorsUsingIndices(
516532
titleBarHeight: titleBarHeight,
517533
children: [currentNode],
518534
sourceIndex: ancestorIndex,
535+
specialModuleType: null,
519536
}
520537

521538
currentNode = ancestorNode
@@ -539,6 +556,7 @@ function wrapLayoutWithAncestorsUsingIndices(
539556
titleBarHeight: minTitleBarHeight,
540557
children: [currentNode],
541558
sourceIndex: rootIndex,
559+
specialModuleType: null,
542560
}
543561

544562
return rootNode

apps/bundle-analyzer/lib/analyze-data.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -388,6 +388,20 @@ export class AnalyzeData {
388388
return { client, server, traced, js, css, json, asset }
389389
}
390390

391+
isPolyfillModule(index: number): boolean {
392+
const fullSourcePath = this.getFullSourcePath(index)
393+
return fullSourcePath.endsWith(
394+
'node_modules/next/dist/build/polyfills/polyfill-module.js'
395+
)
396+
}
397+
398+
isPolyfillNoModule(index: number): boolean {
399+
const fullSourcePath = this.getFullSourcePath(index)
400+
return fullSourcePath.endsWith(
401+
'node_modules/next/dist/build/polyfills/polyfill-nomodule.js'
402+
)
403+
}
404+
391405
// Get the raw header for debugging
392406
getRawAnalyzeHeader(): AnalyzeDataHeader {
393407
return this.analyzeHeader

apps/bundle-analyzer/lib/treemap-layout.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import type { AnalyzeData } from './analyze-data'
22
import { layoutTreemap } from './layout-treemap'
3+
import { SpecialModule } from './types'
4+
import { getSpecialModuleType } from './utils'
35

46
export interface LayoutRect {
57
x: number
@@ -18,6 +20,7 @@ export interface LayoutNode extends LayoutNodeInfo {
1820
size: number
1921
rect: LayoutRect
2022
type: 'file' | 'directory' | 'collapsed-directory'
23+
specialModuleType: SpecialModule | null
2124
titleBarHeight?: number
2225
children?: LayoutNode[]
2326
itemCount?: number
@@ -141,6 +144,7 @@ function computeTreemapLayoutFromAnalyzeInternal(
141144
type: 'file',
142145
rect,
143146
sourceIndex,
147+
specialModuleType: getSpecialModuleType(analyzeData, sourceIndex),
144148
...analyzeData.getSourceFlags(sourceIndex),
145149
}
146150
}
@@ -178,6 +182,7 @@ function computeTreemapLayoutFromAnalyzeInternal(
178182
itemCount: countDescendants(sourceIndex),
179183
children: [],
180184
sourceIndex,
185+
specialModuleType: null,
181186
}
182187
}
183188

@@ -211,6 +216,7 @@ function computeTreemapLayoutFromAnalyzeInternal(
211216
titleBarHeight,
212217
children: [],
213218
sourceIndex,
219+
specialModuleType: null,
214220
}
215221
}
216222

@@ -240,6 +246,7 @@ function computeTreemapLayoutFromAnalyzeInternal(
240246
titleBarHeight,
241247
children: layoutChildren,
242248
sourceIndex,
249+
specialModuleType: null,
243250
}
244251
}
245252

apps/bundle-analyzer/lib/types.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,3 +33,8 @@ export interface RouteManifest {
3333
staticRoutes: Array<Route>
3434
dynamicRoutes: Array<Route>
3535
}
36+
37+
export enum SpecialModule {
38+
POLYFILL_MODULE,
39+
POLYFILL_NOMODULE,
40+
}

apps/bundle-analyzer/lib/utils.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import { type ClassValue, clsx } from 'clsx'
22
import { twMerge } from 'tailwind-merge'
3+
import { SpecialModule } from './types'
4+
import { AnalyzeData } from './analyze-data'
35

46
export function cn(...inputs: ClassValue[]) {
57
return twMerge(clsx(inputs))
@@ -8,3 +10,19 @@ export function cn(...inputs: ClassValue[]) {
810
export function jsonFetcher<T>(url: string): Promise<T> {
911
return fetch(url).then((res) => res.json())
1012
}
13+
14+
export function getSpecialModuleType(
15+
analyzeData: AnalyzeData | undefined,
16+
sourceIndex: number | null
17+
): SpecialModule | null {
18+
if (!analyzeData || sourceIndex == null) return null
19+
20+
const path = analyzeData.source(sourceIndex)?.path || ''
21+
if (path.endsWith('polyfill-module.js')) {
22+
return SpecialModule.POLYFILL_MODULE
23+
} else if (path.endsWith('polyfill-nomodule.js')) {
24+
return SpecialModule.POLYFILL_NOMODULE
25+
}
26+
27+
return null
28+
}

crates/next-api/src/analyze.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -367,6 +367,7 @@ pub async fn analyze_output_assets(output_assets: Vc<OutputAssets>) -> Result<Vc
367367
// Skip source maps.
368368
continue;
369369
}
370+
370371
let output_file_index = builder.add_output_file(AnalyzeOutputFile { filename });
371372
let chunk_parts = split_output_asset_into_parts(*asset).await?;
372373
for chunk_part in chunk_parts {

crates/next-api/src/app.rs

Lines changed: 26 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -62,14 +62,16 @@ use turbopack_core::{
6262
chunk_group_info::{ChunkGroup, ChunkGroupEntry},
6363
},
6464
output::{OutputAsset, OutputAssets, OutputAssetsWithReferenced},
65-
raw_output::RawOutput,
6665
reference::all_assets_from_entries,
6766
reference_type::{CommonJsReferenceSubType, CssReferenceSubType, ReferenceType},
6867
resolve::{origin::PlainResolveOrigin, parse::Request, pattern::Pattern},
6968
source::Source,
69+
source_map::SourceMapAsset,
7070
virtual_output::VirtualOutputAsset,
7171
};
72-
use turbopack_ecmascript::resolve::cjs_resolve;
72+
use turbopack_ecmascript::{
73+
resolve::cjs_resolve, single_file_ecmascript_output::SingleFileEcmascriptOutput,
74+
};
7375

7476
use crate::{
7577
dynamic_imports::{NextDynamicChunkAvailability, collect_next_dynamic_chunks},
@@ -1300,11 +1302,11 @@ impl AppEndpoint {
13001302

13011303
let manifest_path_prefix = &app_entry.original_name;
13021304

1303-
// polyfill-nomodule.js is a pre-compiled asset distributed as part of next,
1304-
// load it as a RawModule.
1305+
// polyfill-nomodule.js is a pre-compiled asset distributed as part of next
13051306
let next_package = get_next_package(project.project_path().owned().await?).await?;
1306-
let polyfill_source =
1307-
FileSource::new(next_package.join("dist/build/polyfills/polyfill-nomodule.js")?);
1307+
let polyfill_source_path =
1308+
next_package.join("dist/build/polyfills/polyfill-nomodule.js")?;
1309+
let polyfill_source = FileSource::new(polyfill_source_path.clone());
13081310
let polyfill_output_path = client_chunking_context
13091311
.chunk_path(
13101312
Some(Vc::upcast(polyfill_source)),
@@ -1314,13 +1316,26 @@ impl AppEndpoint {
13141316
)
13151317
.owned()
13161318
.await?;
1317-
let polyfill_output_asset = ResolvedVc::upcast(
1318-
RawOutput::new(polyfill_output_path, Vc::upcast(polyfill_source))
1319-
.to_resolved()
1320-
.await?,
1321-
);
1319+
1320+
let polyfill_output = SingleFileEcmascriptOutput::new(
1321+
polyfill_output_path.clone(),
1322+
polyfill_source_path,
1323+
Vc::upcast(polyfill_source),
1324+
)
1325+
.to_resolved()
1326+
.await?;
1327+
1328+
let polyfill_output_asset = ResolvedVc::upcast(polyfill_output);
13221329
client_assets.insert(polyfill_output_asset);
13231330

1331+
let polyfill_source_map_asset = SourceMapAsset::new_fixed(
1332+
polyfill_output_path.clone(),
1333+
*ResolvedVc::upcast(polyfill_output),
1334+
)
1335+
.to_resolved()
1336+
.await?;
1337+
client_assets.insert(ResolvedVc::upcast(polyfill_source_map_asset));
1338+
13241339
let client_assets: ResolvedVc<OutputAssets> =
13251340
ResolvedVc::cell(client_assets.into_iter().collect::<Vec<_>>());
13261341

crates/next-api/src/route.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ use turbo_tasks::{
99
};
1010
use turbopack_core::{
1111
module_graph::{GraphEntries, ModuleGraph},
12-
output::OutputAssets,
12+
output::{OptionOutputAsset, OutputAssets},
1313
};
1414

1515
use crate::{operation::OptionEndpoint, paths::ServerPath, project::Project};
@@ -72,6 +72,10 @@ pub trait Endpoint {
7272
}
7373
#[turbo_tasks::function]
7474
fn module_graphs(self: Vc<Self>) -> Vc<ModuleGraphs>;
75+
#[turbo_tasks::function]
76+
fn polyfill_asset(self: Vc<Self>) -> Vc<OptionOutputAsset> {
77+
Vc::cell(None)
78+
}
7579
}
7680

7781
#[derive(

0 commit comments

Comments
 (0)