@mui/internal-bundle-size-checker 1.0.9-canary.17 → 1.0.9-canary.19
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 +0 -1
- package/package.json +2 -14
- package/src/{viteBuilder.js → builder.js} +4 -4
- package/src/cli.js +12 -38
- package/src/fetchSnapshot.js +3 -61
- package/src/fetchSnapshotWithFallback.js +34 -0
- package/src/git.js +45 -0
- package/src/index.js +2 -0
- package/src/renderMarkdownReport.js +18 -8
- package/src/renderMarkdownReport.test.js +6 -1
- package/src/types.d.ts +0 -22
- package/src/uploadSnapshot.js +1 -1
- package/src/worker.js +2 -9
- package/tsconfig.json +2 -2
- package/src/webpackBuilder.js +0 -267
package/README.md
CHANGED
|
@@ -20,7 +20,6 @@ bundle-size-checker [options]
|
|
|
20
20
|
Options:
|
|
21
21
|
|
|
22
22
|
- `--analyze`: Creates a webpack-bundle-analyzer report for each bundle
|
|
23
|
-
- `--accurateBundles`: Displays used bundles accurately at the cost of more CPU cycles
|
|
24
23
|
- `--output`, `-o`: Path to output the size snapshot JSON file
|
|
25
24
|
|
|
26
25
|
### Configuration
|
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.19",
|
|
4
4
|
"description": "Bundle size checker for MUI packages.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./src/index.js",
|
|
@@ -21,37 +21,25 @@
|
|
|
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.4",
|
|
25
|
-
"@babel/preset-react": "^7.18.6",
|
|
26
|
-
"@babel/preset-typescript": "^7.27.1",
|
|
27
24
|
"@octokit/auth-action": "^6.0.1",
|
|
28
25
|
"@octokit/rest": "^22.0.0",
|
|
29
|
-
"babel-loader": "^10.0.0",
|
|
30
26
|
"chalk": "^5.4.1",
|
|
31
|
-
"compression-webpack-plugin": "^10.0.0",
|
|
32
|
-
"css-loader": "^7.1.2",
|
|
33
27
|
"env-ci": "^11.1.0",
|
|
34
28
|
"execa": "^7.2.0",
|
|
35
29
|
"fast-glob": "^3.3.3",
|
|
36
|
-
"file-loader": "^6.2.0",
|
|
37
30
|
"git-url-parse": "^16.1.0",
|
|
38
31
|
"micromatch": "^4.0.8",
|
|
39
32
|
"piscina": "^4.2.1",
|
|
40
33
|
"rollup-plugin-visualizer": "^6.0.3",
|
|
41
|
-
"terser-webpack-plugin": "^5.3.14",
|
|
42
34
|
"vite": "^6.3.5",
|
|
43
|
-
"webpack": "^5.90.3",
|
|
44
|
-
"webpack-bundle-analyzer": "^4.10.2",
|
|
45
35
|
"yargs": "^17.7.2"
|
|
46
36
|
},
|
|
47
37
|
"devDependencies": {
|
|
48
38
|
"@types/env-ci": "^3.1.4",
|
|
49
39
|
"@types/micromatch": "^4.0.9",
|
|
50
|
-
"@types/webpack": "^5.28.5",
|
|
51
|
-
"@types/webpack-bundle-analyzer": "^4.7.0",
|
|
52
40
|
"@types/yargs": "^17.0.33"
|
|
53
41
|
},
|
|
54
|
-
"gitSha": "
|
|
42
|
+
"gitSha": "25211252944f876e77d486423473dcf19779a814",
|
|
55
43
|
"scripts": {
|
|
56
44
|
"typescript": "tsc -p tsconfig.json",
|
|
57
45
|
"test": "pnpm -w test --project @mui/internal-bundle-size-checker"
|
|
@@ -29,7 +29,7 @@ const rootDir = process.cwd();
|
|
|
29
29
|
* Creates vite configuration for bundle size checking
|
|
30
30
|
* @param {ObjectEntry} entry - Entry point (string or object)
|
|
31
31
|
* @param {CommandLineArgs} args
|
|
32
|
-
* @returns {Promise<
|
|
32
|
+
* @returns {Promise<import('vite').InlineConfig>}
|
|
33
33
|
*/
|
|
34
34
|
async function createViteConfig(entry, args) {
|
|
35
35
|
const entryName = entry.id;
|
|
@@ -132,7 +132,7 @@ async function createViteConfig(entry, args) {
|
|
|
132
132
|
],
|
|
133
133
|
};
|
|
134
134
|
|
|
135
|
-
return
|
|
135
|
+
return configuration;
|
|
136
136
|
}
|
|
137
137
|
|
|
138
138
|
/**
|
|
@@ -233,9 +233,9 @@ async function processBundleSizes(output, entryName) {
|
|
|
233
233
|
* @param {CommandLineArgs} args - Command line arguments
|
|
234
234
|
* @returns {Promise<Map<string, { parsed: number, gzip: number }>>}
|
|
235
235
|
*/
|
|
236
|
-
export async function
|
|
236
|
+
export async function getBundleSizes(entry, args) {
|
|
237
237
|
// Create vite configuration
|
|
238
|
-
const
|
|
238
|
+
const configuration = await createViteConfig(entry, args);
|
|
239
239
|
|
|
240
240
|
// Run vite build
|
|
241
241
|
const { output } = /** @type {import('vite').Rollup.RollupOutput} */ (await build(configuration));
|
package/src/cli.js
CHANGED
|
@@ -6,12 +6,11 @@ import fs from 'fs/promises';
|
|
|
6
6
|
import yargs from 'yargs';
|
|
7
7
|
import { Piscina } from 'piscina';
|
|
8
8
|
import micromatch from 'micromatch';
|
|
9
|
-
import { execa } from 'execa';
|
|
10
|
-
import gitUrlParse from 'git-url-parse';
|
|
11
9
|
import { loadConfig } from './configLoader.js';
|
|
12
10
|
import { uploadSnapshot } from './uploadSnapshot.js';
|
|
13
11
|
import { renderMarkdownReport } from './renderMarkdownReport.js';
|
|
14
12
|
import { octokit } from './github.js';
|
|
13
|
+
import { getCurrentRepoInfo } from './git.js';
|
|
15
14
|
|
|
16
15
|
/**
|
|
17
16
|
* @typedef {import('./sizeDiff.js').SizeSnapshot} SizeSnapshot
|
|
@@ -23,32 +22,12 @@ const DEFAULT_CONCURRENCY = os.availableParallelism();
|
|
|
23
22
|
const rootDir = process.cwd();
|
|
24
23
|
|
|
25
24
|
/**
|
|
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
|
|
25
|
+
* creates size snapshot for every bundle
|
|
47
26
|
* @param {CommandLineArgs} args
|
|
48
27
|
* @param {NormalizedBundleSizeCheckerConfig} config - The loaded configuration
|
|
49
28
|
* @returns {Promise<Array<[string, { parsed: number, gzip: number }]>>}
|
|
50
29
|
*/
|
|
51
|
-
async function
|
|
30
|
+
async function getBundleSizes(args, config) {
|
|
52
31
|
const worker = new Piscina({
|
|
53
32
|
filename: new URL('./worker.js', import.meta.url).href,
|
|
54
33
|
maxThreads: args.concurrency || DEFAULT_CONCURRENCY,
|
|
@@ -146,12 +125,14 @@ async function run(argv) {
|
|
|
146
125
|
// eslint-disable-next-line no-console
|
|
147
126
|
console.log(`Starting bundle size snapshot creation with ${concurrency} workers...`);
|
|
148
127
|
|
|
149
|
-
const
|
|
150
|
-
const
|
|
128
|
+
const bundleSizes = await getBundleSizes(argv, config);
|
|
129
|
+
const sortedBundleSizes = Object.fromEntries(
|
|
130
|
+
bundleSizes.sort((a, b) => a[0].localeCompare(b[0])),
|
|
131
|
+
);
|
|
151
132
|
|
|
152
133
|
// Ensure output directory exists
|
|
153
134
|
await fs.mkdir(path.dirname(snapshotDestPath), { recursive: true });
|
|
154
|
-
await fs.writeFile(snapshotDestPath, JSON.stringify(
|
|
135
|
+
await fs.writeFile(snapshotDestPath, JSON.stringify(sortedBundleSizes, null, 2));
|
|
155
136
|
|
|
156
137
|
// eslint-disable-next-line no-console
|
|
157
138
|
console.log(`Bundle size snapshot written to ${snapshotDestPath}`);
|
|
@@ -169,6 +150,9 @@ async function run(argv) {
|
|
|
169
150
|
// Exit with error code to indicate failure
|
|
170
151
|
process.exit(1);
|
|
171
152
|
}
|
|
153
|
+
} else {
|
|
154
|
+
// eslint-disable-next-line no-console
|
|
155
|
+
console.log('No upload configuration provided, skipping upload.');
|
|
172
156
|
}
|
|
173
157
|
}
|
|
174
158
|
|
|
@@ -181,12 +165,7 @@ yargs(process.argv.slice(2))
|
|
|
181
165
|
return cmdYargs
|
|
182
166
|
.option('analyze', {
|
|
183
167
|
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.',
|
|
168
|
+
describe: 'Creates a report for each bundle.',
|
|
190
169
|
type: 'boolean',
|
|
191
170
|
})
|
|
192
171
|
.option('verbose', {
|
|
@@ -194,11 +173,6 @@ yargs(process.argv.slice(2))
|
|
|
194
173
|
describe: 'Show more detailed information during compilation.',
|
|
195
174
|
type: 'boolean',
|
|
196
175
|
})
|
|
197
|
-
.option('vite', {
|
|
198
|
-
default: false,
|
|
199
|
-
describe: 'Use Vite instead of webpack for bundling.',
|
|
200
|
-
type: 'boolean',
|
|
201
|
-
})
|
|
202
176
|
.option('output', {
|
|
203
177
|
alias: 'o',
|
|
204
178
|
describe:
|
package/src/fetchSnapshot.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
|
|
1
|
+
// This file must be importable in the browser
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
*
|
|
5
5
|
* @param {string} repo - The name of the repository e.g. 'mui/material-ui'
|
|
6
6
|
* @param {string} sha - The commit SHA
|
|
7
|
-
* @returns {Promise<import('./sizeDiff').SizeSnapshot>} - The size snapshot data
|
|
7
|
+
* @returns {Promise<import('./sizeDiff.js').SizeSnapshot>} - The size snapshot data
|
|
8
8
|
*/
|
|
9
9
|
export async function fetchSnapshot(repo, sha) {
|
|
10
10
|
const urlsToTry = [
|
|
@@ -27,7 +27,7 @@ export async function fetchSnapshot(repo, sha) {
|
|
|
27
27
|
continue;
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
-
return response.json();
|
|
30
|
+
return /** @type {Promise<any>} */ (response.json());
|
|
31
31
|
} catch (error) {
|
|
32
32
|
lastError = error;
|
|
33
33
|
continue;
|
|
@@ -36,61 +36,3 @@ export async function fetchSnapshot(repo, sha) {
|
|
|
36
36
|
|
|
37
37
|
throw new Error(`Failed to fetch snapshot`, { cause: lastError });
|
|
38
38
|
}
|
|
39
|
-
|
|
40
|
-
/**
|
|
41
|
-
* Gets parent commits for a given commit SHA using GitHub API
|
|
42
|
-
* @param {string} repo - Repository name (e.g., 'mui/material-ui')
|
|
43
|
-
* @param {string} commit - The commit SHA to start from
|
|
44
|
-
* @param {number} depth - How many commits to retrieve (including the starting commit)
|
|
45
|
-
* @returns {Promise<string[]>} Array of commit SHAs in chronological order (excluding the starting commit)
|
|
46
|
-
*/
|
|
47
|
-
async function getParentCommits(repo, commit, depth = 4) {
|
|
48
|
-
try {
|
|
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
|
-
});
|
|
57
|
-
|
|
58
|
-
// Skip the first commit (which is the starting commit) and return the rest
|
|
59
|
-
return commits.slice(1).map((commitDetails) => commitDetails.sha);
|
|
60
|
-
} catch (/** @type {any} */ error) {
|
|
61
|
-
console.warn(`Failed to get parent commits for ${commit}: ${error.message}`);
|
|
62
|
-
return [];
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
/**
|
|
67
|
-
* Attempts to fetch a snapshot with fallback to parent commits
|
|
68
|
-
* @param {string} repo - Repository name
|
|
69
|
-
* @param {string} commit - The commit SHA to start from
|
|
70
|
-
* @param {number} [fallbackDepth=3] - How many parent commits to try as fallback
|
|
71
|
-
* @returns {Promise<{snapshot: import('./sizeDiff').SizeSnapshot | null, actualCommit: string | null}>}
|
|
72
|
-
*/
|
|
73
|
-
export async function fetchSnapshotWithFallback(repo, commit, fallbackDepth = 3) {
|
|
74
|
-
// Try the original commit first
|
|
75
|
-
try {
|
|
76
|
-
const snapshot = await fetchSnapshot(repo, commit);
|
|
77
|
-
return { snapshot, actualCommit: commit };
|
|
78
|
-
} catch (/** @type {any} */ error) {
|
|
79
|
-
// fallthrough to parent commits if the snapshot for the original commit fails
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
// Get parent commits and try each one
|
|
83
|
-
const parentCommits = await getParentCommits(repo, commit, fallbackDepth + 1);
|
|
84
|
-
|
|
85
|
-
for (const parentCommit of parentCommits) {
|
|
86
|
-
try {
|
|
87
|
-
// eslint-disable-next-line no-await-in-loop
|
|
88
|
-
const snapshot = await fetchSnapshot(repo, parentCommit);
|
|
89
|
-
return { snapshot, actualCommit: parentCommit };
|
|
90
|
-
} catch {
|
|
91
|
-
// fallthrough to the next parent commit if fetching fails
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
return { snapshot: null, actualCommit: null };
|
|
96
|
-
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { fetchSnapshot } from './fetchSnapshot.js';
|
|
2
|
+
import { getParentCommits } from './git.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Attempts to fetch a snapshot with fallback to parent commits
|
|
6
|
+
* @param {string} repo - Repository name
|
|
7
|
+
* @param {string} commit - The commit SHA to start from
|
|
8
|
+
* @param {number} [fallbackDepth=3] - How many parent commits to try as fallback
|
|
9
|
+
* @returns {Promise<{snapshot: import('./sizeDiff.js').SizeSnapshot | null, actualCommit: string | null}>}
|
|
10
|
+
*/
|
|
11
|
+
export async function fetchSnapshotWithFallback(repo, commit, fallbackDepth = 3) {
|
|
12
|
+
// Try the original commit first
|
|
13
|
+
try {
|
|
14
|
+
const snapshot = await fetchSnapshot(repo, commit);
|
|
15
|
+
return { snapshot, actualCommit: commit };
|
|
16
|
+
} catch (/** @type {any} */ error) {
|
|
17
|
+
// fallthrough to parent commits if the snapshot for the original commit fails
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// Get parent commits and try each one
|
|
21
|
+
const parentCommits = await getParentCommits(repo, commit, fallbackDepth);
|
|
22
|
+
|
|
23
|
+
for (const parentCommit of parentCommits) {
|
|
24
|
+
try {
|
|
25
|
+
// eslint-disable-next-line no-await-in-loop
|
|
26
|
+
const snapshot = await fetchSnapshot(repo, parentCommit);
|
|
27
|
+
return { snapshot, actualCommit: parentCommit };
|
|
28
|
+
} catch {
|
|
29
|
+
// fallthrough to the next parent commit if fetching fails
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return { snapshot: null, actualCommit: null };
|
|
34
|
+
}
|
package/src/git.js
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { execa } from 'execa';
|
|
2
|
+
import gitUrlParse from 'git-url-parse';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Gets parent commits for a given commit SHA using git CLI
|
|
6
|
+
* @param {string} repo - Repository name (e.g., 'mui/material-ui') - ignored for git CLI
|
|
7
|
+
* @param {string} commit - The commit SHA to start from
|
|
8
|
+
* @param {number} depth - How many commits to retrieve (including the starting commit)
|
|
9
|
+
* @returns {Promise<string[]>} Array of commit SHAs in chronological order (excluding the starting commit)
|
|
10
|
+
*/
|
|
11
|
+
export async function getParentCommits(repo, commit, depth = 3) {
|
|
12
|
+
const { stdout } = await execa('git', ['rev-list', `--max-count=${depth}`, '--skip=1', commit]);
|
|
13
|
+
return stdout.trim().split('\n').filter(Boolean);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Compares two commits and returns merge base information using git CLI
|
|
18
|
+
* @param {string} base - Base commit SHA
|
|
19
|
+
* @param {string} head - Head commit SHA
|
|
20
|
+
* @returns {Promise<string>} Object with merge base commit info
|
|
21
|
+
*/
|
|
22
|
+
export async function getMergeBase(base, head) {
|
|
23
|
+
const { stdout } = await execa('git', ['merge-base', base, head]);
|
|
24
|
+
return stdout.trim();
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Gets the current repository owner and name from git remote
|
|
29
|
+
* @returns {Promise<{owner: string | null, repo: string | null}>}
|
|
30
|
+
*/
|
|
31
|
+
export async function getCurrentRepoInfo() {
|
|
32
|
+
try {
|
|
33
|
+
const { stdout } = await execa('git', ['remote', 'get-url', 'origin']);
|
|
34
|
+
const parsed = gitUrlParse(stdout.trim());
|
|
35
|
+
return {
|
|
36
|
+
owner: parsed.owner,
|
|
37
|
+
repo: parsed.name,
|
|
38
|
+
};
|
|
39
|
+
} catch (error) {
|
|
40
|
+
return {
|
|
41
|
+
owner: null,
|
|
42
|
+
repo: null,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
}
|
package/src/index.js
CHANGED
|
@@ -5,9 +5,11 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import { calculateSizeDiff } from './sizeDiff.js';
|
|
8
|
-
import { fetchSnapshot
|
|
8
|
+
import { fetchSnapshot } from './fetchSnapshot.js';
|
|
9
9
|
import { displayPercentFormatter, byteSizeChangeFormatter } from './formatUtils.js';
|
|
10
10
|
import { octokit } from './github.js';
|
|
11
|
+
import { getCurrentRepoInfo, getMergeBase } from './git.js';
|
|
12
|
+
import { fetchSnapshotWithFallback } from './fetchSnapshotWithFallback.js';
|
|
11
13
|
|
|
12
14
|
/**
|
|
13
15
|
* Generates a symbol based on the relative change value.
|
|
@@ -232,13 +234,21 @@ export async function renderMarkdownReport(prInfo, circleciBuildNumber, options
|
|
|
232
234
|
const { fallbackDepth = 3 } = options;
|
|
233
235
|
|
|
234
236
|
const [owner, repoName] = repo.split('/');
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
237
|
+
|
|
238
|
+
const currentRepo = await getCurrentRepoInfo();
|
|
239
|
+
|
|
240
|
+
let baseCommit;
|
|
241
|
+
if (owner === currentRepo.owner && repoName === currentRepo.repo) {
|
|
242
|
+
baseCommit = await getMergeBase(prInfo.base.sha, prCommit);
|
|
243
|
+
} else {
|
|
244
|
+
const { data } = await octokit.repos.compareCommits({
|
|
245
|
+
owner,
|
|
246
|
+
repo: repoName,
|
|
247
|
+
base: prInfo.base.sha,
|
|
248
|
+
head: prCommit,
|
|
249
|
+
});
|
|
250
|
+
baseCommit = data.merge_base_commit.sha;
|
|
251
|
+
}
|
|
242
252
|
|
|
243
253
|
const [baseResult, prSnapshot] = await Promise.all([
|
|
244
254
|
fetchSnapshotWithFallback(repo, baseCommit, fallbackDepth),
|
|
@@ -2,9 +2,12 @@
|
|
|
2
2
|
import { vi, describe, it, expect, beforeEach } from 'vitest';
|
|
3
3
|
import { renderMarkdownReport } from './renderMarkdownReport.js';
|
|
4
4
|
import * as fetchSnapshotModule from './fetchSnapshot.js';
|
|
5
|
+
import * as fetchSnapshotWithFallbackModule from './fetchSnapshotWithFallback.js';
|
|
5
6
|
|
|
6
7
|
// Mock the fetchSnapshot module
|
|
7
8
|
vi.mock('./fetchSnapshot.js');
|
|
9
|
+
// Mock the fetchSnapshotWithFallback module
|
|
10
|
+
vi.mock('./fetchSnapshotWithFallback.js');
|
|
8
11
|
// Mock the @octokit/rest module
|
|
9
12
|
vi.mock('@octokit/rest', () => ({
|
|
10
13
|
Octokit: vi.fn(() => ({
|
|
@@ -20,7 +23,9 @@ vi.mock('@octokit/rest', () => ({
|
|
|
20
23
|
|
|
21
24
|
describe('renderMarkdownReport', () => {
|
|
22
25
|
const mockFetchSnapshot = vi.mocked(fetchSnapshotModule.fetchSnapshot);
|
|
23
|
-
const mockFetchSnapshotWithFallback = vi.mocked(
|
|
26
|
+
const mockFetchSnapshotWithFallback = vi.mocked(
|
|
27
|
+
fetchSnapshotWithFallbackModule.fetchSnapshotWithFallback,
|
|
28
|
+
);
|
|
24
29
|
|
|
25
30
|
/** @type {PrInfo} */
|
|
26
31
|
const mockPrInfo = {
|
package/src/types.d.ts
CHANGED
|
@@ -1,23 +1,3 @@
|
|
|
1
|
-
// WebpackEntry type
|
|
2
|
-
interface WebpackEntry {
|
|
3
|
-
import: string;
|
|
4
|
-
importName?: string;
|
|
5
|
-
}
|
|
6
|
-
|
|
7
|
-
// Webpack stats types
|
|
8
|
-
interface StatsAsset {
|
|
9
|
-
name: string;
|
|
10
|
-
size: number;
|
|
11
|
-
related?: {
|
|
12
|
-
find: (predicate: (asset: any) => boolean) => { size: number; type: string };
|
|
13
|
-
};
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
interface StatsChunkGroup {
|
|
17
|
-
name: string;
|
|
18
|
-
assets: Array<{ name: string; size: number }>;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
1
|
// Upload configuration with optional properties
|
|
22
2
|
interface UploadConfig {
|
|
23
3
|
repo?: string; // The repository name (e.g., "mui/material-ui")
|
|
@@ -65,12 +45,10 @@ interface NormalizedBundleSizeCheckerConfig {
|
|
|
65
45
|
// Command line argument types
|
|
66
46
|
interface CommandLineArgs {
|
|
67
47
|
analyze?: boolean;
|
|
68
|
-
accurateBundles?: boolean;
|
|
69
48
|
output?: string;
|
|
70
49
|
verbose?: boolean;
|
|
71
50
|
filter?: string[];
|
|
72
51
|
concurrency?: number;
|
|
73
|
-
vite?: boolean;
|
|
74
52
|
}
|
|
75
53
|
|
|
76
54
|
interface ReportCommandArgs {
|
package/src/uploadSnapshot.js
CHANGED
|
@@ -20,7 +20,7 @@ async function getCurrentCommitSHA() {
|
|
|
20
20
|
*/
|
|
21
21
|
function sanitizeS3TagString(str) {
|
|
22
22
|
// Replace disallowed characters with underscore
|
|
23
|
-
const safe = str.replace(/[^a-zA-Z0-9
|
|
23
|
+
const safe = str.replace(/[^a-zA-Z0-9 +\-=.:/@]+/g, '_');
|
|
24
24
|
// Truncate to max lengths (256 for value)
|
|
25
25
|
const maxLen = 256;
|
|
26
26
|
return safe.length > maxLen ? safe.substring(0, maxLen) : safe;
|
package/src/worker.js
CHANGED
|
@@ -4,8 +4,7 @@ import fs from 'fs/promises';
|
|
|
4
4
|
import chalk from 'chalk';
|
|
5
5
|
import * as module from 'module';
|
|
6
6
|
import { byteSizeFormatter } from './formatUtils.js';
|
|
7
|
-
import {
|
|
8
|
-
import { getViteSizes } from './viteBuilder.js';
|
|
7
|
+
import { getBundleSizes } from './builder.js';
|
|
9
8
|
|
|
10
9
|
const require = module.createRequire(import.meta.url);
|
|
11
10
|
|
|
@@ -83,12 +82,7 @@ export default async function getSizes({ entry, args, index, total }) {
|
|
|
83
82
|
}
|
|
84
83
|
|
|
85
84
|
try {
|
|
86
|
-
|
|
87
|
-
if (args.vite) {
|
|
88
|
-
sizeMap = await getViteSizes(entry, args);
|
|
89
|
-
} else {
|
|
90
|
-
sizeMap = await getWebpackSizes(entry, args);
|
|
91
|
-
}
|
|
85
|
+
const sizeMap = await getBundleSizes(entry, args);
|
|
92
86
|
|
|
93
87
|
// Create a concise log message showing import details
|
|
94
88
|
let entryDetails = '';
|
|
@@ -112,7 +106,6 @@ export default async function getSizes({ entry, args, index, total }) {
|
|
|
112
106
|
${chalk.green('✓')} ${chalk.green.bold(`Completed ${index + 1}/${total}: [${entry.id}]`)}
|
|
113
107
|
${chalk.cyan('Import:')} ${entryDetails}
|
|
114
108
|
${chalk.cyan('Externals:')} ${entry.externals.join(', ')}
|
|
115
|
-
${chalk.cyan('Bundler:')} ${args.vite ? 'vite' : 'webpack'}
|
|
116
109
|
${chalk.cyan('Sizes:')} ${chalk.yellow(byteSizeFormatter.format(entrySize.parsed))} (${chalk.yellow(byteSizeFormatter.format(entrySize.gzip))} gzipped)
|
|
117
110
|
${args.analyze ? ` ${chalk.cyan('Analysis:')} ${chalk.underline(pathToFileURL(path.join(rootDir, 'build', `${entry.id}.html`)).href)}` : ''}
|
|
118
111
|
`.trim(),
|
package/tsconfig.json
CHANGED
package/src/webpackBuilder.js
DELETED
|
@@ -1,267 +0,0 @@
|
|
|
1
|
-
import { promisify } from 'util';
|
|
2
|
-
import path from 'path';
|
|
3
|
-
import webpackCallbackBased from 'webpack';
|
|
4
|
-
import CompressionPlugin from 'compression-webpack-plugin';
|
|
5
|
-
import TerserPlugin from 'terser-webpack-plugin';
|
|
6
|
-
import { BundleAnalyzerPlugin } from 'webpack-bundle-analyzer';
|
|
7
|
-
import { createRequire } from 'node:module';
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* @type {(options: webpackCallbackBased.Configuration) => Promise<webpackCallbackBased.Stats>}
|
|
11
|
-
*/
|
|
12
|
-
// @ts-expect-error Can't select the right overload
|
|
13
|
-
const webpack = promisify(webpackCallbackBased);
|
|
14
|
-
const rootDir = process.cwd();
|
|
15
|
-
const require = createRequire(import.meta.url);
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* Creates webpack configuration for bundle size checking
|
|
19
|
-
* @param {ObjectEntry} entry - Entry point (string or object)
|
|
20
|
-
* @param {CommandLineArgs} args
|
|
21
|
-
* @returns {Promise<{configuration: import('webpack').Configuration, externalsArray: string[]}>}
|
|
22
|
-
*/
|
|
23
|
-
async function createWebpackConfig(entry, args) {
|
|
24
|
-
const analyzerMode = args.analyze ? 'static' : 'disabled';
|
|
25
|
-
const concatenateModules = !args.accurateBundles;
|
|
26
|
-
|
|
27
|
-
const entryName = entry.id;
|
|
28
|
-
let entryContent;
|
|
29
|
-
|
|
30
|
-
if (entry.code && (entry.import || entry.importedNames)) {
|
|
31
|
-
entryContent = entry.code;
|
|
32
|
-
} else if (entry.code) {
|
|
33
|
-
entryContent = entry.code;
|
|
34
|
-
} else if (entry.import) {
|
|
35
|
-
if (entry.importedNames && entry.importedNames.length > 0) {
|
|
36
|
-
// Generate named imports for each name in the importedNames array
|
|
37
|
-
const imports = entry.importedNames
|
|
38
|
-
.map((name) => `import { ${name} } from '${entry.import}';`)
|
|
39
|
-
.join('\n');
|
|
40
|
-
const logs = entry.importedNames.map((name) => `console.log(${name});`).join('\n');
|
|
41
|
-
entryContent = `${imports}\n${logs}`;
|
|
42
|
-
} else {
|
|
43
|
-
// Default to import * as if importedNames is not defined
|
|
44
|
-
entryContent = `import * as _ from '${entry.import}';\nconsole.log(_);`;
|
|
45
|
-
}
|
|
46
|
-
} else {
|
|
47
|
-
throw new Error(`Entry "${entry.id}" must have either code or import property defined`);
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
/**
|
|
51
|
-
* Generate externals function from an array of package names
|
|
52
|
-
* @param {string[]} packages - Array of package names to exclude (defaults to react and react-dom)
|
|
53
|
-
* @returns {function} - Function to determine if a request should be treated as external
|
|
54
|
-
*/
|
|
55
|
-
function createExternalsFunction(packages = ['react', 'react-dom']) {
|
|
56
|
-
/**
|
|
57
|
-
* Check if a request should be treated as external
|
|
58
|
-
* Uses the new recommended format to avoid deprecation warnings
|
|
59
|
-
* @param {{ context: string, request: string }} params - Object containing context and request
|
|
60
|
-
* @param {Function} callback - Callback to handle the result
|
|
61
|
-
*/
|
|
62
|
-
return ({ request }, callback) => {
|
|
63
|
-
// Iterate through all packages and check if request is equal to or starts with package + '/'
|
|
64
|
-
for (const pkg of packages) {
|
|
65
|
-
if (request === pkg || request.startsWith(`${pkg}/`)) {
|
|
66
|
-
return callback(null, `commonjs ${request}`);
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
return callback();
|
|
71
|
-
};
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
// Use externals from the entry object
|
|
75
|
-
const externalsArray = entry.externals || ['react', 'react-dom'];
|
|
76
|
-
|
|
77
|
-
/**
|
|
78
|
-
* @type {import('webpack').Configuration}
|
|
79
|
-
*/
|
|
80
|
-
const configuration = {
|
|
81
|
-
externals: [
|
|
82
|
-
// @ts-expect-error -- webpack types are not compatible with the current version
|
|
83
|
-
createExternalsFunction(externalsArray),
|
|
84
|
-
],
|
|
85
|
-
mode: 'production',
|
|
86
|
-
optimization: {
|
|
87
|
-
concatenateModules,
|
|
88
|
-
minimizer: [
|
|
89
|
-
new TerserPlugin({
|
|
90
|
-
test: /\.m?js(\?.*)?$/i,
|
|
91
|
-
// Avoid creating LICENSE.txt files for each module
|
|
92
|
-
// See https://github.com/webpack-contrib/terser-webpack-plugin#remove-comments
|
|
93
|
-
terserOptions: {
|
|
94
|
-
format: {
|
|
95
|
-
comments: false,
|
|
96
|
-
},
|
|
97
|
-
},
|
|
98
|
-
extractComments: false,
|
|
99
|
-
}),
|
|
100
|
-
],
|
|
101
|
-
},
|
|
102
|
-
module: {
|
|
103
|
-
rules: [
|
|
104
|
-
{
|
|
105
|
-
test: /\.[jt]sx?$/,
|
|
106
|
-
include: rootDir,
|
|
107
|
-
exclude: /node_modules/,
|
|
108
|
-
use: {
|
|
109
|
-
loader: require.resolve('babel-loader'),
|
|
110
|
-
options: {
|
|
111
|
-
presets: [
|
|
112
|
-
require.resolve('@babel/preset-react'),
|
|
113
|
-
require.resolve('@babel/preset-typescript'),
|
|
114
|
-
],
|
|
115
|
-
},
|
|
116
|
-
},
|
|
117
|
-
},
|
|
118
|
-
{
|
|
119
|
-
test: /\.css$/,
|
|
120
|
-
use: [require.resolve('css-loader')],
|
|
121
|
-
},
|
|
122
|
-
{
|
|
123
|
-
test: /\.(png|svg|jpg|gif)$/,
|
|
124
|
-
use: [require.resolve('file-loader')],
|
|
125
|
-
},
|
|
126
|
-
],
|
|
127
|
-
},
|
|
128
|
-
output: {
|
|
129
|
-
filename: '[name].js',
|
|
130
|
-
library: {
|
|
131
|
-
// TODO: Use `type: 'module'` once it is supported (currently incompatible with `externals`)
|
|
132
|
-
name: 'M',
|
|
133
|
-
type: 'var',
|
|
134
|
-
// type: 'module',
|
|
135
|
-
},
|
|
136
|
-
path: path.join(rootDir, 'build'),
|
|
137
|
-
},
|
|
138
|
-
plugins: [
|
|
139
|
-
new CompressionPlugin({
|
|
140
|
-
filename: '[path][base][fragment].gz',
|
|
141
|
-
}),
|
|
142
|
-
new BundleAnalyzerPlugin({
|
|
143
|
-
analyzerMode,
|
|
144
|
-
// We create a report for each bundle so around 120 reports.
|
|
145
|
-
// Opening them all is spam.
|
|
146
|
-
// If opened with `webpack --config . --analyze` it'll still open one new tab though.
|
|
147
|
-
openAnalyzer: false,
|
|
148
|
-
// '[name].html' not supported: https://github.com/webpack-contrib/webpack-bundle-analyzer/issues/12
|
|
149
|
-
reportFilename: `${entryName}.html`,
|
|
150
|
-
logLevel: 'warn',
|
|
151
|
-
}),
|
|
152
|
-
],
|
|
153
|
-
// A context to the current dir, which has a node_modules folder with workspace dependencies
|
|
154
|
-
context: rootDir,
|
|
155
|
-
entry: {
|
|
156
|
-
// This format is a data: url combined with inline matchResource to obtain a virtual entry.
|
|
157
|
-
// See https://github.com/webpack/webpack/issues/6437#issuecomment-874466638
|
|
158
|
-
// See https://webpack.js.org/api/module-methods/#import
|
|
159
|
-
// See https://webpack.js.org/api/loaders/#inline-matchresource
|
|
160
|
-
[entryName]: `./index.js!=!data:text/javascript;charset=utf-8;base64,${Buffer.from(entryContent.trim()).toString('base64')}`,
|
|
161
|
-
},
|
|
162
|
-
// TODO: 'browserslist:modern'
|
|
163
|
-
// See https://github.com/webpack/webpack/issues/14203
|
|
164
|
-
target: 'web',
|
|
165
|
-
};
|
|
166
|
-
|
|
167
|
-
// Return both the configuration and the externals array
|
|
168
|
-
return { configuration, externalsArray };
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
/**
|
|
172
|
-
* Process webpack stats to extract bundle sizes
|
|
173
|
-
* @param {import('webpack').Stats} webpackStats - The webpack stats object
|
|
174
|
-
* @returns {Map<string, { parsed: number, gzip: number }>} - Map of bundle names to size information
|
|
175
|
-
*/
|
|
176
|
-
function processBundleSizes(webpackStats) {
|
|
177
|
-
/** @type {Map<string, { parsed: number, gzip: number }>} */
|
|
178
|
-
const sizeMap = new Map();
|
|
179
|
-
|
|
180
|
-
if (!webpackStats) {
|
|
181
|
-
throw new Error('No webpack stats were returned');
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
if (webpackStats.hasErrors()) {
|
|
185
|
-
const statsJson = webpackStats.toJson({
|
|
186
|
-
all: false,
|
|
187
|
-
entrypoints: true,
|
|
188
|
-
errors: true,
|
|
189
|
-
});
|
|
190
|
-
|
|
191
|
-
const entrypointKeys = statsJson.entrypoints ? Object.keys(statsJson.entrypoints) : [];
|
|
192
|
-
|
|
193
|
-
throw new Error(
|
|
194
|
-
`ERROR: The following errors occurred during bundling of ${entrypointKeys.join(', ')} with webpack: \n${(
|
|
195
|
-
statsJson.errors || []
|
|
196
|
-
)
|
|
197
|
-
.map((error) => {
|
|
198
|
-
return `${JSON.stringify(error, null, 2)}`;
|
|
199
|
-
})
|
|
200
|
-
.join('\n')}`,
|
|
201
|
-
);
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
const stats = webpackStats.toJson({
|
|
205
|
-
all: false,
|
|
206
|
-
assets: true,
|
|
207
|
-
entrypoints: true,
|
|
208
|
-
relatedAssets: true,
|
|
209
|
-
});
|
|
210
|
-
|
|
211
|
-
if (!stats.assets) {
|
|
212
|
-
return sizeMap;
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
const assets = new Map(stats.assets.map((asset) => [asset.name, asset]));
|
|
216
|
-
|
|
217
|
-
if (stats.entrypoints) {
|
|
218
|
-
Object.values(stats.entrypoints).forEach((entrypoint) => {
|
|
219
|
-
let parsedSize = 0;
|
|
220
|
-
let gzipSize = 0;
|
|
221
|
-
|
|
222
|
-
if (entrypoint.assets) {
|
|
223
|
-
entrypoint.assets.forEach(({ name, size }) => {
|
|
224
|
-
const asset = assets.get(name);
|
|
225
|
-
if (asset && asset.related) {
|
|
226
|
-
const gzippedAsset = asset.related.find((relatedAsset) => {
|
|
227
|
-
return relatedAsset.type === 'gzipped';
|
|
228
|
-
});
|
|
229
|
-
|
|
230
|
-
if (size !== undefined) {
|
|
231
|
-
parsedSize += size;
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
if (gzippedAsset && gzippedAsset.size !== undefined) {
|
|
235
|
-
gzipSize += gzippedAsset.size;
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
|
-
});
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
if (!entrypoint.name) {
|
|
242
|
-
throw new Error('Entrypoint name is undefined');
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
sizeMap.set(entrypoint.name, { parsed: parsedSize, gzip: gzipSize });
|
|
246
|
-
});
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
return sizeMap;
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
/**
|
|
253
|
-
* Get sizes for a webpack bundle
|
|
254
|
-
* @param {ObjectEntry} entry - The entry configuration
|
|
255
|
-
* @param {CommandLineArgs} args - Command line arguments
|
|
256
|
-
* @returns {Promise<Map<string, { parsed: number, gzip: number }>>}
|
|
257
|
-
*/
|
|
258
|
-
export async function getWebpackSizes(entry, args) {
|
|
259
|
-
// Create webpack configuration
|
|
260
|
-
const { configuration } = await createWebpackConfig(entry, args);
|
|
261
|
-
|
|
262
|
-
// Run webpack
|
|
263
|
-
const webpackStats = await webpack(configuration);
|
|
264
|
-
|
|
265
|
-
// Process the webpack stats to get bundle sizes
|
|
266
|
-
return processBundleSizes(webpackStats);
|
|
267
|
-
}
|