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
8 changes: 4 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,9 @@
"test:coverage": "lerna run test:coverage"
},
"devDependencies": {
"@versini/dev-dependencies-common": "9.2.3",
"@versini/dev-dependencies-server": "8.0.26",
"@versini/dev-dependencies-types": "4.1.2"
"@versini/dev-dependencies-common": "9.2.4",
"@versini/dev-dependencies-server": "8.0.27",
"@versini/dev-dependencies-types": "4.1.3"
},
"packageManager": "pnpm@10.28.2"
"packageManager": "pnpm@10.29.3"
}
4 changes: 2 additions & 2 deletions packages/bundlecheck/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,9 @@
"@node-cli/logger": "workspace:*",
"@node-cli/parser": "workspace:*",
"better-sqlite3": "12.6.2",
"esbuild": "0.27.2",
"esbuild": "0.27.3",
"kleur": "4.1.5",
"semver": "7.7.3"
"semver": "7.7.4"
},
"publishConfig": {
"access": "public"
Expand Down
113 changes: 113 additions & 0 deletions packages/bundlecheck/src/__tests__/bundler.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1126,5 +1126,118 @@ describe("checkBundleSize", () => {

expect(result.platform).toBe("browser");
});

it("should detect browser platform when engines.node is present but react is in peerDependencies", async () => {
vi.mocked(fs.readFileSync).mockReturnValue(
JSON.stringify({
name: "test-package",
version: "1.0.0",
main: "./dist/index.js",
dependencies: {},
peerDependencies: {
react: "^18.0.0",
"react-dom": "^18.0.0",
},
engines: {
node: ">=18.17.0",
},
}),
);

const result = await checkBundleSize({
packageName: "test-package",
});

expect(result.platform).toBe("browser");
expect(result.gzipSize).not.toBeNull();
});

it("should detect browser platform when engines.node is present but react is in dependencies", async () => {
vi.mocked(fs.readFileSync).mockReturnValue(
JSON.stringify({
name: "test-package",
version: "1.0.0",
main: "./dist/index.js",
dependencies: { react: "^18.0.0" },
peerDependencies: {},
engines: {
node: ">=18.0.0",
},
}),
);

const result = await checkBundleSize({
packageName: "test-package",
});

expect(result.platform).toBe("browser");
expect(result.gzipSize).not.toBeNull();
});

it("should detect browser platform when engines.node is present but vue is in dependencies", async () => {
vi.mocked(fs.readFileSync).mockReturnValue(
JSON.stringify({
name: "test-package",
version: "1.0.0",
main: "./dist/index.js",
dependencies: { vue: "^3.0.0" },
peerDependencies: {},
engines: {
node: ">=16.0.0",
},
}),
);

const result = await checkBundleSize({
packageName: "test-package",
});

expect(result.platform).toBe("browser");
expect(result.gzipSize).not.toBeNull();
});

it("should detect browser platform when engines.node is present but svelte is in peerDependencies", async () => {
vi.mocked(fs.readFileSync).mockReturnValue(
JSON.stringify({
name: "test-package",
version: "1.0.0",
main: "./dist/index.js",
dependencies: {},
peerDependencies: { svelte: "^4.0.0" },
engines: {
node: ">=18.0.0",
},
}),
);

const result = await checkBundleSize({
packageName: "test-package",
});

expect(result.platform).toBe("browser");
expect(result.gzipSize).not.toBeNull();
});

it("should still auto-detect node when engines.node is present and no browser framework deps", async () => {
vi.mocked(fs.readFileSync).mockReturnValue(
JSON.stringify({
name: "test-package",
version: "1.0.0",
main: "./dist/index.js",
dependencies: { lodash: "^4.0.0" },
peerDependencies: {},
engines: {
node: ">=18.0.0",
},
}),
);

const result = await checkBundleSize({
packageName: "test-package",
});

expect(result.platform).toBe("node");
expect(result.gzipSize).toBeNull();
});
});
});
20 changes: 18 additions & 2 deletions packages/bundlecheck/src/bundler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { promisify } from "node:util";
import zlib from "node:zlib";
import * as esbuild from "esbuild";
import {
BROWSER_FRAMEWORK_DEPS,
DEFAULT_EXTERNALS,
DEFAULT_TARGET,
EXTERNAL_SUBPATHS,
Expand Down Expand Up @@ -680,8 +681,23 @@ export async function checkBundleSize(
if (explicitPlatform) {
platform = explicitPlatform;
} else if (pkgInfo.engines?.node && !pkgInfo.engines?.browser) {
// Package specifies node engine without browser - likely a Node.js package.
platform = "node";
/**
* Package specifies node engine without browser - potentially a Node.js
* package. However, many browser packages also specify engines.node for
* build tooling or SSR compatibility (e.g., @clerk/clerk-react). Check
* if the package has browser-framework dependencies which indicate it's
* a browser package despite having engines.node.
*/
const allDepNames = [
...Object.keys(pkgInfo.dependencies),
...Object.keys(pkgInfo.peerDependencies),
];
const hasBrowserFrameworkDep = allDepNames.some((dep) =>
BROWSER_FRAMEWORK_DEPS.includes(dep),
);
if (!hasBrowserFrameworkDep) {
platform = "node";
}
}

// Collect all dependency names (prod + peer).
Expand Down
16 changes: 16 additions & 0 deletions packages/bundlecheck/src/defaults.ts
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,22 @@ export function normalizeTarget(target: string | undefined): string {
return target.toLowerCase().trim();
}

/**
* Browser framework packages used for platform auto-detection. If a package has
* any of these in its dependencies or peerDependencies, it's treated as a
* browser package even if it specifies engines.node (many browser packages set
* engines.node for build tooling or SSR compatibility).
*/
export const BROWSER_FRAMEWORK_DEPS = [
"react",
"react-dom",
"vue",
"svelte",
"@angular/core",
"preact",
"solid-js",
];

/**
* Base packages to auto-detect for externalization. These are checked against
* the package's dependencies/peerDependencies.
Expand Down
Loading