diff --git a/apps/bundle-analyzer/app/globals.css b/apps/bundle-analyzer/app/globals.css
index cc4f7fd3b600d..7c945ac8d8677 100644
--- a/apps/bundle-analyzer/app/globals.css
+++ b/apps/bundle-analyzer/app/globals.css
@@ -32,6 +32,8 @@
--sidebar-accent-foreground: oklch(0.205 0 0);
--sidebar-border: oklch(0.922 0 0);
--sidebar-ring: oklch(0.708 0 0);
+ --polyfill: #5f707f;
+ --polyfill-foreground: #ffffff;
}
.dark {
@@ -62,6 +64,8 @@
--sidebar-accent-foreground: oklch(0.985 0 0);
--sidebar-border: oklch(0.269 0 0);
--sidebar-ring: oklch(0.439 0 0);
+ --polyfill: #5f707f;
+ --polyfill-foreground: #ffffff;
}
@theme inline {
@@ -98,6 +102,8 @@
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
--color-sidebar-border: var(--sidebar-border);
--color-sidebar-ring: var(--sidebar-ring);
+ --color-polyfill: var(--polyfill);
+ --color-polyfill-foreground: var(--polyfill-foreground);
}
@layer base {
diff --git a/apps/bundle-analyzer/app/page.tsx b/apps/bundle-analyzer/app/page.tsx
index cd4a5390d2418..514cf0dd00b72 100644
--- a/apps/bundle-analyzer/app/page.tsx
+++ b/apps/bundle-analyzer/app/page.tsx
@@ -14,6 +14,8 @@ import { Input } from '@/components/ui/input'
import { Skeleton, TreemapSkeleton } from '@/components/ui/skeleton'
import { ToggleGroup, ToggleGroupItem } from '@/components/ui/toggle-group'
import { AnalyzeData, ModulesData } from '@/lib/analyze-data'
+import { SpecialModule } from '@/lib/types'
+import { getSpecialModuleType } from '@/lib/utils'
function formatBytes(bytes: number): string {
if (bytes === 0) return '0 B'
@@ -137,6 +139,11 @@ export default function Home() {
const isAnyLoading = isAnalyzeLoading || isModulesLoading
const rootSourceIndex = getRootSourceIndex(analyzeData)
+ const specialModuleType = getSpecialModuleType(
+ analyzeData,
+ selectedSourceIndex
+ )
+
return (
-
- Output Size:{' '}
- {formatBytes(
- analyzeData.getSourceOutputSize(selectedSourceIndex)
+
+
+
-
+ Output Size:{' '}
+
+ -
+ {formatBytes(
+ analyzeData.getSourceOutputSize(
+ selectedSourceIndex
+ )
+ )}
+
+
+ {(specialModuleType === SpecialModule.POLYFILL_MODULE ||
+ specialModuleType ===
+ SpecialModule.POLYFILL_NOMODULE) && (
+
+
+ Polyfill
+
+
+ Next.js built-in polyfills
+ {specialModuleType ===
+ SpecialModule.POLYFILL_NOMODULE ? (
+ <>
+ . polyfill-nomodule.js is only
+ sent to legacy browsers.
+ >
+ ) : null}
+
+
)}
-
+
{modulesData && (
void
searchQuery?: string
filterSource?: (sourceIndex: number) => boolean
+ isModulePolyfillChunk?: (sourceIndex: number) => boolean
+ isNoModulePolyfillChunk?: (sourceIndex: number) => boolean
}
function getFileColor(node: {
@@ -33,12 +36,17 @@ function getFileColor(node: {
server?: boolean
client?: boolean
traced?: boolean
+ specialModuleType: SpecialModule | null
}): string {
- const { js, css, json, asset, client, traced } = node
+ const { js, css, json, asset, client, traced, specialModuleType } = node
+
+ if (isPolyfill(specialModuleType)) {
+ return '#5f707f'
+ }
let color = '#9ca3af' // gray-400 default
- if (js) color = '#0068d6'
- if (css) color = '#663399'
+ if (js) color = '#4682b4'
+ if (css) color = '#8b7d9e'
if (json) color = '#297a3a'
if (asset) color = '#da2f35'
@@ -54,6 +62,13 @@ function getFileColor(node: {
return color
}
+function isPolyfill(specialModuleType: SpecialModule | null): boolean {
+ return (
+ specialModuleType === SpecialModule.POLYFILL_MODULE ||
+ specialModuleType === SpecialModule.POLYFILL_NOMODULE
+ )
+}
+
function findNodeAtPosition(
node: LayoutNode,
x: number,
@@ -320,7 +335,8 @@ function drawTreemap(
ctx.strokeRect(rect.x, rect.y, rect.width, rect.height)
if (rect.width > 60 && rect.height > 30) {
- ctx.fillStyle = colors.text
+ const textColor = readableColor(color)
+ ctx.fillStyle = textColor
ctx.font = '12px sans-serif'
ctx.textAlign = 'center'
ctx.textBaseline = 'middle'
@@ -516,6 +532,7 @@ function wrapLayoutWithAncestorsUsingIndices(
titleBarHeight: titleBarHeight,
children: [currentNode],
sourceIndex: ancestorIndex,
+ specialModuleType: null,
}
currentNode = ancestorNode
@@ -539,6 +556,7 @@ function wrapLayoutWithAncestorsUsingIndices(
titleBarHeight: minTitleBarHeight,
children: [currentNode],
sourceIndex: rootIndex,
+ specialModuleType: null,
}
return rootNode
diff --git a/apps/bundle-analyzer/lib/analyze-data.ts b/apps/bundle-analyzer/lib/analyze-data.ts
index 17202049f73c6..1ae82a5aa1706 100644
--- a/apps/bundle-analyzer/lib/analyze-data.ts
+++ b/apps/bundle-analyzer/lib/analyze-data.ts
@@ -388,6 +388,20 @@ export class AnalyzeData {
return { client, server, traced, js, css, json, asset }
}
+ isPolyfillModule(index: number): boolean {
+ const fullSourcePath = this.getFullSourcePath(index)
+ return fullSourcePath.endsWith(
+ 'node_modules/next/dist/build/polyfills/polyfill-module.js'
+ )
+ }
+
+ isPolyfillNoModule(index: number): boolean {
+ const fullSourcePath = this.getFullSourcePath(index)
+ return fullSourcePath.endsWith(
+ 'node_modules/next/dist/build/polyfills/polyfill-nomodule.js'
+ )
+ }
+
// Get the raw header for debugging
getRawAnalyzeHeader(): AnalyzeDataHeader {
return this.analyzeHeader
diff --git a/apps/bundle-analyzer/lib/treemap-layout.ts b/apps/bundle-analyzer/lib/treemap-layout.ts
index 29a3a0e8b37a6..67b023928a97a 100644
--- a/apps/bundle-analyzer/lib/treemap-layout.ts
+++ b/apps/bundle-analyzer/lib/treemap-layout.ts
@@ -1,5 +1,7 @@
import type { AnalyzeData } from './analyze-data'
import { layoutTreemap } from './layout-treemap'
+import { SpecialModule } from './types'
+import { getSpecialModuleType } from './utils'
export interface LayoutRect {
x: number
@@ -18,6 +20,7 @@ export interface LayoutNode extends LayoutNodeInfo {
size: number
rect: LayoutRect
type: 'file' | 'directory' | 'collapsed-directory'
+ specialModuleType: SpecialModule | null
titleBarHeight?: number
children?: LayoutNode[]
itemCount?: number
@@ -141,6 +144,7 @@ function computeTreemapLayoutFromAnalyzeInternal(
type: 'file',
rect,
sourceIndex,
+ specialModuleType: getSpecialModuleType(analyzeData, sourceIndex),
...analyzeData.getSourceFlags(sourceIndex),
}
}
@@ -178,6 +182,7 @@ function computeTreemapLayoutFromAnalyzeInternal(
itemCount: countDescendants(sourceIndex),
children: [],
sourceIndex,
+ specialModuleType: null,
}
}
@@ -211,6 +216,7 @@ function computeTreemapLayoutFromAnalyzeInternal(
titleBarHeight,
children: [],
sourceIndex,
+ specialModuleType: null,
}
}
@@ -240,6 +246,7 @@ function computeTreemapLayoutFromAnalyzeInternal(
titleBarHeight,
children: layoutChildren,
sourceIndex,
+ specialModuleType: null,
}
}
diff --git a/apps/bundle-analyzer/lib/types.ts b/apps/bundle-analyzer/lib/types.ts
index cb1efb0df7f25..f0320edf363b3 100644
--- a/apps/bundle-analyzer/lib/types.ts
+++ b/apps/bundle-analyzer/lib/types.ts
@@ -33,3 +33,8 @@ export interface RouteManifest {
staticRoutes: Array
dynamicRoutes: Array
}
+
+export enum SpecialModule {
+ POLYFILL_MODULE,
+ POLYFILL_NOMODULE,
+}
diff --git a/apps/bundle-analyzer/lib/utils.ts b/apps/bundle-analyzer/lib/utils.ts
index 879fcef2e26ea..857a409da3175 100644
--- a/apps/bundle-analyzer/lib/utils.ts
+++ b/apps/bundle-analyzer/lib/utils.ts
@@ -1,5 +1,7 @@
import { type ClassValue, clsx } from 'clsx'
import { twMerge } from 'tailwind-merge'
+import { SpecialModule } from './types'
+import { AnalyzeData } from './analyze-data'
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs))
@@ -8,3 +10,19 @@ export function cn(...inputs: ClassValue[]) {
export function jsonFetcher(url: string): Promise {
return fetch(url).then((res) => res.json())
}
+
+export function getSpecialModuleType(
+ analyzeData: AnalyzeData | undefined,
+ sourceIndex: number | null
+): SpecialModule | null {
+ if (!analyzeData || sourceIndex == null) return null
+
+ const path = analyzeData.source(sourceIndex)?.path || ''
+ if (path.endsWith('polyfill-module.js')) {
+ return SpecialModule.POLYFILL_MODULE
+ } else if (path.endsWith('polyfill-nomodule.js')) {
+ return SpecialModule.POLYFILL_NOMODULE
+ }
+
+ return null
+}
diff --git a/apps/bundle-analyzer/styles/globals.css b/apps/bundle-analyzer/styles/globals.css
index c3272f8b358be..812455c123c1a 100644
--- a/apps/bundle-analyzer/styles/globals.css
+++ b/apps/bundle-analyzer/styles/globals.css
@@ -21,6 +21,8 @@
--border: oklch(0.922 0 0);
--input: oklch(0.922 0 0);
--ring: oklch(0.708 0 0);
+ --polyfill: oklch(0.478 0.0185 252.37);
+ --polyfill-foreground: oklch(0.99 0 0);
--radius: 0.625rem;
}
@@ -42,6 +44,8 @@
--border: oklch(0.269 0 0);
--input: oklch(0.269 0 0);
--ring: oklch(0.439 0 0);
+ --polyfill: oklch(0.478 0.0185 252.37);
+ --polyfill-foreground: oklch(0.99 0 0);
}
@theme inline {
@@ -64,6 +68,8 @@
--color-border: var(--border);
--color-input: var(--input);
--color-ring: var(--ring);
+ --color-polyfill: var(--polyfill);
+ --color-polyfill-foreground: var(--polyfill-foreground);
--radius-sm: calc(var(--radius) - 4px);
--radius-md: calc(var(--radius) - 2px);
--radius-lg: var(--radius);
diff --git a/crates/next-api/src/analyze.rs b/crates/next-api/src/analyze.rs
index 97d042e944a51..aaf34df05e1b4 100644
--- a/crates/next-api/src/analyze.rs
+++ b/crates/next-api/src/analyze.rs
@@ -367,6 +367,7 @@ pub async fn analyze_output_assets(output_assets: Vc) -> Result =
ResolvedVc::cell(client_assets.into_iter().collect::>());
diff --git a/crates/next-api/src/route.rs b/crates/next-api/src/route.rs
index 2dd77f52dd19e..1d3c5825392f0 100644
--- a/crates/next-api/src/route.rs
+++ b/crates/next-api/src/route.rs
@@ -9,7 +9,7 @@ use turbo_tasks::{
};
use turbopack_core::{
module_graph::{GraphEntries, ModuleGraph},
- output::OutputAssets,
+ output::{OptionOutputAsset, OutputAssets},
};
use crate::{operation::OptionEndpoint, paths::ServerPath, project::Project};
@@ -72,6 +72,10 @@ pub trait Endpoint {
}
#[turbo_tasks::function]
fn module_graphs(self: Vc) -> Vc;
+ #[turbo_tasks::function]
+ fn polyfill_asset(self: Vc) -> Vc {
+ Vc::cell(None)
+ }
}
#[derive(
diff --git a/turbopack/crates/turbopack-ecmascript/src/lib.rs b/turbopack/crates/turbopack-ecmascript/src/lib.rs
index 7f329e43e9bfb..9dd250face48e 100644
--- a/turbopack/crates/turbopack-ecmascript/src/lib.rs
+++ b/turbopack/crates/turbopack-ecmascript/src/lib.rs
@@ -24,6 +24,7 @@ mod path_visitor;
pub mod references;
pub mod runtime_functions;
pub mod side_effect_optimization;
+pub mod single_file_ecmascript_output;
pub mod source_map;
pub(crate) mod static_code;
mod swc_comments;
diff --git a/turbopack/crates/turbopack-ecmascript/src/single_file_ecmascript_output.rs b/turbopack/crates/turbopack-ecmascript/src/single_file_ecmascript_output.rs
new file mode 100644
index 0000000000000..1592cad139c74
--- /dev/null
+++ b/turbopack/crates/turbopack-ecmascript/src/single_file_ecmascript_output.rs
@@ -0,0 +1,99 @@
+use std::sync::Arc;
+
+use anyhow::Result;
+use swc_core::common::{BytePos, FileName, LineCol, SourceMap};
+use tokio::io::AsyncReadExt;
+use turbo_tasks::{ResolvedVc, Vc};
+use turbo_tasks_fs::{FileContent, FileSystemPath, rope::Rope};
+use turbopack_core::{
+ asset::{Asset, AssetContent},
+ output::{OutputAsset, OutputAssetsReference},
+ source::Source,
+ source_map::{GenerateSourceMap, OptionStringifiedSourceMap},
+};
+
+use crate::parse::generate_js_source_map;
+
+/// An EcmaScript OutputAsset composed of one file, no parsing and no references. Includes a source
+/// map to the original file.
+#[turbo_tasks::value]
+pub struct SingleFileEcmascriptOutput {
+ output_path: FileSystemPath,
+ source_path: FileSystemPath,
+ source: ResolvedVc>,
+}
+
+#[turbo_tasks::value_impl]
+impl OutputAsset for SingleFileEcmascriptOutput {
+ #[turbo_tasks::function]
+ fn path(&self) -> Vc {
+ self.output_path.clone().cell()
+ }
+}
+
+#[turbo_tasks::value_impl]
+impl OutputAssetsReference for SingleFileEcmascriptOutput {}
+
+#[turbo_tasks::value_impl]
+impl Asset for SingleFileEcmascriptOutput {
+ #[turbo_tasks::function]
+ fn content(&self) -> Vc {
+ self.source.content()
+ }
+}
+
+#[turbo_tasks::value_impl]
+impl SingleFileEcmascriptOutput {
+ #[turbo_tasks::function]
+ pub fn new(
+ output_path: FileSystemPath,
+ source_path: FileSystemPath,
+ source: ResolvedVc>,
+ ) -> Vc {
+ SingleFileEcmascriptOutput {
+ output_path,
+ source_path,
+ source,
+ }
+ .cell()
+ }
+}
+
+#[turbo_tasks::value_impl]
+impl GenerateSourceMap for SingleFileEcmascriptOutput {
+ #[turbo_tasks::function]
+ pub async fn generate_source_map(&self) -> Result> {
+ let FileContent::Content(file) = &*self.source.content().file_content().await? else {
+ return Ok(Vc::cell(None));
+ };
+
+ let file_source = {
+ let mut s = String::new();
+ file.read().read_to_string(&mut s).await?;
+ s
+ };
+
+ let mut mappings = vec![];
+ // Start from 1 because 0 is reserved for dummy spans in SWC.
+ let mut pos: u32 = 1;
+ for (index, line) in file_source.split_inclusive('\n').enumerate() {
+ mappings.push((
+ BytePos(pos),
+ LineCol {
+ line: index as u32,
+ col: 0,
+ },
+ ));
+ pos += line.len() as u32;
+ }
+
+ let sm: Arc = Default::default();
+ sm.new_source_file(
+ FileName::Custom(self.source_path.to_string()).into(),
+ file_source,
+ );
+
+ let map = generate_js_source_map(&*sm, mappings, None::<&Rope>, true, true)?;
+ Ok(Vc::cell(Some(map)))
+ }
+}