Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions packages/bundlecheck/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ bundlecheck lodash "debounce,throttle"
| `--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" |

### Examples

Expand Down Expand Up @@ -151,6 +152,9 @@ 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
```

## Programmatic Usage (Library API)
Expand Down Expand Up @@ -253,6 +257,7 @@ Get bundle size statistics for a single package.
| `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>`

Expand Down Expand Up @@ -291,6 +296,7 @@ Get bundle size trend across multiple versions.
| `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>`

Expand Down
85 changes: 84 additions & 1 deletion packages/bundlecheck/src/__tests__/defaults.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
import { describe, expect, it } from "vitest";
import { isValidPlatform, normalizePlatform } from "../defaults.js";
import {
DEFAULT_TARGET,
isValidPlatform,
isValidTarget,
normalizePlatform,
normalizeTarget,
} from "../defaults.js";

describe("normalizePlatform", () => {
describe("auto detection", () => {
Expand Down Expand Up @@ -138,3 +144,80 @@ describe("isValidPlatform", () => {
});
});
});

describe("normalizeTarget", () => {
describe("default value", () => {
it("should return DEFAULT_TARGET for undefined input", () => {
expect(normalizeTarget(undefined)).toBe(DEFAULT_TARGET);
});

it("should return DEFAULT_TARGET for empty string", () => {
expect(normalizeTarget("")).toBe(DEFAULT_TARGET);
});
});

describe("normalization", () => {
it("should normalize to lowercase", () => {
expect(normalizeTarget("ES2022")).toBe("es2022");
expect(normalizeTarget("ES2020")).toBe("es2020");
expect(normalizeTarget("ESNext")).toBe("esnext");
});

it("should trim whitespace", () => {
expect(normalizeTarget(" es2022 ")).toBe("es2022");
expect(normalizeTarget(" es2020 ")).toBe("es2020");
});
});
});

