@mui/internal-bundle-size-checker 1.0.9-canary.5 → 1.0.9-canary.51
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -2
- package/build/browser.d.ts +2 -0
- package/build/builder.d.ts +46 -0
- package/build/cli.d.ts +1 -0
- package/build/configLoader.d.ts +23 -0
- package/build/constants.d.ts +1 -0
- package/build/defineConfig.d.ts +8 -0
- package/build/fetchSnapshot.d.ts +7 -0
- package/build/fetchSnapshotWithFallback.d.ts +11 -0
- package/build/formatUtils.d.ts +6 -0
- package/build/git.d.ts +23 -0
- package/build/github.d.ts +2 -0
- package/build/index.d.ts +4 -0
- package/build/notifyPr.d.ts +12 -0
- package/build/renderMarkdownReport.d.ts +45 -0
- package/build/sizeDiff.d.ts +59 -0
- package/build/strings.d.ts +23 -0
- package/build/uploadSnapshot.d.ts +10 -0
- package/build/worker.d.ts +12 -0
- package/package.json +21 -27
- package/src/{viteBuilder.js → builder.js} +99 -34
- package/src/cli.js +169 -43
- package/src/configLoader.js +122 -43
- package/src/constants.js +1 -0
- package/src/fetchSnapshot.js +3 -61
- package/src/fetchSnapshotWithFallback.js +34 -0
- package/src/git.js +50 -0
- package/src/github.js +4 -1
- package/src/index.js +3 -9
- package/src/notifyPr.js +81 -0
- package/src/renderMarkdownReport.js +18 -24
- package/src/renderMarkdownReport.test.js +97 -80
- package/src/sizeDiff.js +1 -5
- package/src/strings.js +38 -0
- package/src/types.d.ts +12 -23
- package/src/uploadSnapshot.js +2 -2
- package/src/worker.js +13 -20
- package/tsconfig.build.json +15 -0
- package/tsconfig.json +2 -2
- package/src/webpackBuilder.js +0 -267
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import path from 'path';
|
|
2
|
-
import fs from 'fs/promises';
|
|
3
|
-
import * as zlib from 'zlib';
|
|
4
|
-
import { promisify } from 'util';
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import fs from 'node:fs/promises';
|
|
3
|
+
import * as zlib from 'node:zlib';
|
|
4
|
+
import { promisify } from 'node:util';
|
|
5
5
|
import { build, transformWithEsbuild } from 'vite';
|
|
6
6
|
import { visualizer } from 'rollup-plugin-visualizer';
|
|
7
|
+
import { escapeFilename } from './strings.js';
|
|
7
8
|
|
|
8
9
|
const gzipAsync = promisify(zlib.gzip);
|
|
9
10
|
|
|
@@ -25,13 +26,32 @@ const rootDir = process.cwd();
|
|
|
25
26
|
* @typedef {Record<string, ManifestChunk>} Manifest
|
|
26
27
|
*/
|
|
27
28
|
|
|
29
|
+
/**
|
|
30
|
+
* Creates a simple string replacement plugin
|
|
31
|
+
* @param {Record<string, string>} replacements - Object with string replacements
|
|
32
|
+
* @returns {import('vite').Plugin}
|
|
33
|
+
*/
|
|
34
|
+
function createReplacePlugin(replacements) {
|
|
35
|
+
return {
|
|
36
|
+
name: 'string-replace',
|
|
37
|
+
transform(code) {
|
|
38
|
+
let transformedCode = code;
|
|
39
|
+
for (const [search, replace] of Object.entries(replacements)) {
|
|
40
|
+
transformedCode = transformedCode.replaceAll(search, replace);
|
|
41
|
+
}
|
|
42
|
+
return transformedCode !== code ? transformedCode : null;
|
|
43
|
+
},
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
|
|
28
47
|
/**
|
|
29
48
|
* Creates vite configuration for bundle size checking
|
|
30
49
|
* @param {ObjectEntry} entry - Entry point (string or object)
|
|
31
50
|
* @param {CommandLineArgs} args
|
|
32
|
-
* @
|
|
51
|
+
* @param {Record<string, string>} [replacements] - String replacements to apply
|
|
52
|
+
* @returns {Promise<{ config:import('vite').InlineConfig, treemapPath: string }>}
|
|
33
53
|
*/
|
|
34
|
-
async function createViteConfig(entry, args) {
|
|
54
|
+
async function createViteConfig(entry, args, replacements = {}) {
|
|
35
55
|
const entryName = entry.id;
|
|
36
56
|
let entryContent;
|
|
37
57
|
|
|
@@ -59,29 +79,43 @@ async function createViteConfig(entry, args) {
|
|
|
59
79
|
const externalsArray = entry.externals || ['react', 'react-dom'];
|
|
60
80
|
|
|
61
81
|
// Ensure build directory exists
|
|
62
|
-
const outDir = path.join(rootDir, 'build', entryName);
|
|
82
|
+
const outDir = path.join(rootDir, 'build', escapeFilename(entryName));
|
|
63
83
|
await fs.mkdir(outDir, { recursive: true });
|
|
84
|
+
|
|
85
|
+
const treemapPath = path.join(outDir, 'treemap.html');
|
|
86
|
+
|
|
64
87
|
/**
|
|
65
88
|
* @type {import('vite').InlineConfig}
|
|
66
89
|
*/
|
|
67
|
-
const
|
|
90
|
+
const config = {
|
|
68
91
|
configFile: false,
|
|
69
92
|
root: rootDir,
|
|
70
93
|
|
|
71
94
|
build: {
|
|
72
95
|
write: true,
|
|
73
|
-
minify: true,
|
|
96
|
+
minify: args.debug ? 'esbuild' : true,
|
|
74
97
|
outDir,
|
|
75
98
|
emptyOutDir: true,
|
|
99
|
+
modulePreload: false,
|
|
76
100
|
rollupOptions: {
|
|
77
|
-
input:
|
|
78
|
-
|
|
101
|
+
input: {
|
|
102
|
+
ignore: '/ignore.ts',
|
|
103
|
+
bundle: '/entry.tsx',
|
|
104
|
+
},
|
|
105
|
+
output: {
|
|
106
|
+
// The output is for debugging purposes only. Remove all hashes to make it easier to compare two folders
|
|
107
|
+
// of build output.
|
|
108
|
+
entryFileNames: `assets/[name].js`,
|
|
109
|
+
chunkFileNames: `assets/[name].js`,
|
|
110
|
+
assetFileNames: `assets/[name].[ext]`,
|
|
111
|
+
},
|
|
112
|
+
external: (id) => externalsArray.some((ext) => id === ext || id.startsWith(`${ext}/`)),
|
|
79
113
|
plugins: [
|
|
80
114
|
...(args.analyze
|
|
81
115
|
? [
|
|
82
116
|
// File sizes are not accurate, use it only for relative comparison
|
|
83
117
|
visualizer({
|
|
84
|
-
filename:
|
|
118
|
+
filename: treemapPath,
|
|
85
119
|
title: `Bundle Size Analysis: ${entryName}`,
|
|
86
120
|
projectRoot: rootDir,
|
|
87
121
|
open: false,
|
|
@@ -100,19 +134,25 @@ async function createViteConfig(entry, args) {
|
|
|
100
134
|
|
|
101
135
|
esbuild: {
|
|
102
136
|
legalComments: 'none',
|
|
137
|
+
...(args.debug && {
|
|
138
|
+
minifyIdentifiers: false,
|
|
139
|
+
minifyWhitespace: false,
|
|
140
|
+
minifySyntax: true, // This enables tree-shaking and other safe optimizations
|
|
141
|
+
}),
|
|
103
142
|
},
|
|
104
143
|
|
|
105
144
|
define: {
|
|
106
|
-
'process.env.NODE_ENV': JSON.stringify(
|
|
145
|
+
'process.env.NODE_ENV': JSON.stringify('production'),
|
|
107
146
|
},
|
|
108
147
|
logLevel: args.verbose ? 'info' : 'silent',
|
|
109
148
|
// Add plugins to handle virtual entry points
|
|
110
149
|
plugins: [
|
|
150
|
+
createReplacePlugin(replacements),
|
|
111
151
|
{
|
|
112
152
|
name: 'virtual-entry',
|
|
113
153
|
resolveId(id) {
|
|
114
|
-
if (id === '/
|
|
115
|
-
return `\0virtual:
|
|
154
|
+
if (id === '/ignore.ts') {
|
|
155
|
+
return `\0virtual:ignore.ts`;
|
|
116
156
|
}
|
|
117
157
|
if (id === '/entry.tsx') {
|
|
118
158
|
return `\0virtual:entry.tsx`;
|
|
@@ -120,7 +160,9 @@ async function createViteConfig(entry, args) {
|
|
|
120
160
|
return null;
|
|
121
161
|
},
|
|
122
162
|
load(id) {
|
|
123
|
-
if (id === `\0virtual:
|
|
163
|
+
if (id === `\0virtual:ignore.ts`) {
|
|
164
|
+
// ignore chunk will contain the vite preload code, we can ignore this chunk in the output
|
|
165
|
+
// See https://github.com/vitejs/vite/issues/18551
|
|
124
166
|
return transformWithEsbuild(`import('/entry.tsx').then(console.log)`, id);
|
|
125
167
|
}
|
|
126
168
|
if (id === `\0virtual:entry.tsx`) {
|
|
@@ -132,7 +174,7 @@ async function createViteConfig(entry, args) {
|
|
|
132
174
|
],
|
|
133
175
|
};
|
|
134
176
|
|
|
135
|
-
return {
|
|
177
|
+
return { config, treemapPath };
|
|
136
178
|
}
|
|
137
179
|
|
|
138
180
|
/**
|
|
@@ -173,32 +215,49 @@ function walkDependencyTree(chunkKey, manifest, visited = new Set()) {
|
|
|
173
215
|
|
|
174
216
|
/**
|
|
175
217
|
* Process vite output to extract bundle sizes
|
|
176
|
-
* @param {
|
|
218
|
+
* @param {import('vite').Rollup.RollupOutput['output']} output - The Vite output
|
|
177
219
|
* @param {string} entryName - The entry name
|
|
178
|
-
* @returns {Promise<Map<string,
|
|
220
|
+
* @returns {Promise<Map<string, SizeSnapshotEntry>>} - Map of bundle names to size information
|
|
179
221
|
*/
|
|
180
|
-
async function processBundleSizes(
|
|
222
|
+
async function processBundleSizes(output, entryName) {
|
|
223
|
+
const chunksByFileName = new Map(output.map((chunk) => [chunk.fileName, chunk]));
|
|
224
|
+
|
|
181
225
|
// Read the manifest file to find the generated chunks
|
|
182
|
-
const
|
|
183
|
-
|
|
226
|
+
const manifestChunk = chunksByFileName.get('.vite/manifest.json');
|
|
227
|
+
if (manifestChunk?.type !== 'asset') {
|
|
228
|
+
throw new Error(`Manifest file not found in output for entry: ${entryName}`);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
const manifestContent =
|
|
232
|
+
typeof manifestChunk.source === 'string'
|
|
233
|
+
? manifestChunk.source
|
|
234
|
+
: new TextDecoder().decode(manifestChunk.source);
|
|
235
|
+
|
|
184
236
|
/** @type {Manifest} */
|
|
185
237
|
const manifest = JSON.parse(manifestContent);
|
|
186
238
|
|
|
187
239
|
// Find the main entry point JS file in the manifest
|
|
188
|
-
const mainEntry = manifest[
|
|
240
|
+
const mainEntry = Object.entries(manifest).find(([_, entry]) => entry.name === 'bundle');
|
|
189
241
|
|
|
190
242
|
if (!mainEntry) {
|
|
191
243
|
throw new Error(`No main entry found in manifest for ${entryName}`);
|
|
192
244
|
}
|
|
193
245
|
|
|
194
246
|
// Walk the dependency tree to get all chunks that are part of this entry
|
|
195
|
-
const allChunks = walkDependencyTree(
|
|
247
|
+
const allChunks = walkDependencyTree(mainEntry[0], manifest);
|
|
196
248
|
|
|
197
249
|
// Process each chunk in the dependency tree in parallel
|
|
198
250
|
const chunkPromises = Array.from(allChunks, async (chunkKey) => {
|
|
199
251
|
const chunk = manifest[chunkKey];
|
|
200
|
-
const
|
|
201
|
-
|
|
252
|
+
const outputChunk = chunksByFileName.get(chunk.file);
|
|
253
|
+
if (outputChunk?.type !== 'chunk') {
|
|
254
|
+
throw new Error(`Output chunk not found for ${chunk.file}`);
|
|
255
|
+
}
|
|
256
|
+
const fileContent = outputChunk.code;
|
|
257
|
+
if (chunk.name === 'preload-helper') {
|
|
258
|
+
// Skip the preload-helper chunk as it is not relevant for bundle size
|
|
259
|
+
return null;
|
|
260
|
+
}
|
|
202
261
|
|
|
203
262
|
// Calculate sizes
|
|
204
263
|
const parsed = Buffer.byteLength(fileContent);
|
|
@@ -206,28 +265,34 @@ async function processBundleSizes(outDir, entryName) {
|
|
|
206
265
|
const gzipSize = Buffer.byteLength(gzipBuffer);
|
|
207
266
|
|
|
208
267
|
// Use chunk key as the name, or fallback to entry name for main chunk
|
|
209
|
-
const chunkName =
|
|
268
|
+
const chunkName = chunk.name === 'bundle' ? entryName : chunk.name || chunkKey;
|
|
210
269
|
return /** @type {const} */ ([chunkName, { parsed, gzip: gzipSize }]);
|
|
211
270
|
});
|
|
212
271
|
|
|
213
272
|
const chunkEntries = await Promise.all(chunkPromises);
|
|
214
|
-
return new Map(chunkEntries);
|
|
273
|
+
return new Map(/** @type {[string, SizeSnapshotEntry][]} */ (chunkEntries.filter(Boolean)));
|
|
215
274
|
}
|
|
216
275
|
|
|
217
276
|
/**
|
|
218
277
|
* Get sizes for a vite bundle
|
|
219
278
|
* @param {ObjectEntry} entry - The entry configuration
|
|
220
279
|
* @param {CommandLineArgs} args - Command line arguments
|
|
221
|
-
* @
|
|
280
|
+
* @param {Record<string, string>} [replacements] - String replacements to apply
|
|
281
|
+
* @returns {Promise<{ sizes: Map<string, SizeSnapshotEntry>, treemapPath: string }>}
|
|
222
282
|
*/
|
|
223
|
-
export async function
|
|
283
|
+
export async function getBundleSizes(entry, args, replacements) {
|
|
224
284
|
// Create vite configuration
|
|
225
|
-
const {
|
|
226
|
-
const outDir = path.join(rootDir, 'build', entry.id);
|
|
285
|
+
const { config, treemapPath } = await createViteConfig(entry, args, replacements);
|
|
227
286
|
|
|
228
287
|
// Run vite build
|
|
229
|
-
await build(
|
|
288
|
+
const { output } = /** @type {import('vite').Rollup.RollupOutput} */ (await build(config));
|
|
289
|
+
const manifestChunk = output.find((chunk) => chunk.fileName === '.vite/manifest.json');
|
|
290
|
+
if (!manifestChunk) {
|
|
291
|
+
throw new Error(`Manifest file not found in output for entry: ${entry.id}`);
|
|
292
|
+
}
|
|
230
293
|
|
|
231
294
|
// Process the output to get bundle sizes
|
|
232
|
-
|
|
295
|
+
const sizes = await processBundleSizes(output, entry.id);
|
|
296
|
+
|
|
297
|
+
return { sizes, treemapPath };
|
|
233
298
|
}
|
package/src/cli.js
CHANGED
|
@@ -1,17 +1,50 @@
|
|
|
1
1
|
// @ts-check
|
|
2
2
|
|
|
3
|
-
import path from 'path';
|
|
4
|
-
import os from 'os';
|
|
5
|
-
import fs from 'fs/promises';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import os from 'node:os';
|
|
5
|
+
import fs from 'node:fs/promises';
|
|
6
6
|
import yargs from 'yargs';
|
|
7
7
|
import { Piscina } from 'piscina';
|
|
8
8
|
import micromatch from 'micromatch';
|
|
9
|
-
import
|
|
10
|
-
import
|
|
9
|
+
import envCi from 'env-ci';
|
|
10
|
+
import { pathToFileURL } from 'node:url';
|
|
11
|
+
import chalk from 'chalk';
|
|
11
12
|
import { loadConfig } from './configLoader.js';
|
|
12
13
|
import { uploadSnapshot } from './uploadSnapshot.js';
|
|
13
14
|
import { renderMarkdownReport } from './renderMarkdownReport.js';
|
|
14
15
|
import { octokit } from './github.js';
|
|
16
|
+
import { getCurrentRepoInfo } from './git.js';
|
|
17
|
+
import { notifyPr } from './notifyPr.js';
|
|
18
|
+
import { DASHBOARD_ORIGIN } from './constants.js';
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* @param {string} repo
|
|
22
|
+
* @param {number} prNumber
|
|
23
|
+
* @param {string} bundleSizeInfo
|
|
24
|
+
*/
|
|
25
|
+
function formatComment(repo, prNumber, bundleSizeInfo) {
|
|
26
|
+
return [
|
|
27
|
+
'## Bundle size report',
|
|
28
|
+
bundleSizeInfo,
|
|
29
|
+
'<hr>',
|
|
30
|
+
`Check out the [code infra dashboard](${DASHBOARD_ORIGIN}/repository/${repo}/prs/${prNumber}) for more information about this PR.`,
|
|
31
|
+
].join('\n\n');
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
*/
|
|
36
|
+
function getCiInfo() {
|
|
37
|
+
const ciInfo = envCi();
|
|
38
|
+
if (!ciInfo.isCi) {
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
switch (ciInfo.name) {
|
|
42
|
+
case 'CircleCI':
|
|
43
|
+
return ciInfo;
|
|
44
|
+
default:
|
|
45
|
+
throw new Error(`Unsupported CI environment: ${ciInfo.name}`);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
15
48
|
|
|
16
49
|
/**
|
|
17
50
|
* @typedef {import('./sizeDiff.js').SizeSnapshot} SizeSnapshot
|
|
@@ -23,32 +56,12 @@ const DEFAULT_CONCURRENCY = os.availableParallelism();
|
|
|
23
56
|
const rootDir = process.cwd();
|
|
24
57
|
|
|
25
58
|
/**
|
|
26
|
-
*
|
|
27
|
-
* @returns {Promise<{owner: string | null, repo: string | null}>}
|
|
28
|
-
*/
|
|
29
|
-
async function getCurrentRepoInfo() {
|
|
30
|
-
try {
|
|
31
|
-
const { stdout } = await execa('git', ['remote', 'get-url', 'origin']);
|
|
32
|
-
const parsed = gitUrlParse(stdout.trim());
|
|
33
|
-
return {
|
|
34
|
-
owner: parsed.owner,
|
|
35
|
-
repo: parsed.name,
|
|
36
|
-
};
|
|
37
|
-
} catch (error) {
|
|
38
|
-
return {
|
|
39
|
-
owner: null,
|
|
40
|
-
repo: null,
|
|
41
|
-
};
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
/**
|
|
46
|
-
* creates size snapshot for every bundle that built with webpack
|
|
59
|
+
* creates size snapshot for every bundle
|
|
47
60
|
* @param {CommandLineArgs} args
|
|
48
61
|
* @param {NormalizedBundleSizeCheckerConfig} config - The loaded configuration
|
|
49
|
-
* @returns {Promise<Array<[string,
|
|
62
|
+
* @returns {Promise<Array<[string, SizeSnapshotEntry]>>}
|
|
50
63
|
*/
|
|
51
|
-
async function
|
|
64
|
+
async function getBundleSizes(args, config) {
|
|
52
65
|
const worker = new Piscina({
|
|
53
66
|
filename: new URL('./worker.js', import.meta.url).href,
|
|
54
67
|
maxThreads: args.concurrency || DEFAULT_CONCURRENCY,
|
|
@@ -89,13 +102,60 @@ async function getWebpackSizes(args, config) {
|
|
|
89
102
|
|
|
90
103
|
const sizeArrays = await Promise.all(
|
|
91
104
|
validEntries.map((entry, index) =>
|
|
92
|
-
worker.run({ entry, args, index, total: validEntries.length }),
|
|
105
|
+
worker.run({ entry, args, index, total: validEntries.length, replace: config.replace }),
|
|
93
106
|
),
|
|
94
107
|
);
|
|
95
108
|
|
|
96
109
|
return sizeArrays.flat();
|
|
97
110
|
}
|
|
98
111
|
|
|
112
|
+
/**
|
|
113
|
+
* Posts initial "in progress" PR comment with CircleCI build information
|
|
114
|
+
* @returns {Promise<void>}
|
|
115
|
+
*/
|
|
116
|
+
async function postInitialPrComment() {
|
|
117
|
+
// /** @type {envCi.CircleCiEnv} */
|
|
118
|
+
const ciInfo = getCiInfo();
|
|
119
|
+
|
|
120
|
+
if (!ciInfo || !ciInfo.isPr) {
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// In CI PR builds, all required info must be present
|
|
125
|
+
if (!ciInfo.slug || !ciInfo.pr) {
|
|
126
|
+
throw new Error('PR commenting enabled but repository information missing in CI PR build');
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const prNumber = Number(ciInfo.pr);
|
|
130
|
+
const circleBuildNum = process.env.CIRCLE_BUILD_NUM;
|
|
131
|
+
const circleBuildUrl = process.env.CIRCLE_BUILD_URL;
|
|
132
|
+
|
|
133
|
+
if (!circleBuildNum || !circleBuildUrl) {
|
|
134
|
+
throw new Error(
|
|
135
|
+
'PR commenting enabled but CircleCI environment variables missing in CI PR build',
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
try {
|
|
140
|
+
// eslint-disable-next-line no-console
|
|
141
|
+
console.log('Posting initial PR comment...');
|
|
142
|
+
|
|
143
|
+
const initialComment = formatComment(
|
|
144
|
+
ciInfo.slug,
|
|
145
|
+
prNumber,
|
|
146
|
+
`Bundle size will be reported once [CircleCI build #${circleBuildNum}](${circleBuildUrl}) finishes.\n\nStatus: 🟠 Processing...`,
|
|
147
|
+
);
|
|
148
|
+
|
|
149
|
+
await notifyPr(ciInfo.slug, prNumber, 'bundle-size-report', initialComment);
|
|
150
|
+
|
|
151
|
+
// eslint-disable-next-line no-console
|
|
152
|
+
console.log(`Initial PR comment posted for PR #${prNumber}`);
|
|
153
|
+
} catch (/** @type {any} */ error) {
|
|
154
|
+
console.error('Failed to post initial PR comment:', error.message);
|
|
155
|
+
// Don't fail the build for comment failures
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
99
159
|
/**
|
|
100
160
|
* Report command handler
|
|
101
161
|
* @param {ReportCommandArgs} argv - Command line arguments
|
|
@@ -106,7 +166,7 @@ async function reportCommand(argv) {
|
|
|
106
166
|
// Get current repo info and coerce with provided arguments
|
|
107
167
|
const currentRepo = await getCurrentRepoInfo();
|
|
108
168
|
const owner = argOwner ?? currentRepo.owner;
|
|
109
|
-
const repo = argRepo ?? currentRepo.
|
|
169
|
+
const repo = argRepo ?? currentRepo.name;
|
|
110
170
|
|
|
111
171
|
if (typeof pr !== 'number') {
|
|
112
172
|
throw new Error('Invalid pull request number. Please provide a valid --pr option.');
|
|
@@ -126,8 +186,18 @@ async function reportCommand(argv) {
|
|
|
126
186
|
pull_number: pr,
|
|
127
187
|
});
|
|
128
188
|
|
|
189
|
+
const getMergeBaseFromGithubApi = async (
|
|
190
|
+
/** @type {string} */ base,
|
|
191
|
+
/** @type {string} */ head,
|
|
192
|
+
) => {
|
|
193
|
+
const { data } = await octokit.repos.compareCommits({ owner, repo, base, head });
|
|
194
|
+
return data.merge_base_commit.sha;
|
|
195
|
+
};
|
|
196
|
+
|
|
129
197
|
// Generate and print the markdown report
|
|
130
|
-
const report = await renderMarkdownReport(prInfo
|
|
198
|
+
const report = await renderMarkdownReport(prInfo, {
|
|
199
|
+
getMergeBase: getMergeBaseFromGithubApi,
|
|
200
|
+
});
|
|
131
201
|
// eslint-disable-next-line no-console
|
|
132
202
|
console.log(report);
|
|
133
203
|
}
|
|
@@ -143,18 +213,27 @@ async function run(argv) {
|
|
|
143
213
|
|
|
144
214
|
const config = await loadConfig(rootDir);
|
|
145
215
|
|
|
216
|
+
// Post initial PR comment if enabled and in CI environment
|
|
217
|
+
if (config && config.comment) {
|
|
218
|
+
await postInitialPrComment();
|
|
219
|
+
}
|
|
220
|
+
|
|
146
221
|
// eslint-disable-next-line no-console
|
|
147
222
|
console.log(`Starting bundle size snapshot creation with ${concurrency} workers...`);
|
|
148
223
|
|
|
149
|
-
const
|
|
150
|
-
const
|
|
224
|
+
const bundleSizes = await getBundleSizes(argv, config);
|
|
225
|
+
const sortedBundleSizes = Object.fromEntries(
|
|
226
|
+
bundleSizes.sort((a, b) => a[0].localeCompare(b[0])),
|
|
227
|
+
);
|
|
151
228
|
|
|
152
229
|
// Ensure output directory exists
|
|
153
230
|
await fs.mkdir(path.dirname(snapshotDestPath), { recursive: true });
|
|
154
|
-
await fs.writeFile(snapshotDestPath, JSON.stringify(
|
|
231
|
+
await fs.writeFile(snapshotDestPath, JSON.stringify(sortedBundleSizes, null, 2));
|
|
155
232
|
|
|
156
233
|
// eslint-disable-next-line no-console
|
|
157
|
-
console.log(
|
|
234
|
+
console.log(
|
|
235
|
+
`Bundle size snapshot written to ${chalk.underline(pathToFileURL(snapshotDestPath))}`,
|
|
236
|
+
);
|
|
158
237
|
|
|
159
238
|
// Upload the snapshot if upload configuration is provided and not null
|
|
160
239
|
if (config && config.upload) {
|
|
@@ -169,6 +248,57 @@ async function run(argv) {
|
|
|
169
248
|
// Exit with error code to indicate failure
|
|
170
249
|
process.exit(1);
|
|
171
250
|
}
|
|
251
|
+
} else {
|
|
252
|
+
// eslint-disable-next-line no-console
|
|
253
|
+
console.log('No upload configuration provided, skipping upload.');
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// Post PR comment if enabled and in CI environment
|
|
257
|
+
if (config && config.comment) {
|
|
258
|
+
const ciInfo = getCiInfo();
|
|
259
|
+
|
|
260
|
+
// Skip silently if not in CI or not a PR
|
|
261
|
+
if (!ciInfo || !ciInfo.isPr) {
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// In CI PR builds, all required info must be present
|
|
266
|
+
if (!ciInfo.slug || !ciInfo.pr) {
|
|
267
|
+
throw new Error('PR commenting enabled but repository information missing in CI PR build');
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
const prNumber = Number(ciInfo.pr);
|
|
271
|
+
|
|
272
|
+
// eslint-disable-next-line no-console
|
|
273
|
+
console.log('Generating PR comment with bundle size changes...');
|
|
274
|
+
|
|
275
|
+
// Get tracked bundles from config
|
|
276
|
+
const trackedBundles = config.entrypoints
|
|
277
|
+
.filter((entry) => entry.track === true)
|
|
278
|
+
.map((entry) => entry.id);
|
|
279
|
+
|
|
280
|
+
// Get PR info for renderMarkdownReport
|
|
281
|
+
const { data: prInfo } = await octokit.pulls.get({
|
|
282
|
+
owner: ciInfo.slug.split('/')[0],
|
|
283
|
+
repo: ciInfo.slug.split('/')[1],
|
|
284
|
+
pull_number: prNumber,
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
// Generate markdown report
|
|
288
|
+
const report = await renderMarkdownReport(prInfo, {
|
|
289
|
+
track: trackedBundles.length > 0 ? trackedBundles : undefined,
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
// Post or update PR comment
|
|
293
|
+
await notifyPr(
|
|
294
|
+
ciInfo.slug,
|
|
295
|
+
prNumber,
|
|
296
|
+
'bundle-size-report',
|
|
297
|
+
formatComment(ciInfo.slug, prNumber, report),
|
|
298
|
+
);
|
|
299
|
+
|
|
300
|
+
// eslint-disable-next-line no-console
|
|
301
|
+
console.log(`PR comment posted/updated for PR #${prNumber}`);
|
|
172
302
|
}
|
|
173
303
|
}
|
|
174
304
|
|
|
@@ -181,12 +311,7 @@ yargs(process.argv.slice(2))
|
|
|
181
311
|
return cmdYargs
|
|
182
312
|
.option('analyze', {
|
|
183
313
|
default: false,
|
|
184
|
-
describe: 'Creates a
|
|
185
|
-
type: 'boolean',
|
|
186
|
-
})
|
|
187
|
-
.option('accurateBundles', {
|
|
188
|
-
default: false,
|
|
189
|
-
describe: 'Displays used bundles accurately at the cost of more CPU cycles.',
|
|
314
|
+
describe: 'Creates a report for each bundle.',
|
|
190
315
|
type: 'boolean',
|
|
191
316
|
})
|
|
192
317
|
.option('verbose', {
|
|
@@ -194,9 +319,10 @@ yargs(process.argv.slice(2))
|
|
|
194
319
|
describe: 'Show more detailed information during compilation.',
|
|
195
320
|
type: 'boolean',
|
|
196
321
|
})
|
|
197
|
-
.option('
|
|
322
|
+
.option('debug', {
|
|
198
323
|
default: false,
|
|
199
|
-
describe:
|
|
324
|
+
describe:
|
|
325
|
+
'Build with readable output (no name mangling or whitespace collapse, but still tree-shake).',
|
|
200
326
|
type: 'boolean',
|
|
201
327
|
})
|
|
202
328
|
.option('output', {
|