Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: print file size grouped by environment #2683

Merged
merged 4 commits into from
Jun 24, 2024
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
9 changes: 8 additions & 1 deletion e2e/cases/performance/print-file-size/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ test.describe('should print file size correctly', async () => {
expect(logs.some((log) => log.includes('Gzipped size:'))).toBeTruthy();
});

test('should print size of multiple targets correctly', async () => {
test('should print size of multiple environments correctly', async () => {
await build({
cwd,
rsbuildConfig: {
Expand All @@ -59,6 +59,10 @@ test.describe('should print file size correctly', async () => {
});

// dist/index.html
expect(
logs.some((log) => log.includes('File') && log.includes('(web)')),
).toBeTruthy();

expect(
logs.some(
(log) =>
Expand All @@ -69,6 +73,9 @@ test.describe('should print file size correctly', async () => {
).toBeTruthy();

// dist/server/index.js
expect(
logs.some((log) => log.includes('File') && log.includes('(node)')),
).toBeTruthy();
expect(
logs.some(
(log) =>
Expand Down
165 changes: 92 additions & 73 deletions packages/core/src/plugins/fileSize.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,7 @@ import fs from 'node:fs';
import path from 'node:path';
import { JS_REGEX } from '@rsbuild/shared';
import { color } from '@rsbuild/shared';
import type {
MultiStats,
PrintFileSizeOptions,
Stats,
StatsAsset,
} from '@rsbuild/shared';
import type { PrintFileSizeOptions, Stats, StatsAsset } from '@rsbuild/shared';
chenjiahan marked this conversation as resolved.
Show resolved Hide resolved
import { CSS_REGEX, HTML_REGEX } from '../constants';
import { logger } from '../logger';
import type { RsbuildPlugin } from '../types';
Expand All @@ -30,22 +25,26 @@ const getAssetColor = (size: number) => {
return color.green;
};

async function printHeader(
function getHeader(
longestFileLength: number,
longestLabelLength: number,
environment: string,
) {
const longestLengths = [longestFileLength, longestLabelLength];
const headerRow = ['File', 'Size', 'Gzipped'].reduce((prev, cur, index) => {
const length = longestLengths[index];
let curLabel = cur;
if (length) {
curLabel =
cur.length < length ? cur + ' '.repeat(length - cur.length) : cur;
}
return `${prev + curLabel} `;
}, ' ');
const headerRow = [`File (${environment})`, 'Size', 'Gzipped'].reduce(
(prev, cur, index) => {
const length = longestLengths[index];
let curLabel = cur;
if (length) {
curLabel =
cur.length < length ? cur + ' '.repeat(length - cur.length) : cur;
}
return `${prev + curLabel} `;
},
' ',
);

logger.log(color.bold(color.blue(headerRow)));
return color.bold(color.blue(headerRow));
}

const calcFileSize = (len: number) => {
Expand All @@ -68,11 +67,13 @@ const coloringAssetName = (assetName: string) => {

async function printFileSizes(
config: PrintFileSizeOptions,
stats: Stats | MultiStats,
stats: Stats,
rootPath: string,
environment: string,
) {
const logs: string[] = [];
if (config.detail === false && config.total === false) {
return;
return logs;
}

const { default: gzipSize } = await import('@rsbuild/shared/gzip-size');
Expand All @@ -97,55 +98,51 @@ async function printFileSizes(
};
};

const multiStats = 'stats' in stats ? stats.stats : [stats];
const assets = multiStats
.map((stats) => {
const distPath = stats.compilation.outputOptions.path;
const getAssets = () => {
const distPath = stats.compilation.outputOptions.path;

if (!distPath) {
return [];
}
if (!distPath) {
return [];
}

const origin = stats.toJson({
all: false,
assets: true,
// TODO: need supported in rspack
// @ts-expect-error
cachedAssets: true,
groupAssetsByInfo: false,
groupAssetsByPath: false,
groupAssetsByChunk: false,
groupAssetsByExtension: false,
groupAssetsByEmitStatus: false,
});
const origin = stats.toJson({
all: false,
assets: true,
// TODO: need supported in rspack
// @ts-expect-error
cachedAssets: true,
groupAssetsByInfo: false,
groupAssetsByPath: false,
groupAssetsByChunk: false,
groupAssetsByExtension: false,
groupAssetsByEmitStatus: false,
});

const filteredAssets = origin.assets!.filter((asset) =>
filterAsset(asset.name),
);
const filteredAssets = origin.assets!.filter((asset) =>
filterAsset(asset.name),
);

const distFolder = path.relative(rootPath, distPath);
const distFolder = path.relative(rootPath, distPath);

return filteredAssets.map((asset) =>
formatAsset(asset, distPath, distFolder),
);
})
.reduce((single, all) => all.concat(single), []);
return filteredAssets.map((asset) =>
formatAsset(asset, distPath, distFolder),
);
};
const assets = getAssets();

if (assets.length === 0) {
return;
return logs;
}

assets.sort((a, b) => a.size - b.size);

logger.info('Production file sizes:\n');

const longestLabelLength = Math.max(...assets.map((a) => a.sizeLabel.length));
const longestFileLength = Math.max(
...assets.map((a) => (a.folder + path.sep + a.name).length),
);

if (config.detail !== false) {
printHeader(longestFileLength, longestLabelLength);
logs.push(getHeader(longestFileLength, longestLabelLength, environment));
}

let totalSize = 0;
Expand Down Expand Up @@ -174,7 +171,7 @@ async function printFileSizes(
fileNameLabel += rightPadding;
}

logger.log(` ${fileNameLabel} ${sizeLabel} ${gzipSizeLabel}`);
logs.push(` ${fileNameLabel} ${sizeLabel} ${gzipSizeLabel}`);
}
}

Expand All @@ -185,39 +182,61 @@ async function printFileSizes(
const gzippedSizeLabel = `${color.bold(
color.blue('Gzipped size:'),
)} ${calcFileSize(totalGzipSize)}`;
logger.log(`\n ${totalSizeLabel}\n ${gzippedSizeLabel}\n`);
logs.push(`\n ${totalSizeLabel}\n ${gzippedSizeLabel}\n`);
}

return logs;
}

export const pluginFileSize = (): RsbuildPlugin => ({
name: 'rsbuild:file-size',

setup(api) {
api.onAfterBuild(async ({ stats }) => {
const { printFileSize } = api.getNormalizedConfig().performance;

if (printFileSize === false) {
if (!stats) {
return;
}

const printFileSizeConfig =
typeof printFileSize === 'boolean'
? {
total: true,
detail: true,
const logs = await Promise.all(
Object.keys(api.context.environments).map(
async (environment, index) => {
const { printFileSize } = api.getNormalizedConfig({
environment,
}).performance;

const multiStats = 'stats' in stats ? stats.stats : [stats];

const printFileSizeConfig =
typeof printFileSize === 'boolean'
? {
total: true,
detail: true,
}
: printFileSize;

if (printFileSize) {
return printFileSizes(
printFileSizeConfig,
multiStats[index],
api.context.rootPath,
environment,
);
}
: printFileSize;

if (stats) {
try {
await printFileSizes(
printFileSizeConfig,
stats,
api.context.rootPath,
);
} catch (err) {
logger.warn('Failed to print file size.');
logger.warn(err as Error);
return [];
},
),
).catch((err) => {
logger.warn('Failed to print file size.');
logger.warn(err as Error);
return [];
});

if (logs.filter((log) => log.length).length) {
logger.info('Production file sizes:\n');
for (const statsLog of logs) {
for (const log of statsLog) {
logger.log(log);
}
}
}
});
Expand Down
Loading