@bfra.me/workspace-analyzer 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +402 -0
- package/lib/chunk-4LSFAAZW.js +1 -0
- package/lib/chunk-JDF7DQ4V.js +27 -0
- package/lib/chunk-WOJ4C7N7.js +7122 -0
- package/lib/cli.d.ts +1 -0
- package/lib/cli.js +318 -0
- package/lib/index.d.ts +3701 -0
- package/lib/index.js +1262 -0
- package/lib/types/index.d.ts +146 -0
- package/lib/types/index.js +28 -0
- package/package.json +89 -0
- package/src/analyzers/analyzer.ts +201 -0
- package/src/analyzers/architectural-analyzer.ts +304 -0
- package/src/analyzers/build-config-analyzer.ts +334 -0
- package/src/analyzers/circular-import-analyzer.ts +463 -0
- package/src/analyzers/config-consistency-analyzer.ts +335 -0
- package/src/analyzers/dead-code-analyzer.ts +565 -0
- package/src/analyzers/duplicate-code-analyzer.ts +626 -0
- package/src/analyzers/duplicate-dependency-analyzer.ts +381 -0
- package/src/analyzers/eslint-config-analyzer.ts +281 -0
- package/src/analyzers/exports-field-analyzer.ts +324 -0
- package/src/analyzers/index.ts +388 -0
- package/src/analyzers/large-dependency-analyzer.ts +535 -0
- package/src/analyzers/package-json-analyzer.ts +349 -0
- package/src/analyzers/peer-dependency-analyzer.ts +275 -0
- package/src/analyzers/tree-shaking-analyzer.ts +623 -0
- package/src/analyzers/tsconfig-analyzer.ts +382 -0
- package/src/analyzers/unused-dependency-analyzer.ts +356 -0
- package/src/analyzers/version-alignment-analyzer.ts +308 -0
- package/src/api/analyze-workspace.ts +245 -0
- package/src/api/index.ts +11 -0
- package/src/cache/cache-manager.ts +495 -0
- package/src/cache/cache-schema.ts +247 -0
- package/src/cache/change-detector.ts +169 -0
- package/src/cache/file-hasher.ts +65 -0
- package/src/cache/index.ts +47 -0
- package/src/cli/commands/analyze.ts +240 -0
- package/src/cli/commands/index.ts +5 -0
- package/src/cli/index.ts +61 -0
- package/src/cli/types.ts +65 -0
- package/src/cli/ui.ts +213 -0
- package/src/cli.ts +9 -0
- package/src/config/defaults.ts +183 -0
- package/src/config/index.ts +81 -0
- package/src/config/loader.ts +270 -0
- package/src/config/merger.ts +229 -0
- package/src/config/schema.ts +263 -0
- package/src/core/incremental-analyzer.ts +462 -0
- package/src/core/index.ts +34 -0
- package/src/core/orchestrator.ts +416 -0
- package/src/graph/dependency-graph.ts +408 -0
- package/src/graph/index.ts +19 -0
- package/src/index.ts +417 -0
- package/src/parser/config-parser.ts +491 -0
- package/src/parser/import-extractor.ts +340 -0
- package/src/parser/index.ts +54 -0
- package/src/parser/typescript-parser.ts +95 -0
- package/src/performance/bundle-estimator.ts +444 -0
- package/src/performance/index.ts +27 -0
- package/src/reporters/console-reporter.ts +355 -0
- package/src/reporters/index.ts +49 -0
- package/src/reporters/json-reporter.ts +273 -0
- package/src/reporters/markdown-reporter.ts +349 -0
- package/src/reporters/reporter.ts +399 -0
- package/src/rules/builtin-rules.ts +709 -0
- package/src/rules/index.ts +52 -0
- package/src/rules/rule-engine.ts +409 -0
- package/src/scanner/index.ts +18 -0
- package/src/scanner/workspace-scanner.ts +403 -0
- package/src/types/index.ts +176 -0
- package/src/types/result.ts +19 -0
- package/src/utils/index.ts +7 -0
- package/src/utils/pattern-matcher.ts +48 -0
|
@@ -0,0 +1,444 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bundle size estimator for identifying large module contributors.
|
|
3
|
+
*
|
|
4
|
+
* Analyzes source files and dependencies to estimate bundle size impact,
|
|
5
|
+
* helping identify optimization opportunities for tree-shaking and code splitting.
|
|
6
|
+
*
|
|
7
|
+
* This is a heuristic estimator - actual bundle sizes depend on bundler configuration,
|
|
8
|
+
* tree-shaking effectiveness, and minification.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import type {ExtractedImport, ImportExtractionResult} from '../parser/import-extractor'
|
|
12
|
+
import type {WorkspacePackage} from '../scanner/workspace-scanner'
|
|
13
|
+
|
|
14
|
+
import fs from 'node:fs/promises'
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Estimated size information for a module or package.
|
|
18
|
+
*/
|
|
19
|
+
export interface BundleSizeEstimate {
|
|
20
|
+
/** Source file path or package name */
|
|
21
|
+
readonly identifier: string
|
|
22
|
+
/** Raw source size in bytes (unminified) */
|
|
23
|
+
readonly sourceSize: number
|
|
24
|
+
/** Estimated minified size in bytes (heuristic: ~60% of source) */
|
|
25
|
+
readonly estimatedMinifiedSize: number
|
|
26
|
+
/** Estimated gzipped size in bytes (heuristic: ~30% of minified) */
|
|
27
|
+
readonly estimatedGzippedSize: number
|
|
28
|
+
/** Number of imports this module brings in */
|
|
29
|
+
readonly importCount: number
|
|
30
|
+
/** Number of exports this module provides */
|
|
31
|
+
readonly exportCount: number
|
|
32
|
+
/** Whether this is a direct or transitive dependency */
|
|
33
|
+
readonly isTransitive: boolean
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Aggregated bundle size statistics for a package.
|
|
38
|
+
*/
|
|
39
|
+
export interface PackageBundleStats {
|
|
40
|
+
/** Package name */
|
|
41
|
+
readonly packageName: string
|
|
42
|
+
/** Total source size of all modules */
|
|
43
|
+
readonly totalSourceSize: number
|
|
44
|
+
/** Estimated total minified size */
|
|
45
|
+
readonly totalMinifiedSize: number
|
|
46
|
+
/** Estimated total gzipped size */
|
|
47
|
+
readonly totalGzippedSize: number
|
|
48
|
+
/** Number of source files */
|
|
49
|
+
readonly fileCount: number
|
|
50
|
+
/** Individual file estimates */
|
|
51
|
+
readonly files: readonly BundleSizeEstimate[]
|
|
52
|
+
/** External dependency size estimates */
|
|
53
|
+
readonly dependencies: readonly DependencySizeEstimate[]
|
|
54
|
+
/** Top contributors by size */
|
|
55
|
+
readonly topContributors: readonly BundleSizeEstimate[]
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Size estimate for an external dependency.
|
|
60
|
+
*/
|
|
61
|
+
export interface DependencySizeEstimate {
|
|
62
|
+
/** Package name */
|
|
63
|
+
readonly packageName: string
|
|
64
|
+
/** Estimated size (if known from registry data) */
|
|
65
|
+
readonly estimatedSize?: number
|
|
66
|
+
/** Whether size data is available */
|
|
67
|
+
readonly sizeKnown: boolean
|
|
68
|
+
/** Number of times imported */
|
|
69
|
+
readonly importCount: number
|
|
70
|
+
/** Import locations */
|
|
71
|
+
readonly locations: readonly string[]
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Options for bundle size estimation.
|
|
76
|
+
*/
|
|
77
|
+
export interface BundleEstimatorOptions {
|
|
78
|
+
/** Include node_modules size estimates */
|
|
79
|
+
readonly includeNodeModules?: boolean
|
|
80
|
+
/** Maximum files to analyze (for performance) */
|
|
81
|
+
readonly maxFiles?: number
|
|
82
|
+
/** Minification ratio estimate (0-1) */
|
|
83
|
+
readonly minificationRatio?: number
|
|
84
|
+
/** Gzip ratio estimate (0-1) */
|
|
85
|
+
readonly gzipRatio?: number
|
|
86
|
+
/** Size threshold for "large file" warnings (bytes) */
|
|
87
|
+
readonly largeFileThreshold?: number
|
|
88
|
+
/** Size threshold for "large dependency" warnings (bytes) */
|
|
89
|
+
readonly largeDependencyThreshold?: number
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const DEFAULT_OPTIONS: Required<BundleEstimatorOptions> = {
|
|
93
|
+
includeNodeModules: false,
|
|
94
|
+
maxFiles: 10000,
|
|
95
|
+
minificationRatio: 0.6,
|
|
96
|
+
gzipRatio: 0.3,
|
|
97
|
+
largeFileThreshold: 50000,
|
|
98
|
+
largeDependencyThreshold: 100000,
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Known large package sizes (rough estimates in KB, gzipped)
|
|
102
|
+
const KNOWN_PACKAGE_SIZES: Readonly<Record<string, number>> = {
|
|
103
|
+
lodash: 71,
|
|
104
|
+
'lodash-es': 71,
|
|
105
|
+
moment: 67,
|
|
106
|
+
'moment-timezone': 95,
|
|
107
|
+
rxjs: 40,
|
|
108
|
+
'@angular/core': 90,
|
|
109
|
+
'@angular/common': 45,
|
|
110
|
+
react: 6,
|
|
111
|
+
'react-dom': 42,
|
|
112
|
+
vue: 34,
|
|
113
|
+
d3: 80,
|
|
114
|
+
'chart.js': 65,
|
|
115
|
+
three: 150,
|
|
116
|
+
'@mui/material': 120,
|
|
117
|
+
antd: 200,
|
|
118
|
+
'date-fns': 25,
|
|
119
|
+
dayjs: 3,
|
|
120
|
+
axios: 13,
|
|
121
|
+
zod: 12,
|
|
122
|
+
yup: 22,
|
|
123
|
+
'class-validator': 15,
|
|
124
|
+
typeorm: 180,
|
|
125
|
+
prisma: 40,
|
|
126
|
+
'@prisma/client': 40,
|
|
127
|
+
express: 30,
|
|
128
|
+
fastify: 25,
|
|
129
|
+
'ts-morph': 150,
|
|
130
|
+
typescript: 150,
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Estimates bundle size for source files in a package.
|
|
135
|
+
*/
|
|
136
|
+
export async function estimatePackageBundleSize(
|
|
137
|
+
pkg: WorkspacePackage,
|
|
138
|
+
importResults: readonly ImportExtractionResult[],
|
|
139
|
+
options?: BundleEstimatorOptions,
|
|
140
|
+
): Promise<PackageBundleStats> {
|
|
141
|
+
const opts = {...DEFAULT_OPTIONS, ...options}
|
|
142
|
+
|
|
143
|
+
const fileEstimates: BundleSizeEstimate[] = []
|
|
144
|
+
let totalSourceSize = 0
|
|
145
|
+
let totalMinifiedSize = 0
|
|
146
|
+
let totalGzippedSize = 0
|
|
147
|
+
|
|
148
|
+
const filesToAnalyze = pkg.sourceFiles.slice(0, opts.maxFiles)
|
|
149
|
+
|
|
150
|
+
for (const filePath of filesToAnalyze) {
|
|
151
|
+
try {
|
|
152
|
+
const stats = await fs.stat(filePath)
|
|
153
|
+
const sourceSize = stats.size
|
|
154
|
+
|
|
155
|
+
const fileImports = importResults.find(r => r.filePath === filePath)
|
|
156
|
+
const importCount = fileImports?.imports.length ?? 0
|
|
157
|
+
|
|
158
|
+
const estimate = createSizeEstimate(
|
|
159
|
+
filePath,
|
|
160
|
+
sourceSize,
|
|
161
|
+
importCount,
|
|
162
|
+
0,
|
|
163
|
+
false,
|
|
164
|
+
opts.minificationRatio,
|
|
165
|
+
opts.gzipRatio,
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
fileEstimates.push(estimate)
|
|
169
|
+
totalSourceSize += sourceSize
|
|
170
|
+
totalMinifiedSize += estimate.estimatedMinifiedSize
|
|
171
|
+
totalGzippedSize += estimate.estimatedGzippedSize
|
|
172
|
+
} catch {
|
|
173
|
+
// File may not exist or be inaccessible
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
const dependencyEstimates = collectDependencyEstimates(importResults, opts)
|
|
178
|
+
|
|
179
|
+
const topContributors = [...fileEstimates]
|
|
180
|
+
.sort((a, b) => b.sourceSize - a.sourceSize)
|
|
181
|
+
.slice(0, 10)
|
|
182
|
+
|
|
183
|
+
return {
|
|
184
|
+
packageName: pkg.name,
|
|
185
|
+
totalSourceSize,
|
|
186
|
+
totalMinifiedSize,
|
|
187
|
+
totalGzippedSize,
|
|
188
|
+
fileCount: fileEstimates.length,
|
|
189
|
+
files: fileEstimates,
|
|
190
|
+
dependencies: dependencyEstimates,
|
|
191
|
+
topContributors,
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Estimates the size of a single source file.
|
|
197
|
+
*/
|
|
198
|
+
export async function estimateFileSize(
|
|
199
|
+
filePath: string,
|
|
200
|
+
options?: BundleEstimatorOptions,
|
|
201
|
+
): Promise<BundleSizeEstimate | null> {
|
|
202
|
+
const opts = {...DEFAULT_OPTIONS, ...options}
|
|
203
|
+
|
|
204
|
+
try {
|
|
205
|
+
const stats = await fs.stat(filePath)
|
|
206
|
+
return createSizeEstimate(
|
|
207
|
+
filePath,
|
|
208
|
+
stats.size,
|
|
209
|
+
0,
|
|
210
|
+
0,
|
|
211
|
+
false,
|
|
212
|
+
opts.minificationRatio,
|
|
213
|
+
opts.gzipRatio,
|
|
214
|
+
)
|
|
215
|
+
} catch {
|
|
216
|
+
return null
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Estimates the size contribution of an external dependency.
|
|
222
|
+
*/
|
|
223
|
+
export function estimateDependencySize(packageName: string): DependencySizeEstimate {
|
|
224
|
+
const baseName = getBasePackageName(packageName)
|
|
225
|
+
const knownSize = KNOWN_PACKAGE_SIZES[baseName]
|
|
226
|
+
|
|
227
|
+
if (knownSize === undefined) {
|
|
228
|
+
return {
|
|
229
|
+
packageName,
|
|
230
|
+
estimatedSize: undefined,
|
|
231
|
+
sizeKnown: false,
|
|
232
|
+
importCount: 0,
|
|
233
|
+
locations: [],
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
return {
|
|
238
|
+
packageName,
|
|
239
|
+
estimatedSize: knownSize * 1024,
|
|
240
|
+
sizeKnown: true,
|
|
241
|
+
importCount: 0,
|
|
242
|
+
locations: [],
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* Identifies files that exceed the large file threshold.
|
|
248
|
+
*/
|
|
249
|
+
export function findLargeFiles(
|
|
250
|
+
stats: PackageBundleStats,
|
|
251
|
+
threshold?: number,
|
|
252
|
+
): readonly BundleSizeEstimate[] {
|
|
253
|
+
const actualThreshold = threshold ?? DEFAULT_OPTIONS.largeFileThreshold
|
|
254
|
+
return stats.files.filter(file => file.sourceSize > actualThreshold)
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* Identifies dependencies that exceed the large dependency threshold.
|
|
259
|
+
*/
|
|
260
|
+
export function findLargeDependencies(
|
|
261
|
+
stats: PackageBundleStats,
|
|
262
|
+
threshold?: number,
|
|
263
|
+
): readonly DependencySizeEstimate[] {
|
|
264
|
+
const actualThreshold = threshold ?? DEFAULT_OPTIONS.largeDependencyThreshold
|
|
265
|
+
return stats.dependencies.filter(
|
|
266
|
+
dep => dep.sizeKnown && dep.estimatedSize !== undefined && dep.estimatedSize > actualThreshold,
|
|
267
|
+
)
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* Calculates the estimated tree-shaking savings for a file.
|
|
272
|
+
*
|
|
273
|
+
* Files that only use a subset of exports from large modules
|
|
274
|
+
* can benefit significantly from tree-shaking.
|
|
275
|
+
*/
|
|
276
|
+
export function estimateTreeShakingSavings(
|
|
277
|
+
imports: readonly ExtractedImport[],
|
|
278
|
+
): TreeShakingSavingsEstimate {
|
|
279
|
+
let potentialSavings = 0
|
|
280
|
+
const optimizableImports: OptimizableImport[] = []
|
|
281
|
+
|
|
282
|
+
for (const imp of imports) {
|
|
283
|
+
if (imp.namespaceImport !== undefined) {
|
|
284
|
+
// Namespace imports (import * as X) prevent tree-shaking
|
|
285
|
+
const baseName = getBasePackageName(imp.moduleSpecifier)
|
|
286
|
+
const knownSize = KNOWN_PACKAGE_SIZES[baseName]
|
|
287
|
+
|
|
288
|
+
if (knownSize !== undefined) {
|
|
289
|
+
// Estimate potential savings (heuristic: 50% of package)
|
|
290
|
+
const savings = Math.floor(knownSize * 1024 * 0.5)
|
|
291
|
+
potentialSavings += savings
|
|
292
|
+
|
|
293
|
+
optimizableImports.push({
|
|
294
|
+
moduleSpecifier: imp.moduleSpecifier,
|
|
295
|
+
currentImportStyle: 'namespace',
|
|
296
|
+
suggestedImportStyle: 'named',
|
|
297
|
+
estimatedSavings: savings,
|
|
298
|
+
line: imp.line,
|
|
299
|
+
column: imp.column,
|
|
300
|
+
})
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
if (imp.defaultImport !== undefined && imp.importedNames === undefined) {
|
|
305
|
+
// Default-only imports from packages with named exports
|
|
306
|
+
const baseName = getBasePackageName(imp.moduleSpecifier)
|
|
307
|
+
const knownSize = KNOWN_PACKAGE_SIZES[baseName]
|
|
308
|
+
|
|
309
|
+
if (knownSize !== undefined && !imp.isRelative) {
|
|
310
|
+
const savings = Math.floor(knownSize * 1024 * 0.3)
|
|
311
|
+
potentialSavings += savings
|
|
312
|
+
|
|
313
|
+
optimizableImports.push({
|
|
314
|
+
moduleSpecifier: imp.moduleSpecifier,
|
|
315
|
+
currentImportStyle: 'default',
|
|
316
|
+
suggestedImportStyle: 'named',
|
|
317
|
+
estimatedSavings: savings,
|
|
318
|
+
line: imp.line,
|
|
319
|
+
column: imp.column,
|
|
320
|
+
})
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
return {
|
|
326
|
+
potentialSavings,
|
|
327
|
+
optimizableImports,
|
|
328
|
+
hasPotentialOptimizations: optimizableImports.length > 0,
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
/**
|
|
333
|
+
* Estimate of potential tree-shaking savings.
|
|
334
|
+
*/
|
|
335
|
+
export interface TreeShakingSavingsEstimate {
|
|
336
|
+
/** Total potential bytes saved */
|
|
337
|
+
readonly potentialSavings: number
|
|
338
|
+
/** Imports that could be optimized */
|
|
339
|
+
readonly optimizableImports: readonly OptimizableImport[]
|
|
340
|
+
/** Whether any optimizations are available */
|
|
341
|
+
readonly hasPotentialOptimizations: boolean
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
/**
|
|
345
|
+
* An import that could be optimized for better tree-shaking.
|
|
346
|
+
*/
|
|
347
|
+
export interface OptimizableImport {
|
|
348
|
+
/** The module specifier */
|
|
349
|
+
readonly moduleSpecifier: string
|
|
350
|
+
/** Current import style */
|
|
351
|
+
readonly currentImportStyle: 'namespace' | 'default' | 'side-effect'
|
|
352
|
+
/** Suggested import style */
|
|
353
|
+
readonly suggestedImportStyle: 'named' | 'dynamic'
|
|
354
|
+
/** Estimated bytes saved */
|
|
355
|
+
readonly estimatedSavings: number
|
|
356
|
+
/** Line number */
|
|
357
|
+
readonly line: number
|
|
358
|
+
/** Column number */
|
|
359
|
+
readonly column: number
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
function createSizeEstimate(
|
|
363
|
+
identifier: string,
|
|
364
|
+
sourceSize: number,
|
|
365
|
+
importCount: number,
|
|
366
|
+
exportCount: number,
|
|
367
|
+
isTransitive: boolean,
|
|
368
|
+
minificationRatio: number,
|
|
369
|
+
gzipRatio: number,
|
|
370
|
+
): BundleSizeEstimate {
|
|
371
|
+
const estimatedMinifiedSize = Math.floor(sourceSize * minificationRatio)
|
|
372
|
+
const estimatedGzippedSize = Math.floor(estimatedMinifiedSize * gzipRatio)
|
|
373
|
+
|
|
374
|
+
return {
|
|
375
|
+
identifier,
|
|
376
|
+
sourceSize,
|
|
377
|
+
estimatedMinifiedSize,
|
|
378
|
+
estimatedGzippedSize,
|
|
379
|
+
importCount,
|
|
380
|
+
exportCount,
|
|
381
|
+
isTransitive,
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
function collectDependencyEstimates(
|
|
386
|
+
importResults: readonly ImportExtractionResult[],
|
|
387
|
+
_options: Required<BundleEstimatorOptions>,
|
|
388
|
+
): DependencySizeEstimate[] {
|
|
389
|
+
const depMap = new Map<
|
|
390
|
+
string,
|
|
391
|
+
{estimatedSize?: number; sizeKnown: boolean; locations: string[]}
|
|
392
|
+
>()
|
|
393
|
+
|
|
394
|
+
for (const result of importResults) {
|
|
395
|
+
for (const dep of result.externalDependencies) {
|
|
396
|
+
const baseName = getBasePackageName(dep)
|
|
397
|
+
const existing = depMap.get(baseName)
|
|
398
|
+
|
|
399
|
+
if (existing === undefined) {
|
|
400
|
+
const knownSize = KNOWN_PACKAGE_SIZES[baseName]
|
|
401
|
+
const sizeKnown = knownSize !== undefined
|
|
402
|
+
depMap.set(baseName, {
|
|
403
|
+
estimatedSize: sizeKnown ? knownSize * 1024 : undefined,
|
|
404
|
+
sizeKnown,
|
|
405
|
+
locations: [result.filePath],
|
|
406
|
+
})
|
|
407
|
+
} else {
|
|
408
|
+
existing.locations.push(result.filePath)
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
return Array.from(depMap.entries()).map(([packageName, data]) => ({
|
|
414
|
+
packageName,
|
|
415
|
+
estimatedSize: data.estimatedSize,
|
|
416
|
+
sizeKnown: data.sizeKnown,
|
|
417
|
+
importCount: data.locations.length,
|
|
418
|
+
locations: data.locations,
|
|
419
|
+
}))
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
function getBasePackageName(specifier: string): string {
|
|
423
|
+
if (specifier.startsWith('@')) {
|
|
424
|
+
const parts = specifier.split('/')
|
|
425
|
+
if (parts.length >= 2) {
|
|
426
|
+
return `${parts[0]}/${parts[1]}`
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
return specifier.split('/')[0] ?? specifier
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
/**
|
|
433
|
+
* Formats a byte size as a human-readable string.
|
|
434
|
+
*/
|
|
435
|
+
export function formatBytes(bytes: number): string {
|
|
436
|
+
if (bytes === 0) return '0 B'
|
|
437
|
+
|
|
438
|
+
const units = ['B', 'KB', 'MB', 'GB']
|
|
439
|
+
const k = 1024
|
|
440
|
+
const i = Math.floor(Math.log(bytes) / Math.log(k))
|
|
441
|
+
const size = bytes / k ** i
|
|
442
|
+
|
|
443
|
+
return `${size.toFixed(i === 0 ? 0 : 1)} ${units[i]}`
|
|
444
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Performance analysis utilities for bundle size estimation and optimization detection.
|
|
3
|
+
*
|
|
4
|
+
* Provides tools for identifying performance optimization opportunities including:
|
|
5
|
+
* - Bundle size estimation and large file detection
|
|
6
|
+
* - Tree-shaking blocker identification
|
|
7
|
+
* - Dependency size analysis
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
export {
|
|
11
|
+
estimateDependencySize,
|
|
12
|
+
estimateFileSize,
|
|
13
|
+
estimatePackageBundleSize,
|
|
14
|
+
estimateTreeShakingSavings,
|
|
15
|
+
findLargeDependencies,
|
|
16
|
+
findLargeFiles,
|
|
17
|
+
formatBytes,
|
|
18
|
+
} from './bundle-estimator'
|
|
19
|
+
|
|
20
|
+
export type {
|
|
21
|
+
BundleEstimatorOptions,
|
|
22
|
+
BundleSizeEstimate,
|
|
23
|
+
DependencySizeEstimate,
|
|
24
|
+
OptimizableImport,
|
|
25
|
+
PackageBundleStats,
|
|
26
|
+
TreeShakingSavingsEstimate,
|
|
27
|
+
} from './bundle-estimator'
|