@mui/internal-bundle-size-checker 1.0.9-canary.18 → 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 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.18",
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": "f2c9828b5cdaed4deb48c54804882c0e315ae42f",
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<{configuration: import('vite').InlineConfig, externalsArray: string[]}>}
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 { configuration, externalsArray };
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 getViteSizes(entry, args) {
236
+ export async function getBundleSizes(entry, args) {
237
237
  // Create vite configuration
238
- const { configuration } = await createViteConfig(entry, args);
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
- * 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
-
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 getWebpackSizes(args, config) {
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 webpackSizes = await getWebpackSizes(argv, config);
150
- const bundleSizes = Object.fromEntries(webpackSizes.sort((a, b) => a[0].localeCompare(b[0])));
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(bundleSizes, null, 2));
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 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.',
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:
@@ -1,10 +1,10 @@
1
- import { octokit } from './github.js';
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
@@ -1,3 +1,5 @@
1
+ /// <reference types="./types.d.ts" />
2
+
1
3
  import defineConfig from './defineConfig.js';
2
4
  import { loadConfig } from './configLoader.js';
3
5
  import { calculateSizeDiff } from './sizeDiff.js';
@@ -5,9 +5,11 @@
5
5
  */
6
6
 
7
7
  import { calculateSizeDiff } from './sizeDiff.js';
8
- import { fetchSnapshot, fetchSnapshotWithFallback } from './fetchSnapshot.js';
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
- const { data } = await octokit.repos.compareCommits({
236
- owner,
237
- repo: repoName,
238
- base: prInfo.base.sha,
239
- head: prCommit,
240
- });
241
- const baseCommit = data.merge_base_commit.sha;
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(fetchSnapshotModule.fetchSnapshotWithFallback);
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/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 { getWebpackSizes } from './webpackBuilder.js';
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
- let sizeMap;
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
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "compilerOptions": {
3
3
  "target": "ES2022",
4
- "module": "ESNext",
5
- "moduleResolution": "node",
4
+ "module": "nodenext",
5
+ "moduleResolution": "nodenext",
6
6
  "allowJs": true,
7
7
  "checkJs": true,
8
8
  "skipLibCheck": true,
@@ -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
- }