describe("isValidTarget", () => {
describe("valid values", () => {
it("should accept undefined", () => {
expect(isValidTarget(undefined)).toBe(true);
});

it("should accept ECMAScript versions", () => {
expect(isValidTarget("es2015")).toBe(true);
expect(isValidTarget("es2016")).toBe(true);
expect(isValidTarget("es2017")).toBe(true);
expect(isValidTarget("es2018")).toBe(true);
expect(isValidTarget("es2019")).toBe(true);
expect(isValidTarget("es2020")).toBe(true);
expect(isValidTarget("es2021")).toBe(true);
expect(isValidTarget("es2022")).toBe(true);
expect(isValidTarget("es2023")).toBe(true);
expect(isValidTarget("es2024")).toBe(true);
expect(isValidTarget("esnext")).toBe(true);
});

it("should be case insensitive", () => {
expect(isValidTarget("ES2022")).toBe(true);
expect(isValidTarget("ES2020")).toBe(true);
expect(isValidTarget("ESNEXT")).toBe(true);
});

it("should handle whitespace", () => {
expect(isValidTarget(" es2022 ")).toBe(true);
expect(isValidTarget(" es2020 ")).toBe(true);
});
});

describe("invalid values", () => {
it("should reject invalid target names", () => {
expect(isValidTarget("invalid")).toBe(false);
expect(isValidTarget("es5")).toBe(false);
expect(isValidTarget("es6")).toBe(false);
expect(isValidTarget("es2014")).toBe(false);
expect(isValidTarget("es2025")).toBe(false);
});

it("should reject empty string", () => {
expect(isValidTarget("")).toBe(false);
});

it("should reject misspellings", () => {
expect(isValidTarget("es202")).toBe(false);
expect(isValidTarget("esnex")).toBe(false);
});
});
});
11 changes: 10 additions & 1 deletion packages/bundlecheck/src/bundlecheck.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,11 @@ import {
normalizeCacheKey,
setCachedResult,
} from "./cache.js";
import { normalizePlatform, TREND_VERSION_COUNT } from "./defaults.js";
import {
normalizePlatform,
normalizeTarget,
TREND_VERSION_COUNT,
} from "./defaults.js";
import { config } from "./parse.js";
import {
analyzeTrend,
Expand Down Expand Up @@ -124,6 +128,9 @@ async function main() {
// Normalize platform from flag (handles aliases like "web" → "browser").
const platform = normalizePlatform(flags?.platform);

// Normalize target (e.g., lowercase).
const target = normalizeTarget(flags?.target);

/**
* If --trend flag is set, show bundle size trend across versions --trend alone
* uses default (5), --trend N uses N versions.
Expand Down Expand Up @@ -172,6 +179,7 @@ async function main() {
registry: flags?.registry,
platform,
force: flags?.force,
target,
});

if (results.length === 0) {
Expand Down Expand Up @@ -289,6 +297,7 @@ async function main() {
gzipLevel: flags?.gzipLevel,
registry: flags?.registry,
platform,
target,
});

// Store result in cache.
Expand Down
15 changes: 12 additions & 3 deletions packages/bundlecheck/src/bundler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,11 @@ import path from "node:path";
import { promisify } from "node:util";
import zlib from "node:zlib";
import * as esbuild from "esbuild";
import { DEFAULT_EXTERNALS, EXTERNAL_SUBPATHS } from "./defaults.js";
import {
DEFAULT_EXTERNALS,
DEFAULT_TARGET,
EXTERNAL_SUBPATHS,
} from "./defaults.js";
import { getNamedExports } from "./exports.js";

const gzipAsync = promisify(zlib.gzip);
Expand Down Expand Up @@ -88,6 +92,10 @@ export type BundleOptions = {
* Target platform. If undefined, auto-detects from package.json engines.
*/
platform?: "browser" | "node";
/**
* esbuild target (e.g., "es2022", "es2020"). Defaults to "es2022".
*/
target?: string;
};

export type BundleResult = {
Expand Down Expand Up @@ -617,6 +625,7 @@ export async function checkBundleSize(
gzipLevel = 5,
registry,
platform: explicitPlatform,
target = DEFAULT_TARGET,
} = options;

// Parse the package specifier to extract name, version, and subpath.
Expand Down Expand Up @@ -775,7 +784,7 @@ export async function checkBundleSize(
write: false,
format: "esm",
platform,
target: "es2020",
target,
minify: true,
treeShaking: true,
external: esbuildExternals,
Expand Down Expand Up @@ -811,7 +820,7 @@ export async function checkBundleSize(
write: false,
format: "esm",
platform,
target: "es2020",
target,
minify: true,
treeShaking: true,
external: esbuildExternals,
Expand Down
52 changes: 52 additions & 0 deletions packages/bundlecheck/src/defaults.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ export const defaultFlags = {
registry: "",
platform: "auto",
force: false,
target: "es2022",
};

/**
Expand Down Expand Up @@ -79,6 +80,57 @@ export function isValidPlatform(platform: string | undefined): boolean {

export const TREND_VERSION_COUNT = 5;

/**
* Default esbuild target for bundling.
*/
export const DEFAULT_TARGET = "es2022";

/**
* Valid esbuild target values.
* Includes ECMAScript versions (es2015-es2024, esnext) and browser/runtime targets.
*/
export const VALID_TARGETS = [
"es2015",
"es2016",
"es2017",
"es2018",
"es2019",
"es2020",
"es2021",
"es2022",
"es2023",
"es2024",
"esnext",
];

/**
* Check if a target value is valid.
*/
export function isValidTarget(target: string | undefined): boolean {
if (target === undefined) {
return true;
}

const normalized = target.toLowerCase().trim();

// Empty string after trim is invalid.
if (normalized === "") {
return false;
}

return VALID_TARGETS.includes(normalized);
}

/**
* Normalize target to lowercase.
*/
export function normalizeTarget(target: string | undefined): string {
if (!target) {
return DEFAULT_TARGET;
}
return target.toLowerCase().trim();
}

/**
* Base packages to auto-detect for externalization. These are checked against
* the package's dependencies/peerDependencies.
Expand Down
24 changes: 23 additions & 1 deletion packages/bundlecheck/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,11 @@ import {
normalizeCacheKey,
setCachedResult,
} from "./cache.js";
import { normalizePlatform, TREND_VERSION_COUNT } from "./defaults.js";
import {
normalizePlatform,
normalizeTarget,
TREND_VERSION_COUNT,
} from "./defaults.js";
import type { NamedExport } from "./exports.js";
import { analyzeTrend, selectTrendVersions } from "./trend.js";
import { fetchPackageVersions as fetchVersions } from "./versions.js";
Expand Down Expand Up @@ -65,6 +69,10 @@ export type GetBundleStatsOptions = {
* Bypass cache and force re-analysis.
*/
force?: boolean;
/**
* esbuild target (e.g., "es2022", "es2020"). Defaults to "es2022".
*/
target?: string;
};

/**
Expand Down Expand Up @@ -167,6 +175,10 @@ export type GetBundleTrendOptions = {
* Bypass cache and force re-analysis.
*/
force?: boolean;
/**
* esbuild target (e.g., "es2022", "es2020"). Defaults to "es2022".
*/
target?: string;
};

/**
Expand Down Expand Up @@ -313,13 +325,17 @@ export async function getBundleStats(
registry,
platform: platformOption = "auto",
force = false,
target: targetOption,
} = options;

// Normalize platform.
const platform = normalizePlatform(
platformOption === "auto" ? undefined : platformOption,
);

// Normalize target.
const target = normalizeTarget(targetOption);

// Parse package specifier.
const { name: baseName, version: requestedVersion } =
parsePackageSpecifier(packageName);
Expand Down Expand Up @@ -367,6 +383,7 @@ export async function getBundleStats(
gzipLevel,
registry,
platform,
target,
});

// Store in cache.
Expand Down Expand Up @@ -404,13 +421,17 @@ export async function getBundleTrend(
registry,
platform: platformOption = "auto",
force = false,
target: targetOption,
} = options;

// Normalize platform.
const platform = normalizePlatform(
platformOption === "auto" ? undefined : platformOption,
);

// Normalize target.
const target = normalizeTarget(targetOption);

// Parse package name (ignore version if provided).
const { name: baseName, subpath } = parsePackageSpecifier(packageName);
const fullPackagePath = subpath ? `${baseName}/${subpath}` : baseName;
Expand Down Expand Up @@ -440,6 +461,7 @@ export async function getBundleTrend(
registry,
platform,
force,
target,
});

if (results.length === 0) {
Expand Down
Loading