Skip to content

Commit 305aa29

Browse files
author
changfeng
committed
feat: output duplicated pkg size
1 parent 847d19e commit 305aa29

File tree

4 files changed

+95
-9
lines changed

4 files changed

+95
-9
lines changed

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@
6868
},
6969
"dependencies": {
7070
"@rollup/pluginutils": "^5.0.2",
71+
"axios": "^1.4.0",
7172
"consola": "^3.1.0",
7273
"picocolors": "^1.0.0",
7374
"semver": "^7.5.1",

pnpm-lock.yaml

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/index.ts

Lines changed: 39 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import type { RollupPlugin } from 'unplugin';
99
import { createUnplugin } from 'unplugin';
1010
import { workspaceRoot } from 'workspace-root';
1111

12-
import { memoizeAsync } from './utils';
12+
import { memoizeAsync, getPkgSize as _getPkgSize } from './utils';
1313

1414
export interface Options {}
1515

@@ -58,6 +58,22 @@ const formatImporter = memoizeAsync(async (importer: string) => {
5858
return formattedImporter;
5959
});
6060

61+
const getPkgSize = memoizeAsync(_getPkgSize);
62+
63+
function colorizeSize(kb: number) {
64+
if (Number.isNaN(kb)) return '';
65+
66+
let colorFunc: (str: string) => string;
67+
if (kb > 1000) {
68+
colorFunc = c.red;
69+
} else if (kb > 100) {
70+
colorFunc = c.yellow;
71+
} else {
72+
colorFunc = c.green;
73+
}
74+
return `(${colorFunc(`${kb}kb`)})`;
75+
}
76+
6177
export default createUnplugin<Options | undefined>(() => {
6278
const name = 'unplugin-detect-duplicated-deps';
6379
let isVitePlugin = false;
@@ -103,7 +119,7 @@ export default createUnplugin<Options | undefined>(() => {
103119
}
104120
};
105121

106-
const buildEnd: RollupPlugin['buildEnd'] = function () {
122+
const buildEnd: RollupPlugin['buildEnd'] = async function () {
107123
const duplicatedPackages: string[] = [];
108124
for (const [packageName, versionsMap] of packageToVersionsMap.entries()) {
109125
if (versionsMap.size > 1) {
@@ -119,9 +135,7 @@ export default createUnplugin<Options | undefined>(() => {
119135
`multiple versions of ${formattedDuplicatedPackageNames} is bundled!`,
120136
];
121137

122-
for (const duplicatedPackage of duplicatedPackages) {
123-
warningMessages.push(`\n ${c.magenta(duplicatedPackage)}:`);
124-
138+
const promises = duplicatedPackages.map(async (duplicatedPackage) => {
125139
const sortedVersions = [...packageToVersionsMap.get(duplicatedPackage)!.keys()].sort(
126140
(a, b) => (gt(a, b) ? 1 : -1),
127141
);
@@ -133,7 +147,8 @@ export default createUnplugin<Options | undefined>(() => {
133147
}
134148
});
135149

136-
for (const version of sortedVersions) {
150+
let totalSize = 0;
151+
const _promises = sortedVersions.map(async (version) => {
137152
const importers = Array.from(
138153
packageToVersionsMap.get(duplicatedPackage)!.get(version)!,
139154
);
@@ -144,9 +159,23 @@ export default createUnplugin<Options | undefined>(() => {
144159
.filter((importer) => importer !== `${duplicatedPackage}@${version}`)
145160
.map((name) => c.green(name))
146161
.join(', ');
147-
warningMessages.push(` - ${formattedVersion} imported by ${formattedImporters}`);
148-
}
149-
}
162+
const pkgSize = await getPkgSize(duplicatedPackage, version);
163+
totalSize += pkgSize;
164+
warningMessages.push(
165+
// prettier-ignore
166+
` - ${formattedVersion}${colorizeSize(pkgSize)} imported by ${formattedImporters}`,
167+
);
168+
});
169+
await Promise.all(_promises);
170+
171+
warningMessages.splice(
172+
warningMessages.length - sortedVersions.length,
173+
0,
174+
`\n ${c.magenta(duplicatedPackage)}${colorizeSize(totalSize)}:`,
175+
);
176+
});
177+
await Promise.all(promises);
178+
150179
// remove vite output dim colorize
151180
// eslint-disable-next-line unicorn/escape-case, unicorn/no-hex-escape
152181
process.stdout.write(`\x1b[0m${isVitePlugin ? '\n' : ''}`);
@@ -156,6 +185,7 @@ export default createUnplugin<Options | undefined>(() => {
156185
getWorkspaceRootFolder.destroy();
157186
getPackageInfo.destroy();
158187
formatImporter.destroy();
188+
getPkgSize.destroy();
159189
};
160190

161191
return {

src/utils.ts

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
import type { AxiosResponse } from 'axios';
2+
import axios from 'axios';
3+
14
export function memoizeAsync<F extends (...params: any[]) => Promise<any>>(f: F) {
25
const cache = new Map<Parameters<F>[0], Promise<Awaited<ReturnType<F>>>>();
36
const memoizedFuncName = `memoized ${f.name}`;
@@ -27,3 +30,52 @@ export function memoizeAsync<F extends (...params: any[]) => Promise<any>>(f: F)
2730

2831
return memoizedFunc;
2932
}
33+
34+
export interface Asset {
35+
gzip: number;
36+
name: string;
37+
size: number;
38+
type: string;
39+
}
40+
41+
export interface DependencySize {
42+
approximateSize: number;
43+
name: string;
44+
}
45+
46+
export interface GetPkgSizeResponseData {
47+
assets: Asset[];
48+
dependencyCount: number;
49+
dependencySizes: DependencySize[];
50+
description: string;
51+
gzip: number;
52+
hasJSModule: boolean;
53+
hasJSNext: boolean;
54+
hasSideEffects: boolean;
55+
isModuleType: boolean;
56+
name: string;
57+
repository: string;
58+
scoped: boolean;
59+
size?: number;
60+
version: string;
61+
}
62+
63+
export async function getPkgSize(name: string, version: string) {
64+
let resp: AxiosResponse<GetPkgSizeResponseData>;
65+
try {
66+
resp = await axios.get<GetPkgSizeResponseData>(
67+
`https://bundlephobia.com/api/size?package=${name}@${version}`,
68+
{
69+
timeout: 3000,
70+
},
71+
);
72+
} catch {
73+
return Number.NaN;
74+
}
75+
76+
const bytes = resp.data.size;
77+
if (typeof bytes !== 'number') return Number.NaN;
78+
79+
const kb = bytes / 1000;
80+
return kb;
81+
}

0 commit comments

Comments
 (0)