Skip to content

Commit d0c2a16

Browse files
authored
[core] Prevent unstable chunks in size snapshot (#23181)
1 parent c60fbba commit d0c2a16

File tree

3 files changed

+118
-84
lines changed

3 files changed

+118
-84
lines changed

azure-pipelines.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,12 @@ steps:
4747
yarn install
4848
displayName: 'install dependencies'
4949
50+
- task: Cache@2
51+
inputs:
52+
key: 'node-modules-cache | yarn.lock'
53+
path: node_modules/.cache
54+
displayName: Cache node_modules/.cache
55+
5056
- script: |
5157
yarn danger ci
5258
displayName: 'prepare danger on PRs'

scripts/sizeSnapshot/create.js

Lines changed: 55 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ const lodash = require('lodash');
33
const path = require('path');
44
const { promisify } = require('util');
55
const webpackCallbackBased = require('webpack');
6+
const yargs = require('yargs');
67
const createWebpackConfig = require('./webpack.config');
78

89
const webpack = promisify(webpackCallbackBased);
@@ -31,27 +32,38 @@ async function getRollupSize(snapshotPath) {
3132
/**
3233
* creates size snapshot for every bundle that built with webpack
3334
*/
34-
async function getWebpackSizes() {
35+
async function getWebpackSizes(webpackEnvironment) {
3536
await fse.mkdirp(path.join(__dirname, 'build'));
3637

37-
// webpack --config $configPath --json > $statsPath
38-
// will create a 300MB big json file which sometimes requires up to 1.5GB
39-
// memory. This will sometimes crash node in azure pipelines with "heap out of memory"
40-
const webpackStats = await webpack(await createWebpackConfig(webpack));
41-
const stats = webpackStats.toJson();
42-
if (stats.errors.length > 0) {
43-
throw new Error(
44-
`The following errors occured during bundling with webpack: \n${stats.errors.join('\n')}`,
45-
);
46-
}
38+
const configurations = await createWebpackConfig(webpack, webpackEnvironment);
39+
const webpackMultiStats = await webpack(configurations);
40+
41+
const sizes = [];
42+
webpackMultiStats.stats.forEach((webpackStats) => {
43+
if (webpackStats.hasErrors()) {
44+
const { entrypoints, errors } = webpackStats.toJson({
45+
all: false,
46+
entrypoints: true,
47+
errors: true,
48+
});
49+
throw new Error(
50+
`The following errors occured during bundling of ${Object.keys(
51+
entrypoints,
52+
)} with webpack: \n${errors.join('\n')}`,
53+
);
54+
}
4755

48-
const assets = new Map(stats.assets.map((asset) => [asset.name, asset]));
56+
const stats = webpackStats.toJson({ all: false, assets: true });
57+
const assets = new Map(stats.assets.map((asset) => [asset.name, asset]));
4958

50-
return Object.entries(stats.assetsByChunkName).map(([chunkName, assetName]) => {
51-
const parsedSize = assets.get(assetName).size;
52-
const gzipSize = assets.get(`${assetName}.gz`).size;
53-
return [chunkName, { parsed: parsedSize, gzip: gzipSize }];
59+
Object.entries(stats.assetsByChunkName).forEach(([chunkName, assetName]) => {
60+
const parsedSize = assets.get(assetName).size;
61+
const gzipSize = assets.get(`${assetName}.gz`).size;
62+
sizes.push([chunkName, { parsed: parsedSize, gzip: gzipSize }]);
63+
});
5464
});
65+
66+
return sizes;
5567
}
5668

5769
// waiting for String.prototype.matchAll in node 10
@@ -149,18 +161,39 @@ async function getNextPagesSize() {
149161
return entries;
150162
}
151163

152-
async function run() {
164+
async function run(argv) {
165+
const { analyze } = argv;
166+
153167
const rollupBundles = [path.join(workspaceRoot, 'packages/material-ui/size-snapshot.json')];
154168
const bundleSizes = lodash.fromPairs([
155-
...(await getWebpackSizes()),
169+
...(await getWebpackSizes({ analyze })),
156170
...lodash.flatten(await Promise.all(rollupBundles.map(getRollupSize))),
157171
...(await getNextPagesSize()),
158172
]);
159173

160174
await fse.writeJSON(snapshotDestPath, bundleSizes, { spaces: 2 });
161175
}
162176

163-
run().catch((err) => {
164-
console.error(err);
165-
process.exit(1);
166-
});
177+
yargs
178+
.command({
179+
command: '$0',
180+
description: 'Saves a size snapshot in size-snapshot.json',
181+
builder: (command) => {
182+
return command
183+
.option('analyze', {
184+
default: false,
185+
describe: 'Creates a webpack-bundle-analyzer report for each bundle.',
186+
type: 'boolean',
187+
})
188+
.option('accurateBundles', {
189+
default: false,
190+
describe: 'Displays used bundles accurately at the cost of accurate bundle size.',
191+
type: 'boolean',
192+
});
193+
},
194+
handler: run,
195+
})
196+
.help()
197+
.strict(true)
198+
.version(false)
199+
.parse();
Lines changed: 57 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
1-
const globCallback = require('glob');
21
const path = require('path');
2+
const { promisify } = require('util');
33
const CompressionPlugin = require('compression-webpack-plugin');
4+
const globCallback = require('glob');
45
const TerserPlugin = require('terser-webpack-plugin');
5-
const { promisify } = require('util');
6-
7-
const glob = promisify(globCallback);
6+
const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer');
87

98
const workspaceRoot = path.join(__dirname, '..', '..');
109

11-
async function getSizeLimitBundles() {
10+
const glob = promisify(globCallback);
11+
12+
async function getWebpackEntries() {
1213
const corePackagePath = path.join(workspaceRoot, 'packages/material-ui/build');
1314
const coreComponents = (await glob(path.join(corePackagePath, '[A-Z]*/index.js'))).map(
1415
(componentPath) => {
@@ -25,7 +26,6 @@ async function getSizeLimitBundles() {
2526

2627
return {
2728
name: entryName,
28-
webpack: true,
2929
path: path.relative(workspaceRoot, path.dirname(componentPath)),
3030
};
3131
},
@@ -38,7 +38,6 @@ async function getSizeLimitBundles() {
3838

3939
return {
4040
name: componentName,
41-
webpack: true,
4241
path: path.relative(workspaceRoot, path.dirname(componentPath)),
4342
};
4443
},
@@ -47,54 +46,44 @@ async function getSizeLimitBundles() {
4746
return [
4847
{
4948
name: '@material-ui/core',
50-
webpack: true,
5149
path: path.join(path.relative(workspaceRoot, corePackagePath), 'index.js'),
5250
},
5351
{
5452
name: '@material-ui/lab',
55-
webpack: true,
5653
path: path.join(path.relative(workspaceRoot, labPackagePath), 'index.js'),
5754
},
5855
{
5956
name: '@material-ui/styles',
60-
webpack: true,
6157
path: 'packages/material-ui-styles/build/index.js',
6258
},
6359
{
6460
name: '@material-ui/system',
65-
webpack: true,
6661
path: 'packages/material-ui-system/build/esm/index.js',
6762
},
6863
...coreComponents,
6964
{
7065
name: '@material-ui/core/styles/createMuiTheme',
71-
webpack: true,
7266
path: 'packages/material-ui/build/styles/createMuiTheme.js',
7367
},
7468
{
7569
name: 'colorManipulator',
76-
webpack: true,
7770
path: 'packages/material-ui/build/styles/colorManipulator.js',
7871
},
7972
...labComponents,
8073
{
8174
name: 'useAutocomplete',
82-
webpack: true,
8375
path: 'packages/material-ui-lab/build/useAutocomplete/index.js',
8476
},
8577
{
8678
name: '@material-ui/core/useMediaQuery',
87-
webpack: true,
8879
path: 'packages/material-ui/build/useMediaQuery/index.js',
8980
},
9081
{
9182
name: '@material-ui/core/useScrollTrigger',
92-
webpack: true,
9383
path: 'packages/material-ui/build/useScrollTrigger/index.js',
9484
},
9585
{
9686
name: '@material-ui/utils',
97-
webpack: true,
9887
path: 'packages/material-ui-utils/build/esm/index.js',
9988
},
10089
// TODO: Requires webpack v5
@@ -106,59 +95,65 @@ async function getSizeLimitBundles() {
10695
// },
10796
{
10897
name: '@material-ui/core.legacy',
109-
webpack: true,
11098
path: path.join(path.relative(workspaceRoot, corePackagePath), 'legacy/index.js'),
11199
},
112100
];
113101
}
114102

115-
module.exports = async function webpackConfig() {
116-
const entries = await getSizeLimitBundles();
117-
const entry = entries.reduce((acc, bundle) => {
118-
acc[bundle.name] = path.join(workspaceRoot, bundle.path);
119-
return acc;
120-
}, {});
103+
module.exports = async function webpackConfig(webpack, environment) {
104+
const analyzerMode = environment.analyze ? 'static' : 'disabled';
105+
const concatenateModules = !environment.accurateBundles;
121106

122-
const config = {
123-
entry,
124-
// ideally this would be computed from the bundles peer dependencies
125-
externals: /^(react|react-dom|react\/jsx-runtime)$/,
126-
mode: 'production',
127-
optimization: {
128-
// Otherwise bundles with that include chunks for which we track the size separately are penalized
129-
// e.g. without this option `@material-ui/core.legacy` would be smaller since it could concatenate all modules
130-
// while `@material-ui/core` had to import the chunks from all the components.
131-
// Ideally we could just disable shared chunks but I couldn't figure out how.
132-
concatenateModules: false,
133-
minimizer: [
134-
new TerserPlugin({
135-
test: /\.js(\?.*)?$/i,
107+
const entries = await getWebpackEntries();
108+
const configurations = entries.map((entry) => {
109+
return {
110+
// ideally this would be computed from the bundles peer dependencies
111+
externals: /^(react|react-dom|react\/jsx-runtime)$/,
112+
mode: 'production',
113+
optimization: {
114+
concatenateModules,
115+
minimizer: [
116+
new TerserPlugin({
117+
test: /\.js(\?.*)?$/i,
118+
}),
119+
],
120+
},
121+
output: {
122+
filename: '[name].js',
123+
path: path.join(__dirname, 'build'),
124+
},
125+
plugins: [
126+
new CompressionPlugin(),
127+
new BundleAnalyzerPlugin({
128+
analyzerMode,
129+
// We create a report for each bundle so around 120 reports.
130+
// Opening them all is spam.
131+
// If opened with `webpack --config . --analyze` it'll still open one new tab though.
132+
openAnalyzer: false,
133+
// '[name].html' not supported: https://github.com/webpack-contrib/webpack-bundle-analyzer/issues/12
134+
reportFilename: `${entry.name}.html`,
136135
}),
137136
],
138-
},
139-
output: {
140-
filename: '[name].js',
141-
path: path.join(__dirname, 'build'),
142-
},
143-
plugins: [new CompressionPlugin()],
144-
resolve: {
145-
alias: {
146-
'@material-ui/core': path.join(workspaceRoot, 'packages/material-ui/build'),
147-
'@material-ui/lab': path.join(workspaceRoot, 'packages/material-ui-lab/build'),
148-
'@material-ui/styled-engine': path.join(
149-
workspaceRoot,
150-
'packages/material-ui-styled-engine/build',
151-
),
152-
'@material-ui/styled-engine-sc': path.join(
153-
workspaceRoot,
154-
'packages/material-ui-styles-sc/build',
155-
),
156-
'@material-ui/styles': path.join(workspaceRoot, 'packages/material-ui-styles/build'),
157-
'@material-ui/system': path.join(workspaceRoot, 'packages/material-ui-system/build'),
158-
'@material-ui/utils': path.join(workspaceRoot, 'packages/material-ui-utils/build'),
137+
resolve: {
138+
alias: {
139+
'@material-ui/core': path.join(workspaceRoot, 'packages/material-ui/build'),
140+
'@material-ui/lab': path.join(workspaceRoot, 'packages/material-ui-lab/build'),
141+
'@material-ui/styled-engine': path.join(
142+
workspaceRoot,
143+
'packages/material-ui-styled-engine/build',
144+
),
145+
'@material-ui/styled-engine-sc': path.join(
146+
workspaceRoot,
147+
'packages/material-ui-styles-sc/build',
148+
),
149+
'@material-ui/styles': path.join(workspaceRoot, 'packages/material-ui-styles/build'),
150+
'@material-ui/system': path.join(workspaceRoot, 'packages/material-ui-system/build'),
151+
'@material-ui/utils': path.join(workspaceRoot, 'packages/material-ui-utils/build'),
152+
},
159153
},
160-
},
161-
};
154+
entry: { [entry.name]: path.join(workspaceRoot, entry.path) },
155+
};
156+
});
162157

163-
return config;
158+
return configurations;
164159
};

0 commit comments

Comments
 (0)