1+ const  {  resolve }  =  require ( 'path' ) ; 
2+ const  fs  =  require ( 'fs' ) ; 
3+ 
4+ console . log ( 'Starting bundle analysis...' ) ; 
5+ 
6+ // Find the most recent Next.js build manifest 
7+ const  buildManifestPath  =  resolve ( '.next' ,  'build-manifest.json' ) ; 
8+ 
9+ console . log ( `Checking for build manifest at: ${ buildManifestPath }  ) ; 
10+ 
11+ if  ( ! fs . existsSync ( buildManifestPath ) )  { 
12+   console . error ( 'Build manifest not found. Run `npm run build` first.' ) ; 
13+   process . exit ( 1 ) ; 
14+ } 
15+ 
16+ console . log ( 'Found build manifest. Loading...' ) ; 
17+ const  buildManifest  =  require ( buildManifestPath ) ; 
18+ 
19+ // Extract all JavaScript chunks 
20+ const  jsFiles  =  new  Set ( ) ; 
21+ 
22+ // Process polyfillFiles, devFiles, etc. 
23+ const  manifestCategories  =  [ 
24+   'polyfillFiles' , 
25+   'devFiles' , 
26+   'ampDevFiles' , 
27+   'lowPriorityFiles' , 
28+   'rootMainFiles' 
29+ ] ; 
30+ 
31+ manifestCategories . forEach ( category  =>  { 
32+   if  ( buildManifest [ category ]  &&  Array . isArray ( buildManifest [ category ] ) )  { 
33+     buildManifest [ category ] . forEach ( file  =>  { 
34+       if  ( file . endsWith ( '.js' ) )  { 
35+         jsFiles . add ( file ) ; 
36+       } 
37+     } ) ; 
38+   } 
39+ } ) ; 
40+ 
41+ // Process pages 
42+ if  ( buildManifest . pages )  { 
43+   Object . values ( buildManifest . pages ) . forEach ( filesArray  =>  { 
44+     if  ( Array . isArray ( filesArray ) )  { 
45+       filesArray . forEach ( file  =>  { 
46+         if  ( file . endsWith ( '.js' ) )  { 
47+           jsFiles . add ( file ) ; 
48+         } 
49+       } ) ; 
50+     } 
51+   } ) ; 
52+ } 
53+ 
54+ console . log ( `Found ${ jsFiles . size }  ) ; 
55+ 
56+ // Create stats for files 
57+ const  fileStats  =  Array . from ( jsFiles ) . map ( file  =>  { 
58+   const  fullPath  =  resolve ( '.next' ,  file ) ; 
59+   console . log ( `Checking file size for: ${ fullPath }  ) ; 
60+   
61+   let  size  =  0 ; 
62+   try  { 
63+     const  stat  =  fs . statSync ( fullPath ) ; 
64+     size  =  stat . size ; 
65+     console . log ( `File size: ${ size }  ) ; 
66+   }  catch  ( e )  { 
67+     console . warn ( `Couldn't find file: ${ fullPath }  ,  e ) ; 
68+   } 
69+   
70+   // Extract chunk name from path 
71+   const  name  =  file . split ( '/' ) . pop ( ) ; 
72+   
73+   return  { 
74+     path : file , 
75+     name : name , 
76+     size : size , 
77+     sizeKB : ( size  /  1024 ) . toFixed ( 2 ) 
78+   } ; 
79+ } ) ; 
80+ 
81+ // Sort by size, largest first 
82+ fileStats . sort ( ( a ,  b )  =>  b . size  -  a . size ) ; 
83+ 
84+ // Group by directories 
85+ const  groupedStats  =  { } ; 
86+ fileStats . forEach ( stat  =>  { 
87+   const  parts  =  stat . path . split ( '/' ) ; 
88+   const  dir  =  parts . length  >  2  ? parts . slice ( 0 ,  - 1 ) . join ( '/' )  : 'root' ; 
89+   
90+   if  ( ! groupedStats [ dir ] )  { 
91+     groupedStats [ dir ]  =  [ ] ; 
92+   } 
93+   
94+   groupedStats [ dir ] . push ( stat ) ; 
95+ } ) ; 
96+ 
97+ // Calculate total size 
98+ const  totalSize  =  fileStats . reduce ( ( sum ,  file )  =>  sum  +  file . size ,  0 ) ; 
99+ const  totalSizeKB  =  ( totalSize  /  1024 ) . toFixed ( 2 ) ; 
100+ const  totalSizeMB  =  ( totalSize  /  ( 1024  *  1024 ) ) . toFixed ( 2 ) ; 
101+ 
102+ // Generate HTML report 
103+ const  htmlReport  =  ` 
104+ <!DOCTYPE html> 
105+ <html lang="en"> 
106+ <head> 
107+   <meta charset="UTF-8"> 
108+   <meta name="viewport" content="width=device-width, initial-scale=1.0"> 
109+   <title>Next.js Bundle Analysis</title> 
110+   <style> 
111+     body { 
112+       font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif; 
113+       max-width: 1200px; 
114+       margin: 0 auto; 
115+       padding: 20px; 
116+       color: #333; 
117+     } 
118+     h1, h2 { 
119+       border-bottom: 1px solid #eee; 
120+       padding-bottom: 10px; 
121+     } 
122+     table { 
123+       width: 100%; 
124+       border-collapse: collapse; 
125+       margin-bottom: 30px; 
126+     } 
127+     th, td { 
128+       text-align: left; 
129+       padding: 12px; 
130+       border-bottom: 1px solid #ddd; 
131+     } 
132+     th { 
133+       background-color: #f2f2f2; 
134+     } 
135+     tr:hover { 
136+       background-color: #f5f5f5; 
137+     } 
138+     .group-header { 
139+       margin-top: 30px; 
140+       font-weight: bold; 
141+       background-color: #e6e6e6; 
142+       padding: 10px; 
143+     } 
144+     .bar { 
145+       height: 20px; 
146+       background-color: #4CAF50; 
147+       display: inline-block; 
148+     } 
149+     .summary { 
150+       background-color: #f8f9fa; 
151+       padding: 15px; 
152+       border-radius: 5px; 
153+       margin-bottom: 20px; 
154+       border-left: 5px solid #4CAF50; 
155+     } 
156+   </style> 
157+ </head> 
158+ <body> 
159+   <h1>Next.js Bundle Analysis</h1> 
160+    
161+   <div class="summary"> 
162+     <h2>Summary</h2> 
163+     <p>Total JavaScript size: ${ totalSizeKB } ${ totalSizeMB }  
164+     <p>Total chunks: ${ fileStats . length }  
165+   </div> 
166+    
167+   <h2>All Chunks by Size</h2> 
168+   <table> 
169+     <thead> 
170+       <tr> 
171+         <th>File</th> 
172+         <th>Size (KB)</th> 
173+         <th>Percentage</th> 
174+       </tr> 
175+     </thead> 
176+     <tbody> 
177+       ${ fileStats . map ( file  =>  {  
178+         const  percentage  =  ( ( file . size  /  totalSize )  *  100 ) . toFixed ( 2 ) ;  
179+         const  barWidth  =  Math . max ( 1 ,  percentage ) ;  
180+         return  `  
181+           <tr> 
182+             <td>${ file . path }  
183+             <td>${ file . sizeKB }  
184+             <td> 
185+               <div class="bar" style="width: ${ barWidth }  
186+               ${ percentage }  
187+             </td> 
188+           </tr> 
189+         ` ; 
190+       } ) . join ( '' ) }  
191+     </tbody> 
192+   </table> 
193+    
194+   <h2>Chunks by Directory</h2> 
195+   ${ Object . entries ( groupedStats ) . map ( ( [ dir ,  files ] )  =>  {  
196+     const  dirSize  =  files . reduce ( ( sum ,  file )  =>  sum  +  file . size ,  0 ) ;  
197+     const  dirSizeKB  =  ( dirSize  /  1024 ) . toFixed ( 2 ) ;  
198+     const  dirPercentage  =  ( ( dirSize  /  totalSize )  *  100 ) . toFixed ( 2 ) ;  
199+      
200+     return  `  
201+       <div class="group-header">${ dir } ${ dirSizeKB } ${ dirPercentage }  
202+       <table> 
203+         <thead> 
204+           <tr> 
205+             <th>File</th> 
206+             <th>Size (KB)</th> 
207+             <th>Percentage</th> 
208+           </tr> 
209+         </thead> 
210+         <tbody> 
211+           ${ files . map ( file  =>  {  
212+             const  percentage  =  ( ( file . size  /  dirSize )  *  100 ) . toFixed ( 2 ) ;  
213+             const  barWidth  =  Math . max ( 1 ,  percentage ) ;  
214+             return  `  
215+               <tr> 
216+                 <td>${ file . name }  
217+                 <td>${ file . sizeKB }  
218+                 <td> 
219+                   <div class="bar" style="width: ${ barWidth }  
220+                   ${ percentage }  
221+                 </td> 
222+               </tr> 
223+             ` ; 
224+           } ) . join ( '' ) }  
225+         </tbody> 
226+       </table> 
227+     ` ; 
228+   } ) . join ( '' ) }  
229+ </body> 
230+ </html> 
231+ ` ; 
232+ 
233+ // Write HTML report 
234+ const  reportPath  =  'bundle-report.html' ; 
235+ fs . writeFileSync ( reportPath ,  htmlReport ) ; 
236+ 
237+ console . log ( `Bundle analysis report generated: ${ reportPath }  ) ; 
238+ console . log ( 'Open this file in your browser to view the report.' ) 
0 commit comments