
Security News
/Research
Popular node-ipc npm Package Infected with Credential Stealer
Socket detected malicious node-ipc versions with obfuscated stealer/backdoor behavior in a developing npm supply chain attack.
@node-cli/bundlecheck
Advanced tools
A CLI tool to check the bundle size of npm packages, similar to bundlephobia.com.
lodash@4.17.0)--versions flag--trend flag (bar graph across versions)jsx-runtime)browser (default) or node with smart auto-detectionnpm install -g @node-cli/bundlecheck
Or use with npx:
npx @node-cli/bundlecheck <package-name>
bundlecheck lodash
bundlecheck @mantine/core
bundlecheck lodash@4.17.0
bundlecheck @versini/ui-panel@1.0.0
bundlecheck lodash --versions
bundlecheck @mantine/core --versions
This will fetch available versions from npm and let you select one from a list.
bundlecheck lodash --trend # default: 5 versions
bundlecheck clsx --trend 3 # custom: 3 versions
This analyzes recent versions and displays a bar graph showing how the bundle size has evolved:
Bundle Size Trend: clsx
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
Gzip Size:
2.1.1 āāāāāāāāāāāāāāāāāāāāāāāāāāāāāā 331 B
2.0.0 āāāāāāāāāāāāāāāāāāāāāāāāāāāāā 321 B
1.2.0 āāāāāāāāāāāāāāāāāāāāāāāāāāāāā 318 B
1.0.4 āāāāāāāāāāāāāāāāāāāāāāāāāāāā 308 B
1.0.1 āāāāāāāāāāāāāāāāāāāāāāāāāā 285 B
Raw Size:
2.1.1 āāāāāāāāāāāāāāāāāāāāāāāāāāāāāā 527 B
...
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
Change from 1.0.1 to 2.1.1:
Gzip: +46 B (+16.1%)
Raw: +108 B (+25.8%)
bundlecheck @mantine/core "ScrollArea,Button"
bundlecheck lodash "debounce,throttle"
| Flag | Short | Description |
|---|---|---|
--help | -h | Display help instructions |
--version | -v | Output the current version |
--versions | -V | Choose from available package versions interactively |
--trend [N] | -t [N] | Show bundle size trend for N versions (default: 5) |
--boring | -b | Do not use color output |
--gzipLevel <n> | -g <n> | Gzip compression level (1-9, default: 5) |
--external <pkgs> | -e <pkgs> | Comma-separated additional packages to mark as external |
--noExternal | -n | Do not mark any packages as external |
--registry <url> | -r <url> | Custom npm registry URL (default: registry.npmjs.org) |
--platform <name> | -p <name> | Target platform: auto (default), browser, or node |
--force | -f | Bypass cache and force re-fetch/re-calculation |
--target <target> | -T <target> | esbuild target (e.g., "es2022", "es2020"). Default: "es2022" |
# Check the entire lodash package
bundlecheck lodash
# Check a specific version
bundlecheck lodash@4.17.0
bundlecheck @versini/ui-panel@1.0.0
# Check specific exports from @mantine/core
bundlecheck @mantine/core "ScrollArea,Button"
# Check react with all dependencies bundled (no externals)
bundlecheck react -n
# Add vue and svelte as additional externals
bundlecheck some-package --external "vue,svelte"
# Use a different gzip compression level
bundlecheck lodash --gzipLevel 6
# Choose version interactively
bundlecheck lodash --versions
# Show bundle size trend (default: 5 versions)
bundlecheck lodash --trend
# Show bundle size trend for 3 versions
bundlecheck lodash --trend 3
# Use a custom npm registry
bundlecheck @myorg/private-pkg --registry https://npm.mycompany.com
# Check a Node.js package (explicit platform)
bundlecheck express --platform node
# Auto-detect platform (default behavior)
bundlecheck express # auto-detects "node" from package.json engines
# Bypass cache and force re-fetch
bundlecheck lodash --force
# Use a specific esbuild target (default: es2022)
bundlecheck lodash --target es2020
In addition to the CLI, @node-cli/bundlecheck can be used as a library in your Node.js code.
npm install @node-cli/bundlecheck
import { getBundleStats, getBundleTrend, getPackageVersions, getPackageExports } from "@node-cli/bundlecheck";
// Get bundle stats for a single package
const stats = await getBundleStats({
package: "@mantine/core@7.0.0",
});
console.log(stats);
// {
// packageName: "@mantine/core",
// packageVersion: "7.0.0",
// rawSize: 234567,
// gzipSize: 45678,
// rawSizeFormatted: "229.07 kB",
// gzipSizeFormatted: "44.61 kB",
// exports: [],
// externals: ["react", "react-dom"], // Only present because @mantine/core has these as peerDependencies
// dependencies: ["@floating-ui/react", ...],
// platform: "browser",
// gzipLevel: 5,
// fromCache: false
// }
// Check specific exports (tree-shaking)
const buttonStats = await getBundleStats({
package: "@mantine/core",
exports: ["Button", "Input"],
});
console.log(buttonStats.gzipSizeFormatted); // "12.3 kB"
// Get bundle size trend across versions
const trend = await getBundleTrend({
package: "@mantine/core",
versionCount: 5,
});
console.log(trend);
// {
// packageName: "@mantine/core",
// versions: [
// { version: "7.0.0", rawSize: 234567, gzipSize: 45678, rawSizeFormatted: "229.07 kB", ... },
// { version: "6.0.0", rawSize: 220000, gzipSize: 42000, ... },
// ...
// ],
// change: {
// fromVersion: "5.0.0",
// toVersion: "7.0.0",
// rawDiff: 14567,
// rawPercent: 6.6,
// rawDiffFormatted: "+14.23 kB",
// gzipDiff: 3678,
// gzipPercent: 8.7,
// gzipDiffFormatted: "+3.59 kB"
// }
// }
// Get available versions for a package
const versions = await getPackageVersions({
package: "@mantine/core",
});
console.log(versions.tags.latest); // "7.0.0"
console.log(versions.versions.slice(0, 5)); // ["7.0.0", "6.0.21", "6.0.20", ...]
// Get named exports from a package
const exports = await getPackageExports({
package: "date-fns@3.6.0",
});
console.log(`Found ${exports.count} exports`);
console.log(exports.exports.slice(0, 5).map(e => e.name)); // ["add", "addBusinessDays", "addDays", ...]
getBundleStats(options)Get bundle size statistics for a single package.
Options:
| Option | Type | Default | Description |
|---|---|---|---|
package | string | (required) | Package name with optional version (e.g., lodash@4.17.0) |
exports | string[] | undefined | Specific exports to measure (tree-shaking) |
external | string[] | undefined | Additional packages to mark as external |
noExternal | boolean | false | Bundle everything (no externals, even react/react-dom) |
gzipLevel | number | 5 | Gzip compression level (1-9) |
registry | string | undefined | Custom npm registry URL |
platform | "browser" | "node" | "auto" | "auto" | Target platform |
force | boolean | false | Bypass cache |
target | string | "es2022" | esbuild target (e.g., "es2022", "es2020") |
Returns: Promise<BundleStats>
type BundleStats = {
packageName: string; // Display name (may include subpath)
packageVersion: string; // Resolved version
exports: string[]; // Exports analyzed
rawSize: number; // Raw size in bytes
gzipSize: number | null; // Gzip size in bytes (null for node platform)
gzipLevel: number; // Compression level used
externals: string[]; // External packages
dependencies: string[]; // Package dependencies
platform: "browser" | "node";
rawSizeFormatted: string; // Human-readable (e.g., "45.2 kB")
gzipSizeFormatted: string | null;
fromCache: boolean; // Whether result was from cache
namedExportCount: number; // Total named exports in package
};
getBundleTrend(options)Get bundle size trend across multiple versions.
Options:
| Option | Type | Default | Description |
|---|---|---|---|
package | string | (required) | Package name (version ignored if provided) |
versionCount | number | 5 | Number of versions to analyze |
exports | string[] | undefined | Specific exports to measure |
external | string[] | undefined | Additional packages to mark as external |
noExternal | boolean | false | Bundle everything including default externals |
gzipLevel | number | 5 | Gzip compression level (1-9) |
registry | string | undefined | Custom npm registry URL |
platform | "browser" | "node" | "auto" | "auto" | Target platform |
force | boolean | false | Bypass cache |
target | string | "es2022" | esbuild target (e.g., "es2022", "es2020") |
Returns: Promise<BundleTrend>
type BundleTrend = {
packageName: string;
versions: TrendVersionResult[]; // Results for each version (newest first)
change: TrendChange | null; // Change between oldest and newest
};
type TrendVersionResult = {
version: string;
rawSize: number;
gzipSize: number | null;
rawSizeFormatted: string;
gzipSizeFormatted: string | null;
};
type TrendChange = {
fromVersion: string;
toVersion: string;
rawDiff: number; // Positive = increase, negative = decrease
rawPercent: number | null; // null if oldest size was 0
rawDiffFormatted: string; // e.g., "+5.2 kB" or "-1.3 kB"
gzipDiff: number | null;
gzipPercent: number | null; // null if not applicable or oldest size was 0
gzipDiffFormatted: string | null;
};
getPackageVersions(options)Get available versions for an npm package.
Options:
| Option | Type | Default | Description |
|---|---|---|---|
package | string | (required) | Package name |
registry | string | undefined | Custom npm registry URL |
Returns: Promise<PackageVersions>
type PackageVersions = {
versions: string[]; // All versions (newest first)
tags: Record<string, string>; // Dist tags (e.g., { latest: "7.0.0" })
};
getPackageExports(options)Get the named exports of an npm package by analyzing its TypeScript declarations.
Options:
| Option | Type | Default | Description |
|---|---|---|---|
package | string | (required) | Package name with optional version |
registry | string | undefined | Custom npm registry URL |
Returns: Promise<PackageExports>
type PackageExports = {
packageName: string; // Package name
packageVersion: string; // Resolved version
exports: PackageExport[]; // All named exports (including types)
count: number; // Total count (including types)
runtimeExports: PackageExport[]; // Runtime exports only (no types/interfaces)
runtimeCount: number; // Count of runtime exports
};
type PackageExport = {
name: string; // Export name (e.g., "Button")
kind: "function" | "class" | "const" | "type" | "interface" | "enum" | "unknown";
};
Example:
const result = await getPackageExports({
package: "@mantine/core",
});
console.log(result.count); // 1056 (all exports including types)
console.log(result.runtimeCount); // 365 (only importable exports)
console.log(result.runtimeExports[0]); // { name: "Accordion", kind: "unknown" }
import { formatBytes, parsePackageSpecifier, clearCache, getCacheCount } from "@node-cli/bundlecheck";
// Format bytes to human-readable string
formatBytes(1024); // "1 kB"
formatBytes(1536); // "1.5 kB"
// Parse a package specifier
parsePackageSpecifier("@scope/name@1.0.0");
// { name: "@scope/name", version: "1.0.0" }
parsePackageSpecifier("@scope/name/subpath@2.0.0");
// { name: "@scope/name", version: "2.0.0", subpath: "subpath" }
// Cache management
getCacheCount(); // Returns number of cached entries
clearCache(); // Clears all cached results
The --platform flag controls how the bundle is built:
auto (default): Automatically detects the platform from the package's engines field. If the package specifies engines.node without engines.browser, it uses node; otherwise defaults to browser.browser: Builds for browser environments (also accepts aliases: web, desktop, client)node: Builds for Node.js environments (also accepts aliases: server, nodejs, backend)When targeting node platform:
# Auto-detect (recommended for most cases)
bundlecheck express # detects "node" from engines.node
# Explicit platform
bundlecheck lodash --platform browser
bundlecheck fastify -p server # "server" is an alias for "node"
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.
When react is externalized, the following subpaths are also automatically externalized:
react/jsx-runtimereact/jsx-dev-runtimeWhen react-dom is externalized:
react-dom/clientreact-dom/serverThis ensures that modern JSX transform imports and React 18+ APIs work correctly.
Some npm packages import from react without declaring it in their peerDependencies. Bundlecheck automatically detects this scenario: if the initial bundle fails due to unresolved React imports, it will automatically add react and/or react-dom to externals and retry.
This means commands like the following work out of the box, even when the package doesn't properly declare its peer dependencies:
bundlecheck @versini/ui-button # Works even though react is not in peerDependencies
To include all dependencies (including React when present) in the bundle size calculation, use the --no-external flag:
bundlecheck react --no-external # Include React itself in the bundle
Use the --registry flag to check packages from private or alternative npm registries:
# Private corporate registry
bundlecheck @myorg/myorg-ui-library --registry https://npm.mycompany.com
# Verdaccio local registry
bundlecheck my-local-pkg --registry http://localhost:4873
Note: If the registry requires authentication, ensure your npm/pnpm is configured with the appropriate credentials (via .npmrc or environment variables).
Bundle size results are cached locally to speed up repeated lookups. The cache is stored in ~/.bundlecheck/cache.db using SQLite.
The cache uses resolved versions, not the requested specifier. This means:
bundlecheck @mantine/core # Resolves "latest" to e.g. 8.0.0, caches as 8.0.0
bundlecheck @mantine/core@8.0.0 # Cache hit! Same resolved version
This also works with --trend: if you previously checked @mantine/core (which resolved to v8.0.0), running --trend will use the cached result for v8.0.0 and only fetch the other versions.
Use the --force flag to skip the cache and re-fetch/re-calculate:
bundlecheck lodash --force # Always fetches fresh data
bundlecheck lodash -f # Short form
The cache database is stored at:
~/.bundlecheck/cache.db
To clear the cache, simply delete this file or the ~/.bundlecheck directory.
MIT - see LICENSE for details.
FAQs
CLI tool to check the bundle size of npm packages (like bundlephobia)
The npm package @node-cli/bundlecheck receives a total of 40 weekly downloads. As such, @node-cli/bundlecheck popularity was classified as not popular.
We found that @node-cli/bundlecheck demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago.Ā It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Security News
/Research
Socket detected malicious node-ipc versions with obfuscated stealer/backdoor behavior in a developing npm supply chain attack.

Security News
TeamPCP and BreachForums are promoting a Shai-Hulud supply chain attack contest with a $1,000 prize for the biggest package compromise.

Security News
Packagist urges PHP projects to update Composer after a GitHub token format change exposed some GitHub Actions tokens in CI logs.