@mui/internal-bundle-size-checker 1.0.0
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/.eslintrc.cjs +14 -0
- package/LICENSE +21 -0
- package/README.md +109 -0
- package/bin/bundle-size-checker.js +3 -0
- package/package.json +45 -0
- package/src/browser.d.ts +2 -0
- package/src/browser.js +2 -0
- package/src/cli.js +448 -0
- package/src/configLoader.js +189 -0
- package/src/defineConfig.js +15 -0
- package/src/fetchSnapshot.js +36 -0
- package/src/formatUtils.js +28 -0
- package/src/index.js +20 -0
- package/src/renderMarkdownReport.js +200 -0
- package/src/sizeDiff.js +199 -0
- package/src/types.d.ts +106 -0
- package/src/uploadSnapshot.js +72 -0
- package/src/worker.js +332 -0
- package/tsconfig.json +18 -0
package/src/types.d.ts
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
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
|
+
interface WebpackStats {
|
|
22
|
+
hasErrors(): boolean;
|
|
23
|
+
toJson(options: any): {
|
|
24
|
+
assets?: StatsAsset[];
|
|
25
|
+
entrypoints?: Record<string, StatsChunkGroup>;
|
|
26
|
+
errors?: any[];
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Upload configuration with optional properties
|
|
31
|
+
interface UploadConfig {
|
|
32
|
+
repo?: string; // The repository name (e.g., "mui/material-ui")
|
|
33
|
+
branch?: string; // Optional branch name (defaults to current Git branch)
|
|
34
|
+
isPullRequest?: boolean; // Whether this is a pull request build (defaults to CI detection)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Normalized upload configuration where all properties are defined
|
|
38
|
+
interface NormalizedUploadConfig {
|
|
39
|
+
repo: string; // The repository name (e.g., "mui/material-ui")
|
|
40
|
+
branch: string; // Branch name
|
|
41
|
+
isPullRequest: boolean; // Whether this is a pull request build
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// EntryPoint types
|
|
45
|
+
type StringEntry = string;
|
|
46
|
+
|
|
47
|
+
interface ObjectEntry {
|
|
48
|
+
id: string; // Unique identifier for the entry (renamed from 'name')
|
|
49
|
+
code?: string; // Code to be executed in the virtual module (now optional)
|
|
50
|
+
import?: string; // Optional package name to import
|
|
51
|
+
importedNames?: string[]; // Optional array of named imports
|
|
52
|
+
externals?: string[]; // Optional array of packages to exclude from the bundle
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
type EntryPoint = StringEntry | ObjectEntry;
|
|
56
|
+
|
|
57
|
+
// Bundle size checker config with optional upload config
|
|
58
|
+
interface BundleSizeCheckerConfig {
|
|
59
|
+
entrypoints: EntryPoint[];
|
|
60
|
+
upload?: UploadConfig | boolean | null;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Normalized bundle size checker config with all properties defined
|
|
64
|
+
interface NormalizedBundleSizeCheckerConfig {
|
|
65
|
+
entrypoints: ObjectEntry[];
|
|
66
|
+
upload: NormalizedUploadConfig | null; // null means upload is disabled
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Command line argument types
|
|
70
|
+
interface CommandLineArgs {
|
|
71
|
+
analyze?: boolean;
|
|
72
|
+
accurateBundles?: boolean;
|
|
73
|
+
output?: string;
|
|
74
|
+
verbose?: boolean;
|
|
75
|
+
filter?: string[];
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Diff command argument types
|
|
79
|
+
interface DiffCommandArgs {
|
|
80
|
+
base: string;
|
|
81
|
+
head?: string;
|
|
82
|
+
output?: 'json' | 'markdown';
|
|
83
|
+
reportUrl?: string;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// PR command argument types
|
|
87
|
+
interface PrCommandArgs {
|
|
88
|
+
prNumber: number;
|
|
89
|
+
output?: 'json' | 'markdown';
|
|
90
|
+
circleci?: string;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
interface PrInfo {
|
|
94
|
+
number: number;
|
|
95
|
+
base: {
|
|
96
|
+
ref: string;
|
|
97
|
+
sha: string;
|
|
98
|
+
repo: {
|
|
99
|
+
full_name: string;
|
|
100
|
+
};
|
|
101
|
+
};
|
|
102
|
+
head: {
|
|
103
|
+
ref: string;
|
|
104
|
+
sha: string;
|
|
105
|
+
};
|
|
106
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import { S3Client, PutObjectCommand, PutObjectTaggingCommand } from '@aws-sdk/client-s3';
|
|
3
|
+
import { execa } from 'execa';
|
|
4
|
+
import { fromEnv } from '@aws-sdk/credential-providers';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Gets the current Git commit SHA
|
|
8
|
+
* @returns {Promise<string>} The current commit SHA
|
|
9
|
+
*/
|
|
10
|
+
async function getCurrentCommitSHA() {
|
|
11
|
+
const { stdout } = await execa('git', ['rev-parse', 'HEAD']);
|
|
12
|
+
return stdout.trim();
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Uploads the size snapshot to S3
|
|
17
|
+
* @param {string} snapshotPath - The path to the size snapshot JSON file
|
|
18
|
+
* @param {NormalizedUploadConfig} uploadConfig - The normalized upload configuration
|
|
19
|
+
* @param {string} [commitSha] - Optional commit SHA (defaults to current Git HEAD)
|
|
20
|
+
* @returns {Promise<{key:string}>}
|
|
21
|
+
*/
|
|
22
|
+
export async function uploadSnapshot(snapshotPath, uploadConfig, commitSha) {
|
|
23
|
+
// By the time this function is called, the config should be fully normalized
|
|
24
|
+
// No need to check for repo existence as it's required in the normalized config
|
|
25
|
+
|
|
26
|
+
// Run git operations and file reading in parallel
|
|
27
|
+
const [sha, fileContent] = await Promise.all([
|
|
28
|
+
// Get the current commit SHA if not provided
|
|
29
|
+
commitSha || getCurrentCommitSHA(),
|
|
30
|
+
// Read the snapshot file
|
|
31
|
+
fs.promises.readFile(snapshotPath),
|
|
32
|
+
]);
|
|
33
|
+
|
|
34
|
+
// Use values from normalized config
|
|
35
|
+
const { branch, isPullRequest } = uploadConfig;
|
|
36
|
+
|
|
37
|
+
// Create S3 client (uses AWS credentials from environment)
|
|
38
|
+
const client = new S3Client({
|
|
39
|
+
region: process.env.AWS_REGION_ARTIFACTS || process.env.AWS_REGION || 'eu-central-1',
|
|
40
|
+
credentials: fromEnv(),
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
// S3 bucket and key
|
|
44
|
+
const bucket = 'mui-org-ci';
|
|
45
|
+
const key = `artifacts/${uploadConfig.repo}/${sha}/size-snapshot.json`;
|
|
46
|
+
|
|
47
|
+
// Upload the file first
|
|
48
|
+
await client.send(
|
|
49
|
+
new PutObjectCommand({
|
|
50
|
+
Bucket: bucket,
|
|
51
|
+
Key: key,
|
|
52
|
+
Body: fileContent,
|
|
53
|
+
ContentType: 'application/json',
|
|
54
|
+
}),
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
// Then add tags to the uploaded object
|
|
58
|
+
await client.send(
|
|
59
|
+
new PutObjectTaggingCommand({
|
|
60
|
+
Bucket: bucket,
|
|
61
|
+
Key: key,
|
|
62
|
+
Tagging: {
|
|
63
|
+
TagSet: [
|
|
64
|
+
{ Key: 'isPullRequest', Value: isPullRequest ? 'yes' : 'no' },
|
|
65
|
+
{ Key: 'branch', Value: branch },
|
|
66
|
+
],
|
|
67
|
+
},
|
|
68
|
+
}),
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
return { key };
|
|
72
|
+
}
|
package/src/worker.js
ADDED
|
@@ -0,0 +1,332 @@
|
|
|
1
|
+
import { promisify } from 'util';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import fs from 'fs/promises';
|
|
4
|
+
import { pathToFileURL } from 'url';
|
|
5
|
+
import webpackCallbackBased from 'webpack';
|
|
6
|
+
import CompressionPlugin from 'compression-webpack-plugin';
|
|
7
|
+
import TerserPlugin from 'terser-webpack-plugin';
|
|
8
|
+
import { BundleAnalyzerPlugin } from 'webpack-bundle-analyzer';
|
|
9
|
+
import { createRequire } from 'node:module';
|
|
10
|
+
import chalk from 'chalk';
|
|
11
|
+
import { byteSizeFormatter } from './formatUtils.js';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* @type {(options: webpackCallbackBased.Configuration) => Promise<webpackCallbackBased.Stats>}
|
|
15
|
+
*/
|
|
16
|
+
// @ts-expect-error Can't select the right overload
|
|
17
|
+
const webpack = promisify(webpackCallbackBased);
|
|
18
|
+
const rootDir = process.cwd();
|
|
19
|
+
const require = createRequire(import.meta.url);
|
|
20
|
+
|
|
21
|
+
// Type declarations are now in types.d.ts
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Attempts to extract peer dependencies from a package's package.json
|
|
25
|
+
* @param {string} packageName - Package to extract peer dependencies from
|
|
26
|
+
* @returns {Promise<string[]|null>} - Array of peer dependency package names or null if not found
|
|
27
|
+
*/
|
|
28
|
+
async function getPeerDependencies(packageName) {
|
|
29
|
+
try {
|
|
30
|
+
// Try to resolve packageName/package.json
|
|
31
|
+
const packageJsonPath = require.resolve(`${packageName}/package.json`, {
|
|
32
|
+
paths: [rootDir],
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
// Read and parse the package.json
|
|
36
|
+
const packageJsonContent = await fs.readFile(packageJsonPath, 'utf8');
|
|
37
|
+
const packageJson = JSON.parse(packageJsonContent);
|
|
38
|
+
|
|
39
|
+
// Extract peer dependencies
|
|
40
|
+
if (packageJson.peerDependencies) {
|
|
41
|
+
return Object.keys(packageJson.peerDependencies);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return null;
|
|
45
|
+
} catch (/** @type {any} */ error) {
|
|
46
|
+
console.warn(
|
|
47
|
+
chalk.yellow(
|
|
48
|
+
`Could not resolve peer dependencies for ${chalk.bold(packageName)}: ${error.message}`,
|
|
49
|
+
),
|
|
50
|
+
);
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Creates webpack configuration for bundle size checking
|
|
57
|
+
* @param {ObjectEntry} entry - Entry point (string or object)
|
|
58
|
+
* @param {CommandLineArgs} args
|
|
59
|
+
* @returns {Promise<{configuration: import('webpack').Configuration, externalsArray: string[]}>}
|
|
60
|
+
*/
|
|
61
|
+
async function createWebpackConfig(entry, args) {
|
|
62
|
+
const analyzerMode = args.analyze ? 'static' : 'disabled';
|
|
63
|
+
const concatenateModules = !args.accurateBundles;
|
|
64
|
+
|
|
65
|
+
const entryName = entry.id;
|
|
66
|
+
let entryContent;
|
|
67
|
+
let packageExternals = null;
|
|
68
|
+
|
|
69
|
+
// Process peer dependencies if externals aren't specified but import is
|
|
70
|
+
if (entry.import && !entry.externals) {
|
|
71
|
+
const packageRoot = entry.import
|
|
72
|
+
.split('/')
|
|
73
|
+
.slice(0, entry.import.startsWith('@') ? 2 : 1)
|
|
74
|
+
.join('/');
|
|
75
|
+
packageExternals = await getPeerDependencies(packageRoot);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (entry.code && (entry.import || entry.importedNames)) {
|
|
79
|
+
console.warn(
|
|
80
|
+
chalk.yellow(
|
|
81
|
+
`Warning: Both code and import/importedNames are defined for entry "${chalk.bold(entry.id)}". Using code property.`,
|
|
82
|
+
),
|
|
83
|
+
);
|
|
84
|
+
entryContent = entry.code;
|
|
85
|
+
} else if (entry.code) {
|
|
86
|
+
entryContent = entry.code;
|
|
87
|
+
} else if (entry.import) {
|
|
88
|
+
if (entry.importedNames && entry.importedNames.length > 0) {
|
|
89
|
+
// Generate named imports for each name in the importedNames array
|
|
90
|
+
const imports = entry.importedNames
|
|
91
|
+
.map((name) => `import { ${name} } from '${entry.import}';`)
|
|
92
|
+
.join('\n');
|
|
93
|
+
const logs = entry.importedNames.map((name) => `console.log(${name});`).join('\n');
|
|
94
|
+
entryContent = `${imports}\n${logs}`;
|
|
95
|
+
} else {
|
|
96
|
+
// Default to import * as if importedNames is not defined
|
|
97
|
+
entryContent = `import * as _ from '${entry.import}';\nconsole.log(_);`;
|
|
98
|
+
}
|
|
99
|
+
} else {
|
|
100
|
+
throw new Error(`Entry "${entry.id}" must have either code or import property defined`);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Generate externals function from an array of package names
|
|
105
|
+
* @param {string[]} packages - Array of package names to exclude (defaults to react and react-dom)
|
|
106
|
+
* @returns {function} - Function to determine if a request should be treated as external
|
|
107
|
+
*/
|
|
108
|
+
function createExternalsFunction(packages = ['react', 'react-dom']) {
|
|
109
|
+
/**
|
|
110
|
+
* Check if a request should be treated as external
|
|
111
|
+
* Uses the new recommended format to avoid deprecation warnings
|
|
112
|
+
* @param {{ context: string, request: string }} params - Object containing context and request
|
|
113
|
+
* @param {Function} callback - Callback to handle the result
|
|
114
|
+
*/
|
|
115
|
+
return ({ request }, callback) => {
|
|
116
|
+
// Iterate through all packages and check if request is equal to or starts with package + '/'
|
|
117
|
+
for (const pkg of packages) {
|
|
118
|
+
if (request === pkg || request.startsWith(`${pkg}/`)) {
|
|
119
|
+
return callback(null, `commonjs ${request}`);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
return callback();
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Generate externals based on priorities:
|
|
128
|
+
// 1. Explicitly defined externals in the entry object
|
|
129
|
+
// 2. Peer dependencies from package.json if available
|
|
130
|
+
// 3. Default externals (react, react-dom)
|
|
131
|
+
const externalsArray =
|
|
132
|
+
typeof entry === 'object' && entry.externals
|
|
133
|
+
? entry.externals
|
|
134
|
+
: (packageExternals ?? ['react', 'react-dom']);
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* @type {import('webpack').Configuration}
|
|
138
|
+
*/
|
|
139
|
+
const configuration = {
|
|
140
|
+
externals: [
|
|
141
|
+
// @ts-expect-error -- webpack types are not compatible with the current version
|
|
142
|
+
createExternalsFunction(externalsArray),
|
|
143
|
+
],
|
|
144
|
+
mode: 'production',
|
|
145
|
+
optimization: {
|
|
146
|
+
concatenateModules,
|
|
147
|
+
minimizer: [
|
|
148
|
+
new TerserPlugin({
|
|
149
|
+
test: /\.m?js(\?.*)?$/i,
|
|
150
|
+
// Avoid creating LICENSE.txt files for each module
|
|
151
|
+
// See https://github.com/webpack-contrib/terser-webpack-plugin#remove-comments
|
|
152
|
+
terserOptions: {
|
|
153
|
+
format: {
|
|
154
|
+
comments: false,
|
|
155
|
+
},
|
|
156
|
+
},
|
|
157
|
+
extractComments: false,
|
|
158
|
+
}),
|
|
159
|
+
],
|
|
160
|
+
},
|
|
161
|
+
module: {
|
|
162
|
+
rules: [
|
|
163
|
+
{
|
|
164
|
+
test: /\.css$/,
|
|
165
|
+
use: [require.resolve('css-loader')],
|
|
166
|
+
},
|
|
167
|
+
{
|
|
168
|
+
test: /\.(png|svg|jpg|gif)$/,
|
|
169
|
+
use: [require.resolve('file-loader')],
|
|
170
|
+
},
|
|
171
|
+
],
|
|
172
|
+
},
|
|
173
|
+
output: {
|
|
174
|
+
filename: '[name].js',
|
|
175
|
+
library: {
|
|
176
|
+
// TODO: Use `type: 'module'` once it is supported (currently incompatible with `externals`)
|
|
177
|
+
name: 'M',
|
|
178
|
+
type: 'var',
|
|
179
|
+
// type: 'module',
|
|
180
|
+
},
|
|
181
|
+
path: path.join(rootDir, 'build'),
|
|
182
|
+
},
|
|
183
|
+
plugins: [
|
|
184
|
+
new CompressionPlugin({
|
|
185
|
+
filename: '[path][base][fragment].gz',
|
|
186
|
+
}),
|
|
187
|
+
new BundleAnalyzerPlugin({
|
|
188
|
+
analyzerMode,
|
|
189
|
+
// We create a report for each bundle so around 120 reports.
|
|
190
|
+
// Opening them all is spam.
|
|
191
|
+
// If opened with `webpack --config . --analyze` it'll still open one new tab though.
|
|
192
|
+
openAnalyzer: false,
|
|
193
|
+
// '[name].html' not supported: https://github.com/webpack-contrib/webpack-bundle-analyzer/issues/12
|
|
194
|
+
reportFilename: `${entryName}.html`,
|
|
195
|
+
logLevel: 'warn',
|
|
196
|
+
}),
|
|
197
|
+
],
|
|
198
|
+
// A context to the current dir, which has a node_modules folder with workspace dependencies
|
|
199
|
+
context: rootDir,
|
|
200
|
+
entry: {
|
|
201
|
+
// This format is a data: url combined with inline matchResource to obtain a virtual entry.
|
|
202
|
+
// See https://github.com/webpack/webpack/issues/6437#issuecomment-874466638
|
|
203
|
+
// See https://webpack.js.org/api/module-methods/#import
|
|
204
|
+
// See https://webpack.js.org/api/loaders/#inline-matchresource
|
|
205
|
+
[entryName]: `./index.js!=!data:text/javascript;charset=utf-8;base64,${Buffer.from(entryContent.trim()).toString('base64')}`,
|
|
206
|
+
},
|
|
207
|
+
// TODO: 'browserslist:modern'
|
|
208
|
+
// See https://github.com/webpack/webpack/issues/14203
|
|
209
|
+
target: 'web',
|
|
210
|
+
};
|
|
211
|
+
|
|
212
|
+
// Return both the configuration and the externals array
|
|
213
|
+
return { configuration, externalsArray };
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Get sizes for a bundle
|
|
218
|
+
* @param {{ entry: ObjectEntry, args: CommandLineArgs, index: number, total: number }} options
|
|
219
|
+
* @returns {Promise<Array<[string, { parsed: number, gzip: number }]>>}
|
|
220
|
+
*/
|
|
221
|
+
export default async function getSizes({ entry, args, index, total }) {
|
|
222
|
+
/** @type {Map<string, { parsed: number, gzip: number }>} */
|
|
223
|
+
const sizeMap = new Map();
|
|
224
|
+
|
|
225
|
+
// Create webpack configuration (now async to handle peer dependency resolution)
|
|
226
|
+
const { configuration, externalsArray } = await createWebpackConfig(entry, args);
|
|
227
|
+
|
|
228
|
+
// eslint-disable-next-line no-console -- process monitoring
|
|
229
|
+
console.log(chalk.blue(`Compiling ${index + 1}/${total}: ${chalk.bold(`[${entry.id}]`)}`));
|
|
230
|
+
|
|
231
|
+
const webpackStats = await webpack(configuration);
|
|
232
|
+
|
|
233
|
+
if (!webpackStats) {
|
|
234
|
+
throw new Error('No webpack stats were returned');
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
if (webpackStats.hasErrors()) {
|
|
238
|
+
const statsJson = webpackStats.toJson({
|
|
239
|
+
all: false,
|
|
240
|
+
entrypoints: true,
|
|
241
|
+
errors: true,
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
const entrypointKeys = statsJson.entrypoints ? Object.keys(statsJson.entrypoints) : [];
|
|
245
|
+
|
|
246
|
+
throw new Error(
|
|
247
|
+
`${chalk.red.bold('ERROR:')} The following errors occurred during bundling of ${chalk.yellow(entrypointKeys.join(', '))} with webpack: \n${(
|
|
248
|
+
statsJson.errors || []
|
|
249
|
+
)
|
|
250
|
+
.map((error) => {
|
|
251
|
+
return `${JSON.stringify(error, null, 2)}`;
|
|
252
|
+
})
|
|
253
|
+
.join('\n')}`,
|
|
254
|
+
);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
const stats = webpackStats.toJson({
|
|
258
|
+
all: false,
|
|
259
|
+
assets: true,
|
|
260
|
+
entrypoints: true,
|
|
261
|
+
relatedAssets: true,
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
if (!stats.assets) {
|
|
265
|
+
return Array.from(sizeMap.entries());
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
const assets = new Map(stats.assets.map((asset) => [asset.name, asset]));
|
|
269
|
+
|
|
270
|
+
if (stats.entrypoints) {
|
|
271
|
+
Object.values(stats.entrypoints).forEach((entrypoint) => {
|
|
272
|
+
let parsedSize = 0;
|
|
273
|
+
let gzipSize = 0;
|
|
274
|
+
|
|
275
|
+
if (entrypoint.assets) {
|
|
276
|
+
entrypoint.assets.forEach(({ name, size }) => {
|
|
277
|
+
const asset = assets.get(name);
|
|
278
|
+
if (asset && asset.related) {
|
|
279
|
+
const gzippedAsset = asset.related.find((relatedAsset) => {
|
|
280
|
+
return relatedAsset.type === 'gzipped';
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
if (size !== undefined) {
|
|
284
|
+
parsedSize += size;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
if (gzippedAsset && gzippedAsset.size !== undefined) {
|
|
288
|
+
gzipSize += gzippedAsset.size;
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
});
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
if (!entrypoint.name) {
|
|
295
|
+
throw new Error('Entrypoint name is undefined');
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
sizeMap.set(entrypoint.name, { parsed: parsedSize, gzip: gzipSize });
|
|
299
|
+
});
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// Create a concise log message showing import details
|
|
303
|
+
let entryDetails = '';
|
|
304
|
+
if (entry.code) {
|
|
305
|
+
entryDetails = 'code import';
|
|
306
|
+
} else if (entry.import) {
|
|
307
|
+
entryDetails = `${entry.import}`;
|
|
308
|
+
if (entry.importedNames && entry.importedNames.length > 0) {
|
|
309
|
+
entryDetails += ` [${entry.importedNames.join(', ')}]`;
|
|
310
|
+
} else {
|
|
311
|
+
entryDetails += ' [*]';
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
// Print a summary with all the requested information
|
|
316
|
+
// Get the size entry for this entry.id from the map
|
|
317
|
+
const entrySize = sizeMap.get(entry.id) || { parsed: 0, gzip: 0 };
|
|
318
|
+
|
|
319
|
+
// eslint-disable-next-line no-console -- process monitoring
|
|
320
|
+
console.log(
|
|
321
|
+
`
|
|
322
|
+
${chalk.green('✓')} ${chalk.green.bold(`Completed ${index + 1}/${total}: [${entry.id}]`)}
|
|
323
|
+
${chalk.cyan('Import:')} ${entryDetails}
|
|
324
|
+
${chalk.cyan('Externals:')} ${externalsArray.join(', ')}
|
|
325
|
+
${chalk.cyan('Sizes:')} ${chalk.yellow(byteSizeFormatter.format(entrySize.parsed))} (${chalk.yellow(byteSizeFormatter.format(entrySize.gzip))} gzipped)
|
|
326
|
+
${args.analyze ? ` ${chalk.cyan('Analysis:')} ${chalk.underline(pathToFileURL(path.join(rootDir, 'build', `${entry.id}.html`)).href)}` : ''}
|
|
327
|
+
`.trim(),
|
|
328
|
+
);
|
|
329
|
+
|
|
330
|
+
// Convert the Map to an array of entries for the return value
|
|
331
|
+
return Array.from(sizeMap.entries());
|
|
332
|
+
}
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"module": "ESNext",
|
|
5
|
+
"moduleResolution": "node",
|
|
6
|
+
"allowJs": true,
|
|
7
|
+
"checkJs": true,
|
|
8
|
+
"skipLibCheck": true,
|
|
9
|
+
"strict": true,
|
|
10
|
+
"esModuleInterop": true,
|
|
11
|
+
"resolveJsonModule": true,
|
|
12
|
+
"isolatedModules": true,
|
|
13
|
+
"outDir": "./build",
|
|
14
|
+
"noEmit": true
|
|
15
|
+
},
|
|
16
|
+
"include": ["**/*.js", "**/*.mjs", "**/*.ts"],
|
|
17
|
+
"exclude": ["node_modules", "build"]
|
|
18
|
+
}
|