@node-cli/bundlecheck 1.4.1 → 1.5.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 CHANGED
@@ -11,7 +11,7 @@ A CLI tool to check the bundle size of npm packages, similar to [bundlephobia.co
11
11
  - Interactive version selection with `--versions` flag
12
12
  - Bundle size trend analysis with `--trend` flag (bar graph across versions)
13
13
  - Support for checking specific exports (tree-shaking)
14
- - Automatic externalization of React and React-DOM
14
+ - Smart externalization of React and React-DOM (only when declared as dependencies)
15
15
  - Raw and gzip sizes with configurable compression level
16
16
  - **Platform support**: target `browser` (default) or `node` with smart auto-detection
17
17
  - Custom npm registry support (for private registries)
@@ -122,7 +122,7 @@ bundlecheck @versini/ui-panel@1.0.0
122
122
  # Check specific exports from @mantine/core
123
123
  bundlecheck @mantine/core "ScrollArea,Button"
124
124
 
125
- # Check react itself (without marking it as external)
125
+ # Check react with all dependencies bundled (no externals)
126
126
  bundlecheck react -n
127
127
 
128
128
  # Add vue and svelte as additional externals
@@ -166,7 +166,7 @@ npm install @node-cli/bundlecheck
166
166
  ### Basic Usage
167
167
 
168
168
  ```js
169
- import { getBundleStats, getBundleTrend, getPackageVersions } from "@node-cli/bundlecheck";
169
+ import { getBundleStats, getBundleTrend, getPackageVersions, getPackageExports } from "@node-cli/bundlecheck";
170
170
 
171
171
  // Get bundle stats for a single package
172
172
  const stats = await getBundleStats({
@@ -181,7 +181,7 @@ console.log(stats);
181
181
  // rawSizeFormatted: "229.07 kB",
182
182
  // gzipSizeFormatted: "44.61 kB",
183
183
  // exports: [],
184
- // externals: ["react", "react-dom"],
184
+ // externals: ["react", "react-dom"], // Only present because @mantine/core has these as peerDependencies
185
185
  // dependencies: ["@floating-ui/react", ...],
186
186
  // platform: "browser",
187
187
  // gzipLevel: 5,
@@ -226,6 +226,13 @@ const versions = await getPackageVersions({
226
226
  });
227
227
  console.log(versions.tags.latest); // "7.0.0"
228
228
  console.log(versions.versions.slice(0, 5)); // ["7.0.0", "6.0.21", "6.0.20", ...]
229
+
230
+ // Get named exports from a package
231
+ const exports = await getPackageExports({
232
+ package: "date-fns@3.6.0",
233
+ });
234
+ console.log(`Found ${exports.count} exports`);
235
+ console.log(exports.exports.slice(0, 5).map(e => e.name)); // ["add", "addBusinessDays", "addDays", ...]
229
236
  ```
230
237
 
231
238
  ### API Reference
@@ -241,7 +248,7 @@ Get bundle size statistics for a single package.
241
248
  | `package` | `string` | (required) | Package name with optional version (e.g., `lodash@4.17.0`) |
242
249
  | `exports` | `string[]` | `undefined`| Specific exports to measure (tree-shaking) |
243
250
  | `external` | `string[]` | `undefined`| Additional packages to mark as external |
244
- | `noExternal` | `boolean` | `false` | Bundle everything including default externals |
251
+ | `noExternal` | `boolean` | `false` | Bundle everything (no externals, even react/react-dom) |
245
252
  | `gzipLevel` | `number` | `5` | Gzip compression level (1-9) |
246
253
  | `registry` | `string` | `undefined`| Custom npm registry URL |
247
254
  | `platform` | `"browser" \| "node" \| "auto"` | `"auto"` | Target platform |
@@ -263,6 +270,7 @@ type BundleStats = {
263
270
  rawSizeFormatted: string; // Human-readable (e.g., "45.2 kB")
264
271
  gzipSizeFormatted: string | null;
265
272
  fromCache: boolean; // Whether result was from cache
273
+ namedExportCount: number; // Total named exports in package
266
274
  };
267
275
  ```
268
276
 
@@ -333,6 +341,47 @@ type PackageVersions = {
333
341
  };
334
342
  ```
335
343
 
344
+ #### `getPackageExports(options)`
345
+
346
+ Get the named exports of an npm package by analyzing its TypeScript declarations.
347
+
348
+ **Options:**
349
+
350
+ | Option | Type | Default | Description |
351
+ | ---------- | -------- | ---------- | ------------------------------------------------ |
352
+ | `package` | `string` | (required) | Package name with optional version |
353
+ | `registry` | `string` | `undefined`| Custom npm registry URL |
354
+
355
+ **Returns:** `Promise<PackageExports>`
356
+
357
+ ```ts
358
+ type PackageExports = {
359
+ packageName: string; // Package name
360
+ packageVersion: string; // Resolved version
361
+ exports: PackageExport[]; // All named exports (including types)
362
+ count: number; // Total count (including types)
363
+ runtimeExports: PackageExport[]; // Runtime exports only (no types/interfaces)
364
+ runtimeCount: number; // Count of runtime exports
365
+ };
366
+
367
+ type PackageExport = {
368
+ name: string; // Export name (e.g., "Button")
369
+ kind: "function" | "class" | "const" | "type" | "interface" | "enum" | "unknown";
370
+ };
371
+ ```
372
+
373
+ **Example:**
374
+
375
+ ```js
376
+ const result = await getPackageExports({
377
+ package: "@mantine/core",
378
+ });
379
+
380
+ console.log(result.count); // 1056 (all exports including types)
381
+ console.log(result.runtimeCount); // 365 (only importable exports)
382
+ console.log(result.runtimeExports[0]); // { name: "Accordion", kind: "unknown" }
383
+ ```
384
+
336
385
  ### Utility Functions
337
386
 
338
387
  ```js
@@ -386,9 +435,11 @@ bundlecheck fastify -p server # "server" is an alias for "node"
386
435
 
387
436
  ## Default Externals
388
437
 
389
- By default, `react` and `react-dom` are marked as external (not included in the bundle size) since most React-based packages expect these as peer dependencies. This matches how these packages would typically be used in a real application.
438
+ When a package declares `react` or `react-dom` in its `dependencies` or `peerDependencies`, they are automatically marked as external (not included in the bundle size). This matches how these packages would typically be used in a real application where React is provided by the host application.
439
+
440
+ For packages that don't depend on React, these are not automatically externalized.
390
441
 
391
- To include React/React-DOM in the bundle size calculation, use the `--no-external` flag.
442
+ To include all dependencies (including React when present) in the bundle size calculation, use the `--no-external` flag.
392
443
 
393
444
  ## Custom Registry
394
445
 
@@ -21,9 +21,18 @@ const log = new Logger({
21
21
  const green = kleur.green;
22
22
  const platformLabel = result.platform === "node" ? "node" : "browser";
23
23
  const platformNote = isAutoDetected ? " (auto-detected)" : "";
24
+ // Format exports display.
25
+ let exportsDisplay;
26
+ if (result.exports.length > 0) {
27
+ exportsDisplay = `{ ${result.exports.join(", ")} }`;
28
+ } else if (result.namedExportCount > 0) {
29
+ exportsDisplay = `${result.namedExportCount} named exports (entire package)`;
30
+ } else {
31
+ exportsDisplay = "* (entire package)";
32
+ }
24
33
  log.printBox([
25
34
  `${blue("Package:")} ${result.packageName} (${blue("version:")} ${result.packageVersion})`,
26
- result.exports.length > 0 ? `${blue("Exports:")} { ${result.exports.join(", ")} }` : `${blue("Exports:")} * (entire package)`,
35
+ `${blue("Exports:")} ${exportsDisplay}`,
27
36
  "",
28
37
  `${blue("Raw size:")} ${formatBytes(result.rawSize)}`,
29
38
  result.gzipSize !== null ? `${blue("Gzip size:")} ${formatBytes(result.gzipSize)} (level ${result.gzipLevel})` : `${blue("Gzip size:")} N/A (not applicable for node platform)`,
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/bundlecheck.ts"],"sourcesContent":["#!/usr/bin/env node\n\n/* istanbul ignore file */\n\nimport { Logger } from \"@node-cli/logger\";\nimport kleur from \"kleur\";\nimport {\n\tcheckBundleSize,\n\tformatBytes,\n\tgetExternals,\n\tparsePackageSpecifier,\n} from \"./bundler.js\";\nimport {\n\tgetCachedResult,\n\tnormalizeCacheKey,\n\tsetCachedResult,\n} from \"./cache.js\";\nimport { normalizePlatform, TREND_VERSION_COUNT } from \"./defaults.js\";\nimport { config } from \"./parse.js\";\nimport {\n\tanalyzeTrend,\n\trenderTrendGraph,\n\tselectTrendVersions,\n} from \"./trend.js\";\nimport { fetchPackageVersions, promptForVersion } from \"./versions.js\";\n\nconst flags = config.flags;\nconst parameters = config.parameters;\n\n// Disable kleur colors when --boring flag is set.\nkleur.enabled = !flags?.boring;\n\nconst log = new Logger({\n\tboring: flags?.boring,\n});\n\n/**\n * Display bundle result in a formatted box.\n */\nfunction displayResult(\n\tresult: {\n\t\tpackageName: string;\n\t\tpackageVersion: string;\n\t\texports: string[];\n\t\trawSize: number;\n\t\tgzipSize: number | null;\n\t\tgzipLevel: number;\n\t\texternals: string[];\n\t\tdependencies: string[];\n\t\tplatform: \"browser\" | \"node\";\n\t},\n\tisAutoDetected: boolean,\n): void {\n\tconst blue = kleur.blue;\n\tconst green = kleur.green;\n\n\tconst platformLabel = result.platform === \"node\" ? \"node\" : \"browser\";\n\tconst platformNote = isAutoDetected ? \" (auto-detected)\" : \"\";\n\n\tlog.printBox(\n\t\t[\n\t\t\t`${blue(\"Package:\")} ${result.packageName} (${blue(\"version:\")} ${result.packageVersion})`,\n\t\t\tresult.exports.length > 0\n\t\t\t\t? `${blue(\"Exports:\")} { ${result.exports.join(\", \")} }`\n\t\t\t\t: `${blue(\"Exports:\")} * (entire package)`,\n\t\t\t\"\",\n\t\t\t`${blue(\"Raw size:\")} ${formatBytes(result.rawSize)}`,\n\t\t\tresult.gzipSize !== null\n\t\t\t\t? `${blue(\"Gzip size:\")} ${formatBytes(result.gzipSize)} (level ${result.gzipLevel})`\n\t\t\t\t: `${blue(\"Gzip size:\")} N/A (not applicable for node platform)`,\n\t\t\t\"\",\n\t\t\tresult.externals.length > 0\n\t\t\t\t? `${blue(\"Externals:\")} ${result.externals.join(\", \")}`\n\t\t\t\t: `${blue(\"Externals:\")} ${green(\"none\")}`,\n\t\t\tresult.dependencies.length > 0\n\t\t\t\t? `${blue(\"Dependencies:\")} ${result.dependencies.join(\", \")}`\n\t\t\t\t: `${blue(\"Dependencies:\")} ${green(\"none\")}`,\n\t\t\t`${blue(\"Platform:\")} ${platformLabel}${platformNote}`,\n\t\t],\n\t\t{\n\t\t\tborderStyle: \"round\",\n\t\t\talign: \"left\",\n\t\t},\n\t);\n}\n\nasync function main() {\n\tlet packageName = parameters?.[\"0\"];\n\n\tif (!packageName) {\n\t\tlog.error(\"Package name is required\");\n\t\tconfig.showHelp?.();\n\t\tprocess.exit(1);\n\t}\n\n\t// Parse additional externals if provided (comma-separated).\n\tlet additionalExternals: string[] | undefined;\n\tif (flags?.external) {\n\t\tadditionalExternals = flags.external\n\t\t\t.split(\",\")\n\t\t\t.map((e) => e.trim())\n\t\t\t.filter(Boolean);\n\t}\n\n\t// Parse exports if provided (comma-separated).\n\tlet exports: string[] | undefined;\n\tconst exportsArg = parameters?.[\"1\"];\n\tif (exportsArg) {\n\t\texports = exportsArg\n\t\t\t.split(\",\")\n\t\t\t.map((e) => e.trim())\n\t\t\t.filter(Boolean);\n\t}\n\n\t// Normalize platform from flag (handles aliases like \"web\" → \"browser\").\n\tconst platform = normalizePlatform(flags?.platform);\n\n\t/**\n\t * If --trend flag is set, show bundle size trend across versions --trend alone\n\t * uses default (5), --trend N uses N versions.\n\t */\n\tconst trendValue = flags?.trend;\n\tif (trendValue !== undefined) {\n\t\tconst parsedCount = Number.parseInt(trendValue, 10);\n\t\tconst versionCount =\n\t\t\t!Number.isNaN(parsedCount) && parsedCount > 0\n\t\t\t\t? parsedCount\n\t\t\t\t: TREND_VERSION_COUNT;\n\n\t\ttry {\n\t\t\tconst { name, subpath } = parsePackageSpecifier(packageName);\n\t\t\t// Construct the full package path including subpath if present.\n\t\t\tconst fullPackagePath = subpath ? `${name}/${subpath}` : name;\n\n\t\t\tlog.info(`\\nFetching available versions for ${name}...`);\n\n\t\t\tconst { versions } = await fetchPackageVersions({\n\t\t\t\tpackageName,\n\t\t\t\tregistry: flags?.registry,\n\t\t\t});\n\n\t\t\tif (versions.length === 0) {\n\t\t\t\tlog.error(\"No versions found for this package\");\n\t\t\t\tprocess.exit(1);\n\t\t\t}\n\n\t\t\t// Select versions for trend.\n\t\t\tconst trendVersions = selectTrendVersions(versions, versionCount);\n\n\t\t\tlog.info(\n\t\t\t\t`Analyzing ${trendVersions.length} versions: ${trendVersions.join(\", \")}`,\n\t\t\t);\n\t\t\tlog.info(\"\");\n\n\t\t\tconst results = await analyzeTrend({\n\t\t\t\tpackageName: fullPackagePath,\n\t\t\t\tversions: trendVersions,\n\t\t\t\texports,\n\t\t\t\tadditionalExternals,\n\t\t\t\tnoExternal: flags?.noExternal,\n\t\t\t\tgzipLevel: flags?.gzipLevel,\n\t\t\t\tboring: flags?.boring,\n\t\t\t\tregistry: flags?.registry,\n\t\t\t\tplatform,\n\t\t\t\tforce: flags?.force,\n\t\t\t});\n\n\t\t\tif (results.length === 0) {\n\t\t\t\tlog.error(\"Failed to analyze any versions\");\n\t\t\t\tprocess.exit(1);\n\t\t\t}\n\n\t\t\t// Render and display the trend graph.\n\t\t\tconst graphLines = renderTrendGraph(\n\t\t\t\tfullPackagePath,\n\t\t\t\tresults,\n\t\t\t\tflags?.boring,\n\t\t\t);\n\t\t\tfor (const line of graphLines) {\n\t\t\t\tlog.log(line);\n\t\t\t}\n\n\t\t\tprocess.exit(0);\n\t\t} catch (error) {\n\t\t\tconst errorMessage =\n\t\t\t\terror instanceof Error ? error.message : String(error);\n\t\t\tlog.error(`Failed to analyze trend: ${errorMessage}`);\n\t\t\tprocess.exit(1);\n\t\t}\n\t}\n\n\t// If --versions flag is set, fetch and prompt for version selection.\n\tif (flags?.versions) {\n\t\ttry {\n\t\t\tconst { name, subpath } = parsePackageSpecifier(packageName);\n\t\t\tlog.info(`\\nFetching available versions for ${name}...`);\n\n\t\t\tconst { versions, tags } = await fetchPackageVersions({\n\t\t\t\tpackageName,\n\t\t\t\tregistry: flags?.registry,\n\t\t\t});\n\n\t\t\tif (versions.length === 0) {\n\t\t\t\tlog.error(\"No versions found for this package\");\n\t\t\t\tprocess.exit(1);\n\t\t\t}\n\n\t\t\tconst selectedVersion = await promptForVersion(name, versions, tags);\n\t\t\t// Rebuild specifier preserving any subpath.\n\t\t\tpackageName = subpath\n\t\t\t\t? `${name}/${subpath}@${selectedVersion}`\n\t\t\t\t: `${name}@${selectedVersion}`;\n\t\t\tlog.info(`\\nSelected: ${packageName}`);\n\t\t} catch (error) {\n\t\t\tconst errorMessage =\n\t\t\t\terror instanceof Error ? error.message : String(error);\n\t\t\tlog.error(`Failed to fetch versions: ${errorMessage}`);\n\t\t\tprocess.exit(1);\n\t\t}\n\t}\n\n\tlog.info(`\\nAnalyzing bundle size for: ${packageName}`);\n\tif (exports && exports.length > 0) {\n\t\tlog.info(`Exports: { ${exports.join(\", \")} }`);\n\t}\n\n\ttry {\n\t\t// Parse package specifier to get name and version.\n\t\tconst { name: baseName, version: requestedVersion } =\n\t\t\tparsePackageSpecifier(packageName);\n\n\t\t// Resolve \"latest\" to actual version for cache key.\n\t\tlet resolvedVersion = requestedVersion;\n\t\tif (requestedVersion === \"latest\") {\n\t\t\tconst { tags } = await fetchPackageVersions({\n\t\t\t\tpackageName: baseName,\n\t\t\t\tregistry: flags?.registry,\n\t\t\t});\n\t\t\tresolvedVersion = tags.latest || requestedVersion;\n\t\t}\n\n\t\t// Compute externals for cache key (same logic as bundler).\n\t\tconst externals = getExternals(\n\t\t\tbaseName,\n\t\t\tadditionalExternals,\n\t\t\tflags?.noExternal,\n\t\t);\n\n\t\t/**\n\t\t * Build cache key.\n\t\t * NOTE: platform can be undefined (auto-detect), which is stored as \"auto\" in cache.\n\t\t */\n\t\tconst cacheKey = normalizeCacheKey({\n\t\t\tpackageName: baseName,\n\t\t\tversion: resolvedVersion,\n\t\t\texports,\n\t\t\tplatform,\n\t\t\tgzipLevel: flags?.gzipLevel ?? 5,\n\t\t\texternals,\n\t\t\tnoExternal: flags?.noExternal ?? false,\n\t\t});\n\n\t\t// Check cache (unless --force flag is set).\n\t\tif (!flags?.force) {\n\t\t\tconst cached = getCachedResult(cacheKey);\n\t\t\tif (cached) {\n\t\t\t\tlog.info(\"NOTE: Using cached results\\n\");\n\t\t\t\tdisplayResult(cached, platform === undefined);\n\t\t\t\tprocess.exit(0);\n\t\t\t}\n\t\t}\n\n\t\tlog.info(\"Please wait, installing and bundling...\\n\");\n\n\t\tconst result = await checkBundleSize({\n\t\t\tpackageName,\n\t\t\texports,\n\t\t\tadditionalExternals,\n\t\t\tnoExternal: flags?.noExternal,\n\t\t\tgzipLevel: flags?.gzipLevel,\n\t\t\tregistry: flags?.registry,\n\t\t\tplatform,\n\t\t});\n\n\t\t// Store result in cache.\n\t\tsetCachedResult(cacheKey, result);\n\n\t\tdisplayResult(result, platform === undefined);\n\n\t\tprocess.exit(0);\n\t} catch (error) {\n\t\tconst errorMessage = error instanceof Error ? error.message : String(error);\n\t\tlog.error(`Failed to analyze bundle size: ${errorMessage}`);\n\t\tprocess.exit(1);\n\t}\n}\n\nmain();\n"],"names":["Logger","kleur","checkBundleSize","formatBytes","getExternals","parsePackageSpecifier","getCachedResult","normalizeCacheKey","setCachedResult","normalizePlatform","TREND_VERSION_COUNT","config","analyzeTrend","renderTrendGraph","selectTrendVersions","fetchPackageVersions","promptForVersion","flags","parameters","enabled","boring","log","displayResult","result","isAutoDetected","blue","green","platformLabel","platform","platformNote","printBox","packageName","packageVersion","exports","length","join","rawSize","gzipSize","gzipLevel","externals","dependencies","borderStyle","align","main","error","showHelp","process","exit","additionalExternals","external","split","map","e","trim","filter","Boolean","exportsArg","trendValue","trend","undefined","parsedCount","Number","parseInt","versionCount","isNaN","name","subpath","fullPackagePath","info","versions","registry","trendVersions","results","noExternal","force","graphLines","line","errorMessage","Error","message","String","tags","selectedVersion","baseName","version","requestedVersion","resolvedVersion","latest","cacheKey","cached"],"mappings":"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"}
1
+ {"version":3,"sources":["../src/bundlecheck.ts"],"sourcesContent":["#!/usr/bin/env node\n\n/* istanbul ignore file */\n\nimport { Logger } from \"@node-cli/logger\";\nimport kleur from \"kleur\";\nimport {\n\tcheckBundleSize,\n\tformatBytes,\n\tgetExternals,\n\tparsePackageSpecifier,\n} from \"./bundler.js\";\nimport {\n\tgetCachedResult,\n\tnormalizeCacheKey,\n\tsetCachedResult,\n} from \"./cache.js\";\nimport { normalizePlatform, TREND_VERSION_COUNT } from \"./defaults.js\";\nimport { config } from \"./parse.js\";\nimport {\n\tanalyzeTrend,\n\trenderTrendGraph,\n\tselectTrendVersions,\n} from \"./trend.js\";\nimport { fetchPackageVersions, promptForVersion } from \"./versions.js\";\n\nconst flags = config.flags;\nconst parameters = config.parameters;\n\n// Disable kleur colors when --boring flag is set.\nkleur.enabled = !flags?.boring;\n\nconst log = new Logger({\n\tboring: flags?.boring,\n});\n\n/**\n * Display bundle result in a formatted box.\n */\nfunction displayResult(\n\tresult: {\n\t\tpackageName: string;\n\t\tpackageVersion: string;\n\t\texports: string[];\n\t\trawSize: number;\n\t\tgzipSize: number | null;\n\t\tgzipLevel: number;\n\t\texternals: string[];\n\t\tdependencies: string[];\n\t\tplatform: \"browser\" | \"node\";\n\t\tnamedExportCount: number;\n\t},\n\tisAutoDetected: boolean,\n): void {\n\tconst blue = kleur.blue;\n\tconst green = kleur.green;\n\n\tconst platformLabel = result.platform === \"node\" ? \"node\" : \"browser\";\n\tconst platformNote = isAutoDetected ? \" (auto-detected)\" : \"\";\n\n\t// Format exports display.\n\tlet exportsDisplay: string;\n\tif (result.exports.length > 0) {\n\t\texportsDisplay = `{ ${result.exports.join(\", \")} }`;\n\t} else if (result.namedExportCount > 0) {\n\t\texportsDisplay = `${result.namedExportCount} named exports (entire package)`;\n\t} else {\n\t\texportsDisplay = \"* (entire package)\";\n\t}\n\n\tlog.printBox(\n\t\t[\n\t\t\t`${blue(\"Package:\")} ${result.packageName} (${blue(\"version:\")} ${result.packageVersion})`,\n\t\t\t`${blue(\"Exports:\")} ${exportsDisplay}`,\n\t\t\t\"\",\n\t\t\t`${blue(\"Raw size:\")} ${formatBytes(result.rawSize)}`,\n\t\t\tresult.gzipSize !== null\n\t\t\t\t? `${blue(\"Gzip size:\")} ${formatBytes(result.gzipSize)} (level ${result.gzipLevel})`\n\t\t\t\t: `${blue(\"Gzip size:\")} N/A (not applicable for node platform)`,\n\t\t\t\"\",\n\t\t\tresult.externals.length > 0\n\t\t\t\t? `${blue(\"Externals:\")} ${result.externals.join(\", \")}`\n\t\t\t\t: `${blue(\"Externals:\")} ${green(\"none\")}`,\n\t\t\tresult.dependencies.length > 0\n\t\t\t\t? `${blue(\"Dependencies:\")} ${result.dependencies.join(\", \")}`\n\t\t\t\t: `${blue(\"Dependencies:\")} ${green(\"none\")}`,\n\t\t\t`${blue(\"Platform:\")} ${platformLabel}${platformNote}`,\n\t\t],\n\t\t{\n\t\t\tborderStyle: \"round\",\n\t\t\talign: \"left\",\n\t\t},\n\t);\n}\n\nasync function main() {\n\tlet packageName = parameters?.[\"0\"];\n\n\tif (!packageName) {\n\t\tlog.error(\"Package name is required\");\n\t\tconfig.showHelp?.();\n\t\tprocess.exit(1);\n\t}\n\n\t// Parse additional externals if provided (comma-separated).\n\tlet additionalExternals: string[] | undefined;\n\tif (flags?.external) {\n\t\tadditionalExternals = flags.external\n\t\t\t.split(\",\")\n\t\t\t.map((e) => e.trim())\n\t\t\t.filter(Boolean);\n\t}\n\n\t// Parse exports if provided (comma-separated).\n\tlet exports: string[] | undefined;\n\tconst exportsArg = parameters?.[\"1\"];\n\tif (exportsArg) {\n\t\texports = exportsArg\n\t\t\t.split(\",\")\n\t\t\t.map((e) => e.trim())\n\t\t\t.filter(Boolean);\n\t}\n\n\t// Normalize platform from flag (handles aliases like \"web\" → \"browser\").\n\tconst platform = normalizePlatform(flags?.platform);\n\n\t/**\n\t * If --trend flag is set, show bundle size trend across versions --trend alone\n\t * uses default (5), --trend N uses N versions.\n\t */\n\tconst trendValue = flags?.trend;\n\tif (trendValue !== undefined) {\n\t\tconst parsedCount = Number.parseInt(trendValue, 10);\n\t\tconst versionCount =\n\t\t\t!Number.isNaN(parsedCount) && parsedCount > 0\n\t\t\t\t? parsedCount\n\t\t\t\t: TREND_VERSION_COUNT;\n\n\t\ttry {\n\t\t\tconst { name, subpath } = parsePackageSpecifier(packageName);\n\t\t\t// Construct the full package path including subpath if present.\n\t\t\tconst fullPackagePath = subpath ? `${name}/${subpath}` : name;\n\n\t\t\tlog.info(`\\nFetching available versions for ${name}...`);\n\n\t\t\tconst { versions } = await fetchPackageVersions({\n\t\t\t\tpackageName,\n\t\t\t\tregistry: flags?.registry,\n\t\t\t});\n\n\t\t\tif (versions.length === 0) {\n\t\t\t\tlog.error(\"No versions found for this package\");\n\t\t\t\tprocess.exit(1);\n\t\t\t}\n\n\t\t\t// Select versions for trend.\n\t\t\tconst trendVersions = selectTrendVersions(versions, versionCount);\n\n\t\t\tlog.info(\n\t\t\t\t`Analyzing ${trendVersions.length} versions: ${trendVersions.join(\", \")}`,\n\t\t\t);\n\t\t\tlog.info(\"\");\n\n\t\t\tconst results = await analyzeTrend({\n\t\t\t\tpackageName: fullPackagePath,\n\t\t\t\tversions: trendVersions,\n\t\t\t\texports,\n\t\t\t\tadditionalExternals,\n\t\t\t\tnoExternal: flags?.noExternal,\n\t\t\t\tgzipLevel: flags?.gzipLevel,\n\t\t\t\tboring: flags?.boring,\n\t\t\t\tregistry: flags?.registry,\n\t\t\t\tplatform,\n\t\t\t\tforce: flags?.force,\n\t\t\t});\n\n\t\t\tif (results.length === 0) {\n\t\t\t\tlog.error(\"Failed to analyze any versions\");\n\t\t\t\tprocess.exit(1);\n\t\t\t}\n\n\t\t\t// Render and display the trend graph.\n\t\t\tconst graphLines = renderTrendGraph(\n\t\t\t\tfullPackagePath,\n\t\t\t\tresults,\n\t\t\t\tflags?.boring,\n\t\t\t);\n\t\t\tfor (const line of graphLines) {\n\t\t\t\tlog.log(line);\n\t\t\t}\n\n\t\t\tprocess.exit(0);\n\t\t} catch (error) {\n\t\t\tconst errorMessage =\n\t\t\t\terror instanceof Error ? error.message : String(error);\n\t\t\tlog.error(`Failed to analyze trend: ${errorMessage}`);\n\t\t\tprocess.exit(1);\n\t\t}\n\t}\n\n\t// If --versions flag is set, fetch and prompt for version selection.\n\tif (flags?.versions) {\n\t\ttry {\n\t\t\tconst { name, subpath } = parsePackageSpecifier(packageName);\n\t\t\tlog.info(`\\nFetching available versions for ${name}...`);\n\n\t\t\tconst { versions, tags } = await fetchPackageVersions({\n\t\t\t\tpackageName,\n\t\t\t\tregistry: flags?.registry,\n\t\t\t});\n\n\t\t\tif (versions.length === 0) {\n\t\t\t\tlog.error(\"No versions found for this package\");\n\t\t\t\tprocess.exit(1);\n\t\t\t}\n\n\t\t\tconst selectedVersion = await promptForVersion(name, versions, tags);\n\t\t\t// Rebuild specifier preserving any subpath.\n\t\t\tpackageName = subpath\n\t\t\t\t? `${name}/${subpath}@${selectedVersion}`\n\t\t\t\t: `${name}@${selectedVersion}`;\n\t\t\tlog.info(`\\nSelected: ${packageName}`);\n\t\t} catch (error) {\n\t\t\tconst errorMessage =\n\t\t\t\terror instanceof Error ? error.message : String(error);\n\t\t\tlog.error(`Failed to fetch versions: ${errorMessage}`);\n\t\t\tprocess.exit(1);\n\t\t}\n\t}\n\n\tlog.info(`\\nAnalyzing bundle size for: ${packageName}`);\n\tif (exports && exports.length > 0) {\n\t\tlog.info(`Exports: { ${exports.join(\", \")} }`);\n\t}\n\n\ttry {\n\t\t// Parse package specifier to get name and version.\n\t\tconst { name: baseName, version: requestedVersion } =\n\t\t\tparsePackageSpecifier(packageName);\n\n\t\t// Resolve \"latest\" to actual version for cache key.\n\t\tlet resolvedVersion = requestedVersion;\n\t\tif (requestedVersion === \"latest\") {\n\t\t\tconst { tags } = await fetchPackageVersions({\n\t\t\t\tpackageName: baseName,\n\t\t\t\tregistry: flags?.registry,\n\t\t\t});\n\t\t\tresolvedVersion = tags.latest || requestedVersion;\n\t\t}\n\n\t\t// Compute externals for cache key (same logic as bundler).\n\t\tconst externals = getExternals(\n\t\t\tbaseName,\n\t\t\tadditionalExternals,\n\t\t\tflags?.noExternal,\n\t\t);\n\n\t\t/**\n\t\t * Build cache key.\n\t\t * NOTE: platform can be undefined (auto-detect), which is stored as \"auto\" in cache.\n\t\t */\n\t\tconst cacheKey = normalizeCacheKey({\n\t\t\tpackageName: baseName,\n\t\t\tversion: resolvedVersion,\n\t\t\texports,\n\t\t\tplatform,\n\t\t\tgzipLevel: flags?.gzipLevel ?? 5,\n\t\t\texternals,\n\t\t\tnoExternal: flags?.noExternal ?? false,\n\t\t});\n\n\t\t// Check cache (unless --force flag is set).\n\t\tif (!flags?.force) {\n\t\t\tconst cached = getCachedResult(cacheKey);\n\t\t\tif (cached) {\n\t\t\t\tlog.info(\"NOTE: Using cached results\\n\");\n\t\t\t\tdisplayResult(cached, platform === undefined);\n\t\t\t\tprocess.exit(0);\n\t\t\t}\n\t\t}\n\n\t\tlog.info(\"Please wait, installing and bundling...\\n\");\n\n\t\tconst result = await checkBundleSize({\n\t\t\tpackageName,\n\t\t\texports,\n\t\t\tadditionalExternals,\n\t\t\tnoExternal: flags?.noExternal,\n\t\t\tgzipLevel: flags?.gzipLevel,\n\t\t\tregistry: flags?.registry,\n\t\t\tplatform,\n\t\t});\n\n\t\t// Store result in cache.\n\t\tsetCachedResult(cacheKey, result);\n\n\t\tdisplayResult(result, platform === undefined);\n\n\t\tprocess.exit(0);\n\t} catch (error) {\n\t\tconst errorMessage = error instanceof Error ? error.message : String(error);\n\t\tlog.error(`Failed to analyze bundle size: ${errorMessage}`);\n\t\tprocess.exit(1);\n\t}\n}\n\nmain();\n"],"names":["Logger","kleur","checkBundleSize","formatBytes","getExternals","parsePackageSpecifier","getCachedResult","normalizeCacheKey","setCachedResult","normalizePlatform","TREND_VERSION_COUNT","config","analyzeTrend","renderTrendGraph","selectTrendVersions","fetchPackageVersions","promptForVersion","flags","parameters","enabled","boring","log","displayResult","result","isAutoDetected","blue","green","platformLabel","platform","platformNote","exportsDisplay","exports","length","join","namedExportCount","printBox","packageName","packageVersion","rawSize","gzipSize","gzipLevel","externals","dependencies","borderStyle","align","main","error","showHelp","process","exit","additionalExternals","external","split","map","e","trim","filter","Boolean","exportsArg","trendValue","trend","undefined","parsedCount","Number","parseInt","versionCount","isNaN","name","subpath","fullPackagePath","info","versions","registry","trendVersions","results","noExternal","force","graphLines","line","errorMessage","Error","message","String","tags","selectedVersion","baseName","version","requestedVersion","resolvedVersion","latest","cacheKey","cached"],"mappings":"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"}
package/dist/bundler.d.ts CHANGED
@@ -37,6 +37,11 @@ export type BundleResult = {
37
37
  externals: string[];
38
38
  dependencies: string[];
39
39
  platform: "browser" | "node";
40
+ /**
41
+ * Total number of named exports in the package (when analyzing entire
42
+ * package).
43
+ */
44
+ namedExportCount: number;
40
45
  };
41
46
  /**
42
47
  * Format bytes to human-readable string.
@@ -50,9 +55,21 @@ export type EntryContentOptions = {
50
55
  exportToSubpath?: Map<string, string>;
51
56
  };
52
57
  /**
53
- * Get externals list based on options.
58
+ * Get externals list based on options and package dependencies. react and
59
+ * react-dom are only marked as external if they are declared in the package's
60
+ * dependencies or peerDependencies.
61
+ *
62
+ * Returns the base package names (e.g., ["react", "react-dom"]) for display
63
+ * purposes.
64
+ *
65
+ */
66
+ export declare function getExternals(packageName: string, externals?: string[], noExternal?: boolean, packageDependencies?: string[]): string[];
67
+ /**
68
+ * Expand externals to include subpaths for esbuild. For example, "react"
69
+ * expands to ["react", "react/jsx-runtime", "react/jsx-dev-runtime"]. This is
70
+ * needed because esbuild doesn't automatically externalize subpaths.
54
71
  */
55
- export declare function getExternals(packageName: string, externals?: string[], noExternal?: boolean): string[];
72
+ export declare function expandExternalsForEsbuild(externals: string[]): string[];
56
73
  export type PackageExports = Record<string, string | {
57
74
  import?: string;
58
75
  types?: string;
package/dist/bundler.js CHANGED
@@ -5,7 +5,8 @@ import path from "node:path";
5
5
  import { promisify } from "node:util";
6
6
  import zlib from "node:zlib";
7
7
  import * as esbuild from "esbuild";
8
- import { DEFAULT_EXTERNALS } from "./defaults.js";
8
+ import { DEFAULT_EXTERNALS, EXTERNAL_SUBPATHS } from "./defaults.js";
9
+ import { getNamedExports } from "./exports.js";
9
10
  const gzipAsync = promisify(zlib.gzip);
10
11
  /**
11
12
  * Escape special regex characters in a string.
@@ -208,20 +209,89 @@ let usePnpm = null;
208
209
  return `import * as pkg from "${packageName}";\nexport default pkg;\n`;
209
210
  }
210
211
  /**
211
- * Get externals list based on options.
212
- */ export function getExternals(packageName, externals, noExternal) {
212
+ * Check if an error is an esbuild BuildFailure. BuildFailure has an `errors`
213
+ * array with structured error objects.
214
+ */ function isEsbuildBuildFailure(error) {
215
+ return typeof error === "object" && error !== null && "errors" in error && Array.isArray(error.errors);
216
+ }
217
+ /**
218
+ * Extract unresolved module paths from an esbuild BuildFailure error. Returns
219
+ * an object indicating which react-related modules failed to resolve.
220
+ *
221
+ * Uses esbuild's structured error objects (errors array) for reliable parsing.
222
+ * Falls back to string matching if the error format is unexpected.
223
+ *
224
+ * Tested against esbuild 0.27.x error format.
225
+ *
226
+ */ function parseUnresolvedModules(error) {
227
+ const result = {
228
+ hasUnresolvedReact: false,
229
+ hasUnresolvedReactDom: false
230
+ };
231
+ /**
232
+ * Pattern to extract module path from "Could not resolve X" errors. Matches:
233
+ * Could not resolve "module-name" or Could not resolve 'module-name'.
234
+ */ const resolveErrorPattern = /Could not resolve ["']([^"']+)["']/;
235
+ if (isEsbuildBuildFailure(error)) {
236
+ // Use structured error objects from esbuild BuildFailure.
237
+ for (const err of error.errors){
238
+ const match = resolveErrorPattern.exec(err.text);
239
+ if (match) {
240
+ const modulePath = match[1];
241
+ // Check if it's a react or react-dom import (including subpaths).
242
+ if (modulePath === "react" || modulePath.startsWith("react/")) {
243
+ result.hasUnresolvedReact = true;
244
+ }
245
+ if (modulePath === "react-dom" || modulePath.startsWith("react-dom/")) {
246
+ result.hasUnresolvedReactDom = true;
247
+ }
248
+ }
249
+ }
250
+ } else {
251
+ // Fallback: parse error message string (less reliable).
252
+ const errorMessage = String(error);
253
+ const matches = errorMessage.matchAll(/Could not resolve ["']([^"']+)["']/g);
254
+ for (const match of matches){
255
+ const modulePath = match[1];
256
+ if (modulePath === "react" || modulePath.startsWith("react/")) {
257
+ result.hasUnresolvedReact = true;
258
+ }
259
+ if (modulePath === "react-dom" || modulePath.startsWith("react-dom/")) {
260
+ result.hasUnresolvedReactDom = true;
261
+ }
262
+ }
263
+ }
264
+ return result;
265
+ }
266
+ /**
267
+ * Get externals list based on options and package dependencies. react and
268
+ * react-dom are only marked as external if they are declared in the package's
269
+ * dependencies or peerDependencies.
270
+ *
271
+ * Returns the base package names (e.g., ["react", "react-dom"]) for display
272
+ * purposes.
273
+ *
274
+ */ export function getExternals(packageName, externals, noExternal, packageDependencies) {
213
275
  if (noExternal) {
214
276
  return [];
215
277
  }
216
- // Start with default externals (react, react-dom).
217
- let result = [
218
- ...DEFAULT_EXTERNALS
219
- ];
220
- // If checking react or react-dom themselves, don't mark them as external.
221
- if (packageName === "react") {
222
- result = result.filter((e)=>e !== "react");
223
- } else if (packageName === "react-dom") {
224
- result = result.filter((e)=>e !== "react-dom");
278
+ /**
279
+ * Start with empty result - we'll only add react/react-dom if they're in
280
+ * package deps.
281
+ */ let result = [];
282
+ /**
283
+ * Only include react/react-dom if they're in the package's dependencies or
284
+ * peerDependencies.
285
+ */ if (packageDependencies && packageDependencies.length > 0) {
286
+ for (const dep of DEFAULT_EXTERNALS){
287
+ // Don't mark as external if we're checking the package itself.
288
+ if (dep === packageName) {
289
+ continue;
290
+ }
291
+ if (packageDependencies.includes(dep)) {
292
+ result.push(dep);
293
+ }
294
+ }
225
295
  }
226
296
  // Add any additional externals.
227
297
  if (externals && externals.length > 0) {
@@ -234,6 +304,24 @@ let usePnpm = null;
234
304
  }
235
305
  return result;
236
306
  }
307
+ /**
308
+ * Expand externals to include subpaths for esbuild. For example, "react"
309
+ * expands to ["react", "react/jsx-runtime", "react/jsx-dev-runtime"]. This is
310
+ * needed because esbuild doesn't automatically externalize subpaths.
311
+ */ export function expandExternalsForEsbuild(externals) {
312
+ const expanded = [];
313
+ for (const ext of externals){
314
+ expanded.push(ext);
315
+ // Add subpaths if this is a known package with subpath exports.
316
+ const subpaths = EXTERNAL_SUBPATHS[ext];
317
+ if (subpaths) {
318
+ expanded.push(...subpaths);
319
+ }
320
+ }
321
+ return [
322
+ ...new Set(expanded)
323
+ ];
324
+ }
237
325
  /**
238
326
  * Get version, dependencies, peer dependencies, and exports from an installed
239
327
  * package.
@@ -447,23 +535,79 @@ let usePnpm = null;
447
535
  });
448
536
  const entryFile = path.join(tmpDir, "entry.js");
449
537
  fs.writeFileSync(entryFile, entryContent);
450
- // Get externals.
451
- const externals = getExternals(packageName, additionalExternals, noExternal);
452
- // Bundle with esbuild.
453
- const result = await esbuild.build({
454
- entryPoints: [
455
- entryFile
456
- ],
457
- bundle: true,
458
- write: false,
459
- format: "esm",
460
- platform,
461
- target: "es2020",
462
- minify: true,
463
- treeShaking: true,
464
- external: externals,
465
- metafile: true
466
- });
538
+ /**
539
+ * Get externals based on package dependencies. Only include react/react-dom
540
+ * if they're in the package's dependencies or peerDependencies.
541
+ */ let externals = getExternals(packageName, additionalExternals, noExternal, allDependencies);
542
+ /**
543
+ * Expand externals to include subpaths for esbuild.
544
+ * e.g., "react" -> ["react", "react/jsx-runtime", "react/jsx-dev-runtime"]
545
+ */ let esbuildExternals = expandExternalsForEsbuild(externals);
546
+ /**
547
+ * Bundle with esbuild. We use a two-phase approach:
548
+ * 1. First attempt with logLevel: "silent" to suppress errors
549
+ * 2. If it fails due to unresolved react imports, auto-add react to externals and retry
550
+ * This handles packages that don't properly declare react as a peer
551
+ * dependency.
552
+ */ let result;
553
+ try {
554
+ result = await esbuild.build({
555
+ entryPoints: [
556
+ entryFile
557
+ ],
558
+ bundle: true,
559
+ write: false,
560
+ format: "esm",
561
+ platform,
562
+ target: "es2020",
563
+ minify: true,
564
+ treeShaking: true,
565
+ external: esbuildExternals,
566
+ metafile: true,
567
+ logLevel: "silent"
568
+ });
569
+ } catch (error) {
570
+ /**
571
+ * Parse unresolved module errors using esbuild's structured error format.
572
+ * This handles packages that don't properly declare react as a peer
573
+ * dependency.
574
+ */ const { hasUnresolvedReact, hasUnresolvedReactDom } = parseUnresolvedModules(error);
575
+ if (!noExternal && (hasUnresolvedReact || hasUnresolvedReactDom)) {
576
+ // Auto-add react and/or react-dom to externals and retry.
577
+ const autoExternals = [];
578
+ if (hasUnresolvedReact && !externals.includes("react")) {
579
+ autoExternals.push("react");
580
+ }
581
+ if (hasUnresolvedReactDom && !externals.includes("react-dom")) {
582
+ autoExternals.push("react-dom");
583
+ }
584
+ if (autoExternals.length > 0) {
585
+ externals = [
586
+ ...externals,
587
+ ...autoExternals
588
+ ];
589
+ esbuildExternals = expandExternalsForEsbuild(externals);
590
+ result = await esbuild.build({
591
+ entryPoints: [
592
+ entryFile
593
+ ],
594
+ bundle: true,
595
+ write: false,
596
+ format: "esm",
597
+ platform,
598
+ target: "es2020",
599
+ minify: true,
600
+ treeShaking: true,
601
+ external: esbuildExternals,
602
+ metafile: true
603
+ });
604
+ } else {
605
+ throw error;
606
+ }
607
+ } else {
608
+ throw error;
609
+ }
610
+ }
467
611
  // Get raw size.
468
612
  const bundleContent = result.outputFiles[0].contents;
469
613
  const rawSize = bundleContent.length;
@@ -486,6 +630,10 @@ let usePnpm = null;
486
630
  ].sort();
487
631
  displayName = `${packageName}/{${uniqueSubpaths.join(", ")}}`;
488
632
  }
633
+ /**
634
+ * Get named export count from the package's type definitions. Use
635
+ * runtimeCount to exclude type-only exports (types, interfaces).
636
+ */ const { runtimeCount: namedExportCount } = getNamedExports(tmpDir, packageName);
489
637
  return {
490
638
  packageName: displayName,
491
639
  packageVersion: pkgInfo.version,
@@ -495,7 +643,8 @@ let usePnpm = null;
495
643
  gzipLevel,
496
644
  externals,
497
645
  dependencies: allDependencies,
498
- platform
646
+ platform,
647
+ namedExportCount
499
648
  };
500
649
  } finally{
501
650
  cleanupTempDir(tmpDir);