@mui/internal-bundle-size-checker 1.0.9-canary.1 → 1.0.9-canary.11
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/package.json +6 -3
- package/src/cli.js +131 -47
- package/src/fetchSnapshot.js +10 -8
- package/src/github.js +11 -0
- package/src/renderMarkdownReport.js +12 -3
- package/src/renderMarkdownReport.test.js +39 -5
- package/src/types.d.ts +6 -0
- package/.eslintrc.cjs +0 -14
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mui/internal-bundle-size-checker",
|
|
3
|
-
"version": "1.0.9-canary.
|
|
3
|
+
"version": "1.0.9-canary.11",
|
|
4
4
|
"description": "Bundle size checker for MUI packages.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./src/index.js",
|
|
@@ -21,9 +21,11 @@
|
|
|
21
21
|
"dependencies": {
|
|
22
22
|
"@aws-sdk/client-s3": "^3.515.0",
|
|
23
23
|
"@aws-sdk/credential-providers": "^3.787.0",
|
|
24
|
-
"@babel/core": "^7.27.
|
|
24
|
+
"@babel/core": "^7.27.4",
|
|
25
25
|
"@babel/preset-react": "^7.18.6",
|
|
26
26
|
"@babel/preset-typescript": "^7.27.1",
|
|
27
|
+
"@octokit/auth-action": "^6.0.1",
|
|
28
|
+
"@octokit/rest": "^22.0.0",
|
|
27
29
|
"babel-loader": "^10.0.0",
|
|
28
30
|
"chalk": "^5.4.1",
|
|
29
31
|
"compression-webpack-plugin": "^10.0.0",
|
|
@@ -32,6 +34,7 @@
|
|
|
32
34
|
"execa": "^7.2.0",
|
|
33
35
|
"fast-glob": "^3.3.2",
|
|
34
36
|
"file-loader": "^6.2.0",
|
|
37
|
+
"git-url-parse": "^16.1.0",
|
|
35
38
|
"micromatch": "^4.0.8",
|
|
36
39
|
"piscina": "^4.2.1",
|
|
37
40
|
"rollup-plugin-visualizer": "^6.0.1",
|
|
@@ -48,7 +51,7 @@
|
|
|
48
51
|
"@types/webpack-bundle-analyzer": "^4.7.0",
|
|
49
52
|
"@types/yargs": "^17.0.33"
|
|
50
53
|
},
|
|
51
|
-
"gitSha": "
|
|
54
|
+
"gitSha": "ad63edcb005175f8a6087a828e52648c7b4f1635",
|
|
52
55
|
"scripts": {
|
|
53
56
|
"typescript": "tsc -p tsconfig.json",
|
|
54
57
|
"test": "pnpm -w test --project @mui/internal-bundle-size-checker"
|
package/src/cli.js
CHANGED
|
@@ -4,10 +4,14 @@ import path from 'path';
|
|
|
4
4
|
import os from 'os';
|
|
5
5
|
import fs from 'fs/promises';
|
|
6
6
|
import yargs from 'yargs';
|
|
7
|
-
import Piscina from 'piscina';
|
|
7
|
+
import { Piscina } from 'piscina';
|
|
8
8
|
import micromatch from 'micromatch';
|
|
9
|
+
import { execa } from 'execa';
|
|
10
|
+
import gitUrlParse from 'git-url-parse';
|
|
9
11
|
import { loadConfig } from './configLoader.js';
|
|
10
12
|
import { uploadSnapshot } from './uploadSnapshot.js';
|
|
13
|
+
import { renderMarkdownReport } from './renderMarkdownReport.js';
|
|
14
|
+
import { octokit } from './github.js';
|
|
11
15
|
|
|
12
16
|
/**
|
|
13
17
|
* @typedef {import('./sizeDiff.js').SizeSnapshot} SizeSnapshot
|
|
@@ -18,6 +22,26 @@ const DEFAULT_CONCURRENCY = os.availableParallelism();
|
|
|
18
22
|
|
|
19
23
|
const rootDir = process.cwd();
|
|
20
24
|
|
|
25
|
+
/**
|
|
26
|
+
* Gets the current repository owner and name from git remote
|
|
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
|
+
|
|
21
45
|
/**
|
|
22
46
|
* creates size snapshot for every bundle that built with webpack
|
|
23
47
|
* @param {CommandLineArgs} args
|
|
@@ -72,6 +96,42 @@ async function getWebpackSizes(args, config) {
|
|
|
72
96
|
return sizeArrays.flat();
|
|
73
97
|
}
|
|
74
98
|
|
|
99
|
+
/**
|
|
100
|
+
* Report command handler
|
|
101
|
+
* @param {ReportCommandArgs} argv - Command line arguments
|
|
102
|
+
*/
|
|
103
|
+
async function reportCommand(argv) {
|
|
104
|
+
const { pr, owner: argOwner, repo: argRepo } = argv;
|
|
105
|
+
|
|
106
|
+
// Get current repo info and coerce with provided arguments
|
|
107
|
+
const currentRepo = await getCurrentRepoInfo();
|
|
108
|
+
const owner = argOwner ?? currentRepo.owner;
|
|
109
|
+
const repo = argRepo ?? currentRepo.repo;
|
|
110
|
+
|
|
111
|
+
if (typeof pr !== 'number') {
|
|
112
|
+
throw new Error('Invalid pull request number. Please provide a valid --pr option.');
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Validate that both owner and repo are available
|
|
116
|
+
if (!owner || !repo) {
|
|
117
|
+
throw new Error(
|
|
118
|
+
'Repository owner and name are required. Please provide --owner and --repo options, or run this command from within a git repository.',
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Fetch PR information
|
|
123
|
+
const { data: prInfo } = await octokit.pulls.get({
|
|
124
|
+
owner,
|
|
125
|
+
repo,
|
|
126
|
+
pull_number: pr,
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
// Generate and print the markdown report
|
|
130
|
+
const report = await renderMarkdownReport(prInfo);
|
|
131
|
+
// eslint-disable-next-line no-console
|
|
132
|
+
console.log(report);
|
|
133
|
+
}
|
|
134
|
+
|
|
75
135
|
/**
|
|
76
136
|
* Main runner function
|
|
77
137
|
* @param {CommandLineArgs} argv - Command line arguments
|
|
@@ -113,52 +173,76 @@ async function run(argv) {
|
|
|
113
173
|
}
|
|
114
174
|
|
|
115
175
|
yargs(process.argv.slice(2))
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
176
|
+
.command(
|
|
177
|
+
/** @type {import('yargs').CommandModule<{}, CommandLineArgs>} */ ({
|
|
178
|
+
command: '$0',
|
|
179
|
+
describe: 'Saves a size snapshot in size-snapshot.json',
|
|
180
|
+
builder: (cmdYargs) => {
|
|
181
|
+
return cmdYargs
|
|
182
|
+
.option('analyze', {
|
|
183
|
+
default: false,
|
|
184
|
+
describe: 'Creates a webpack-bundle-analyzer report for each bundle.',
|
|
185
|
+
type: 'boolean',
|
|
186
|
+
})
|
|
187
|
+
.option('accurateBundles', {
|
|
188
|
+
default: false,
|
|
189
|
+
describe: 'Displays used bundles accurately at the cost of more CPU cycles.',
|
|
190
|
+
type: 'boolean',
|
|
191
|
+
})
|
|
192
|
+
.option('verbose', {
|
|
193
|
+
default: false,
|
|
194
|
+
describe: 'Show more detailed information during compilation.',
|
|
195
|
+
type: 'boolean',
|
|
196
|
+
})
|
|
197
|
+
.option('vite', {
|
|
198
|
+
default: false,
|
|
199
|
+
describe: 'Use Vite instead of webpack for bundling.',
|
|
200
|
+
type: 'boolean',
|
|
201
|
+
})
|
|
202
|
+
.option('output', {
|
|
203
|
+
alias: 'o',
|
|
204
|
+
describe:
|
|
205
|
+
'Path to output the size snapshot JSON file (defaults to size-snapshot.json in current directory).',
|
|
206
|
+
type: 'string',
|
|
207
|
+
})
|
|
208
|
+
.option('filter', {
|
|
209
|
+
alias: 'F',
|
|
210
|
+
describe: 'Filter entry points by glob pattern(s) applied to their IDs',
|
|
211
|
+
type: 'array',
|
|
212
|
+
})
|
|
213
|
+
.option('concurrency', {
|
|
214
|
+
alias: 'c',
|
|
215
|
+
describe: 'Number of workers to use for parallel processing',
|
|
216
|
+
type: 'number',
|
|
217
|
+
default: DEFAULT_CONCURRENCY,
|
|
218
|
+
});
|
|
219
|
+
},
|
|
220
|
+
handler: run,
|
|
221
|
+
}),
|
|
222
|
+
)
|
|
223
|
+
.command(
|
|
224
|
+
/** @type {import('yargs').CommandModule<{}, ReportCommandArgs>} */ ({
|
|
225
|
+
command: 'report',
|
|
226
|
+
describe: 'Generate a markdown report for a pull request',
|
|
227
|
+
builder: (cmdYargs) => {
|
|
228
|
+
return cmdYargs
|
|
229
|
+
.option('pr', {
|
|
230
|
+
describe: 'Pull request number',
|
|
231
|
+
type: 'number',
|
|
232
|
+
demandOption: true,
|
|
233
|
+
})
|
|
234
|
+
.option('owner', {
|
|
235
|
+
describe: 'Repository owner (defaults to current git repo owner)',
|
|
236
|
+
type: 'string',
|
|
237
|
+
})
|
|
238
|
+
.option('repo', {
|
|
239
|
+
describe: 'Repository name (defaults to current git repo name)',
|
|
240
|
+
type: 'string',
|
|
241
|
+
});
|
|
242
|
+
},
|
|
243
|
+
handler: reportCommand,
|
|
244
|
+
}),
|
|
245
|
+
)
|
|
162
246
|
.help()
|
|
163
247
|
.strict(true)
|
|
164
248
|
.version(false)
|
package/src/fetchSnapshot.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { octokit } from './github.js';
|
|
2
|
+
|
|
1
3
|
/**
|
|
2
4
|
*
|
|
3
5
|
* @param {string} repo - The name of the repository e.g. 'mui/material-ui'
|
|
@@ -44,15 +46,15 @@ export async function fetchSnapshot(repo, sha) {
|
|
|
44
46
|
*/
|
|
45
47
|
async function getParentCommits(repo, commit, depth = 4) {
|
|
46
48
|
try {
|
|
47
|
-
const
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
49
|
+
const [owner, repoName] = repo.split('/');
|
|
50
|
+
|
|
51
|
+
const { data: commits } = await octokit.repos.listCommits({
|
|
52
|
+
owner,
|
|
53
|
+
repo: repoName,
|
|
54
|
+
sha: commit,
|
|
55
|
+
per_page: depth,
|
|
56
|
+
});
|
|
53
57
|
|
|
54
|
-
/** @type {{ sha: string }[]} */
|
|
55
|
-
const commits = await response.json();
|
|
56
58
|
// Skip the first commit (which is the starting commit) and return the rest
|
|
57
59
|
return commits.slice(1).map((commitDetails) => commitDetails.sha);
|
|
58
60
|
} catch (/** @type {any} */ error) {
|
package/src/github.js
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
|
|
3
|
+
import { Octokit } from '@octokit/rest';
|
|
4
|
+
import { createActionAuth } from '@octokit/auth-action';
|
|
5
|
+
|
|
6
|
+
// Create and export Octokit instance
|
|
7
|
+
/** @type {import('@octokit/rest').Octokit} */
|
|
8
|
+
export const octokit = new Octokit({
|
|
9
|
+
authStrategy: process.env.GITHUB_TOKEN ? createActionAuth : undefined,
|
|
10
|
+
userAgent: 'bundle-size-checker',
|
|
11
|
+
});
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
import { calculateSizeDiff } from './sizeDiff.js';
|
|
8
8
|
import { fetchSnapshot, fetchSnapshotWithFallback } from './fetchSnapshot.js';
|
|
9
9
|
import { displayPercentFormatter, byteSizeChangeFormatter } from './formatUtils.js';
|
|
10
|
+
import { octokit } from './github.js';
|
|
10
11
|
|
|
11
12
|
/**
|
|
12
13
|
* Generates a symbol based on the relative change value.
|
|
@@ -224,11 +225,19 @@ function getDetailsUrl(prInfo, options = {}) {
|
|
|
224
225
|
export async function renderMarkdownReport(prInfo, circleciBuildNumber, options = {}) {
|
|
225
226
|
let markdownContent = '';
|
|
226
227
|
|
|
227
|
-
const baseCommit = prInfo.base.sha;
|
|
228
228
|
const prCommit = prInfo.head.sha;
|
|
229
229
|
const repo = prInfo.base.repo.full_name;
|
|
230
230
|
const { fallbackDepth = 3 } = options;
|
|
231
231
|
|
|
232
|
+
const [owner, repoName] = repo.split('/');
|
|
233
|
+
const { data } = await octokit.repos.compareCommits({
|
|
234
|
+
owner,
|
|
235
|
+
repo: repoName,
|
|
236
|
+
base: prInfo.base.sha,
|
|
237
|
+
head: prCommit,
|
|
238
|
+
});
|
|
239
|
+
const baseCommit = data.merge_base_commit.sha;
|
|
240
|
+
|
|
232
241
|
const [baseResult, prSnapshot] = await Promise.all([
|
|
233
242
|
fetchSnapshotWithFallback(repo, baseCommit, fallbackDepth),
|
|
234
243
|
fetchSnapshot(repo, prCommit),
|
|
@@ -237,9 +246,9 @@ export async function renderMarkdownReport(prInfo, circleciBuildNumber, options
|
|
|
237
246
|
const { snapshot: baseSnapshot, actualCommit: actualBaseCommit } = baseResult;
|
|
238
247
|
|
|
239
248
|
if (!baseSnapshot) {
|
|
240
|
-
markdownContent += `_:no_entry_sign: No bundle size snapshot found for base
|
|
249
|
+
markdownContent += `_:no_entry_sign: No bundle size snapshot found for merge base ${baseCommit} or any of its ${fallbackDepth} parent commits._\n\n`;
|
|
241
250
|
} else if (actualBaseCommit !== baseCommit) {
|
|
242
|
-
markdownContent += `_:information_source: Using snapshot from parent commit ${actualBaseCommit} (fallback from ${baseCommit})._\n\n`;
|
|
251
|
+
markdownContent += `_:information_source: Using snapshot from parent commit ${actualBaseCommit} (fallback from merge base ${baseCommit})._\n\n`;
|
|
243
252
|
}
|
|
244
253
|
|
|
245
254
|
const sizeDiff = calculateSizeDiff(baseSnapshot ?? {}, prSnapshot);
|
|
@@ -1,9 +1,22 @@
|
|
|
1
|
+
/* eslint-disable testing-library/render-result-naming-convention */
|
|
1
2
|
import { vi, describe, it, expect, beforeEach } from 'vitest';
|
|
2
3
|
import { renderMarkdownReport } from './renderMarkdownReport.js';
|
|
3
4
|
import * as fetchSnapshotModule from './fetchSnapshot.js';
|
|
4
5
|
|
|
5
6
|
// Mock the fetchSnapshot module
|
|
6
7
|
vi.mock('./fetchSnapshot.js');
|
|
8
|
+
// Mock the @octokit/rest module
|
|
9
|
+
vi.mock('@octokit/rest', () => ({
|
|
10
|
+
Octokit: vi.fn(() => ({
|
|
11
|
+
repos: {
|
|
12
|
+
compareCommits: vi.fn(),
|
|
13
|
+
listCommits: vi.fn(),
|
|
14
|
+
},
|
|
15
|
+
pulls: {
|
|
16
|
+
get: vi.fn(),
|
|
17
|
+
},
|
|
18
|
+
})),
|
|
19
|
+
}));
|
|
7
20
|
|
|
8
21
|
describe('renderMarkdownReport', () => {
|
|
9
22
|
const mockFetchSnapshot = vi.mocked(fetchSnapshotModule.fetchSnapshot);
|
|
@@ -23,9 +36,26 @@ describe('renderMarkdownReport', () => {
|
|
|
23
36
|
},
|
|
24
37
|
};
|
|
25
38
|
|
|
26
|
-
beforeEach(() => {
|
|
39
|
+
beforeEach(async () => {
|
|
27
40
|
mockFetchSnapshot.mockClear();
|
|
28
41
|
mockFetchSnapshotWithFallback.mockClear();
|
|
42
|
+
|
|
43
|
+
// Import and mock the octokit instance after mocking the module
|
|
44
|
+
const { octokit } = await import('./github.js');
|
|
45
|
+
|
|
46
|
+
// Set up default mock for compareCommits to return the base commit SHA
|
|
47
|
+
vi.mocked(octokit.repos.compareCommits).mockResolvedValue(
|
|
48
|
+
/** @type {any} */ ({
|
|
49
|
+
data: {
|
|
50
|
+
merge_base_commit: {
|
|
51
|
+
sha: mockPrInfo.base.sha,
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
}),
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
// Clear any previous mock calls
|
|
58
|
+
vi.mocked(octokit.repos.compareCommits).mockClear();
|
|
29
59
|
});
|
|
30
60
|
|
|
31
61
|
it('should generate markdown report with size increases', async () => {
|
|
@@ -108,7 +138,7 @@ describe('renderMarkdownReport', () => {
|
|
|
108
138
|
const result = await renderMarkdownReport(mockPrInfo);
|
|
109
139
|
|
|
110
140
|
expect(result).toContain(
|
|
111
|
-
'No bundle size snapshot found for base
|
|
141
|
+
'No bundle size snapshot found for merge base abc123 or any of its 3 parent commits.',
|
|
112
142
|
);
|
|
113
143
|
});
|
|
114
144
|
|
|
@@ -511,7 +541,9 @@ describe('renderMarkdownReport', () => {
|
|
|
511
541
|
|
|
512
542
|
const result = await renderMarkdownReport(mockPrInfo);
|
|
513
543
|
|
|
514
|
-
expect(result).toContain(
|
|
544
|
+
expect(result).toContain(
|
|
545
|
+
'Using snapshot from parent commit parent1 (fallback from merge base abc123)',
|
|
546
|
+
);
|
|
515
547
|
expect(result).toContain('baseCommit=parent1');
|
|
516
548
|
});
|
|
517
549
|
|
|
@@ -526,7 +558,7 @@ describe('renderMarkdownReport', () => {
|
|
|
526
558
|
const result = await renderMarkdownReport(mockPrInfo);
|
|
527
559
|
|
|
528
560
|
expect(result).toContain(
|
|
529
|
-
'No bundle size snapshot found for base
|
|
561
|
+
'No bundle size snapshot found for merge base abc123 or any of its 3 parent commits.',
|
|
530
562
|
);
|
|
531
563
|
});
|
|
532
564
|
|
|
@@ -547,7 +579,9 @@ describe('renderMarkdownReport', () => {
|
|
|
547
579
|
|
|
548
580
|
const result = await renderMarkdownReport(mockPrInfo, undefined, { fallbackDepth: 1 });
|
|
549
581
|
|
|
550
|
-
expect(result).toContain(
|
|
582
|
+
expect(result).toContain(
|
|
583
|
+
'Using snapshot from parent commit parent1 (fallback from merge base abc123)',
|
|
584
|
+
);
|
|
551
585
|
expect(mockFetchSnapshotWithFallback).toHaveBeenCalledWith('mui/material-ui', 'abc123', 1);
|
|
552
586
|
});
|
|
553
587
|
});
|
package/src/types.d.ts
CHANGED
package/.eslintrc.cjs
DELETED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
module.exports = {
|
|
2
|
-
rules: {
|
|
3
|
-
'import/prefer-default-export': 'off',
|
|
4
|
-
// Allow .js file extensions in import statements for ESM compatibility
|
|
5
|
-
'import/extensions': [
|
|
6
|
-
'error',
|
|
7
|
-
'ignorePackages',
|
|
8
|
-
{
|
|
9
|
-
js: 'always',
|
|
10
|
-
mjs: 'always',
|
|
11
|
-
},
|
|
12
|
-
],
|
|
13
|
-
},
|
|
14
|
-
};
|