Skip to content
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,11 @@ _Note: Gaps between patch versions are faulty, broken or test releases._

## UNRELEASED

* **Bug Fix**
* Prevent `TypeError` when `assets` or `modules` are undefined in `analyzer.js`
([#679](https://github.com/webpack-contrib/webpack-bundle-analyzer/pull/679) by [@Srushti-33](https://github.com/Srushti-33))


* **Breaking Change**
* Remove explicit support for Node versions below 20.9.0 ([#676](https://github.com/webpack-contrib/webpack-bundle-analyzer/pull/676) by [@valscion](https://github.com/valscion))

Expand Down
48 changes: 34 additions & 14 deletions src/analyzer.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,23 @@

const isAssetIncluded = createAssetsFilter(excludeAssets);

// Sometimes all the information is located in `children` array (e.g. problem in #10)
if (
(bundleStats.assets == null || bundleStats.assets.length === 0)
&& bundleStats.children && bundleStats.children.length > 0
) {
// Handle minimal stats format that only has assetsByChunkName but no assets array

Check failure on line 29 in src/analyzer.js

View workflow job for this annotation

GitHub Actions / lint

Expected indentation of 2 spaces but found 0
if ((bundleStats.assets == null || bundleStats.assets.length === 0) && bundleStats.assetsByChunkName) {

Check failure on line 30 in src/analyzer.js

View workflow job for this annotation

GitHub Actions / lint

Expected indentation of 2 spaces but found 0
// Convert assetsByChunkName to assets array for minimal stats
bundleStats.assets = [];

Check failure on line 32 in src/analyzer.js

View workflow job for this annotation

GitHub Actions / lint

Expected indentation of 4 spaces but found 2
Object.entries(bundleStats.assetsByChunkName).forEach(([chunkName, assetNames]) => {

Check failure on line 33 in src/analyzer.js

View workflow job for this annotation

GitHub Actions / lint

Expected indentation of 4 spaces but found 2
assetNames.forEach(assetName => {

Check failure on line 34 in src/analyzer.js

View workflow job for this annotation

GitHub Actions / lint

Expected indentation of 6 spaces but found 4
bundleStats.assets.push({

Check failure on line 35 in src/analyzer.js

View workflow job for this annotation

GitHub Actions / lint

Expected indentation of 8 spaces but found 6
name: assetName,

Check failure on line 36 in src/analyzer.js

View workflow job for this annotation

GitHub Actions / lint

Expected indentation of 10 spaces but found 8
chunks: [chunkName],

Check failure on line 37 in src/analyzer.js

View workflow job for this annotation

GitHub Actions / lint

Expected indentation of 10 spaces but found 8
size: 0 // Default size for minimal stats

Check failure on line 38 in src/analyzer.js

View workflow job for this annotation

GitHub Actions / lint

Expected comment to be above code

Check failure on line 38 in src/analyzer.js

View workflow job for this annotation

GitHub Actions / lint

Expected indentation of 10 spaces but found 8
});
});
});
}

// Sometimes all the information is located in `children` array (e.g. problem in #10)
if ((bundleStats.assets == null || bundleStats.assets.length === 0) && bundleStats.children && bundleStats.children.length > 0) {
const {children} = bundleStats;
bundleStats = bundleStats.children[0];
// Sometimes if there are additional child chunks produced add them as child assets,
Expand All @@ -52,7 +64,7 @@
}

// Picking only `*.js, *.cjs or *.mjs` assets from bundle that has non-empty `chunks` array
bundleStats.assets = bundleStats.assets.filter(asset => {
bundleStats.assets = (bundleStats.assets || []).filter(asset => {
// Filter out non 'asset' type asset if type is provided (Webpack 5 add a type to indicate asset types)
if (asset.type && asset.type !== 'asset') {
return false;
Expand Down Expand Up @@ -116,7 +128,7 @@
}

// Picking modules from current bundle script
let assetModules = modules.filter(statModule => assetHasModule(statAsset, statModule));
let assetModules = (modules || []).filter(statModule => assetHasModule(statAsset, statModule));

// Adding parsed sources
if (parsedModules) {
Expand All @@ -140,7 +152,7 @@
unparsedEntryModules[0].parsedSrc = assetSources.runtimeSrc;
} else {
// If there are multiple entry points we move all of them under synthetic concatenated module.
assetModules = assetModules.filter(mod => !unparsedEntryModules.includes(mod));
assetModules = (assetModules || []).filter(mod => !unparsedEntryModules.includes(mod));
assetModules.unshift({
identifier: './entry modules',
name: './entry modules',
Expand Down Expand Up @@ -189,13 +201,21 @@
}

function getBundleModules(bundleStats) {
// Handle case where bundleStats is undefined or has no modules/chunks
if (!bundleStats) {
return [];
}

const seenIds = new Set();

return flatten(
((bundleStats.chunks?.map(chunk => chunk.modules)) || [])
.concat(bundleStats.modules)
.filter(Boolean)
).filter(mod => {

// Safely handle chunks and modules that might be undefined
const chunksModules = bundleStats.chunks ?
bundleStats.chunks.map(chunk => chunk.modules || []) :
[];

const statsModules = bundleStats.modules || [];

return flatten(chunksModules.concat(statsModules).filter(Boolean)).filter(mod => {
// Filtering out Webpack's runtime modules as they don't have ids and can't be parsed (introduced in Webpack 5)
if (isRuntimeModule(mod)) {
return false;
Expand Down
8 changes: 8 additions & 0 deletions test/analyzer.js
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,14 @@ describe('Analyzer', function () {
);
});

it('should handle stats with minimal configuration', async function () {
generateReportFrom('minimal-stats/stats.json');
await expectValidReport({
bundleLabel: 'viewer.js',
statSize: 0
});
});

it.skip("should not filter out modules that we couldn't find during parsing", async function () {
generateReportFrom('with-missing-parsed-module/stats.json');
const chartData = await getChartData();
Expand Down
1 change: 1 addition & 0 deletions test/stats/minimal-stats/expected-chart-data.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
[]
1 change: 1 addition & 0 deletions test/stats/minimal-stats/stats.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"logging":{"./node_modules/babel-loader/lib/index.js babel-loader ./node_modules/babel-loader/lib/index.js??ruleSet[1].rules[0]!./simple-entry.js":{"entries":[],"filteredEntries":3,"debug":false},"./node_modules/babel-loader/lib/index.js babel-loader ./node_modules/babel-loader/lib/index.js??ruleSet[1].rules[0]!./client/viewer.jsx":{"entries":[],"filteredEntries":3,"debug":false},"./node_modules/babel-loader/lib/index.js babel-loader ./node_modules/babel-loader/lib/index.js??ruleSet[1].rules[0]!./client/store.js":{"entries":[],"filteredEntries":3,"debug":false},"./node_modules/babel-loader/lib/index.js babel-loader ./node_modules/babel-loader/lib/index.js??ruleSet[1].rules[0]!./client/components/ModulesTreemap.jsx":{"entries":[],"filteredEntries":3,"debug":false},"./node_modules/babel-loader/lib/index.js babel-loader ./node_modules/babel-loader/lib/index.js??ruleSet[1].rules[0]!./client/utils.js":{"entries":[],"filteredEntries":3,"debug":false},"./node_modules/babel-loader/lib/index.js babel-loader ./node_modules/babel-loader/lib/index.js??ruleSet[1].rules[0]!./client/localStorage.js":{"entries":[],"filteredEntries":3,"debug":false},"./node_modules/babel-loader/lib/index.js babel-loader ./node_modules/babel-loader/lib/index.js??ruleSet[1].rules[0]!./client/components/Tooltip.jsx":{"entries":[],"filteredEntries":3,"debug":false},"./node_modules/babel-loader/lib/index.js babel-loader ./node_modules/babel-loader/lib/index.js??ruleSet[1].rules[0]!./client/components/Treemap.jsx":{"entries":[],"filteredEntries":3,"debug":false},"./node_modules/babel-loader/lib/index.js babel-loader ./node_modules/babel-loader/lib/index.js??ruleSet[1].rules[0]!./client/components/Sidebar.jsx":{"entries":[],"filteredEntries":3,"debug":false},"./node_modules/babel-loader/lib/index.js babel-loader ./node_modules/babel-loader/lib/index.js??ruleSet[1].rules[0]!./client/components/CheckboxList.jsx":{"entries":[],"filteredEntries":3,"debug":false},"./node_modules/babel-loader/lib/index.js babel-loader ./node_modules/babel-loader/lib/index.js??ruleSet[1].rules[0]!./client/components/Checkbox.jsx":{"entries":[],"filteredEntries":3,"debug":false},"./node_modules/babel-loader/lib/index.js babel-loader ./node_modules/babel-loader/lib/index.js??ruleSet[1].rules[0]!./client/components/Dropdown.jsx":{"entries":[],"filteredEntries":3,"debug":false},"./node_modules/babel-loader/lib/index.js babel-loader ./node_modules/babel-loader/lib/index.js??ruleSet[1].rules[0]!./client/components/Switcher.jsx":{"entries":[],"filteredEntries":3,"debug":false},"./node_modules/babel-loader/lib/index.js babel-loader ./node_modules/babel-loader/lib/index.js??ruleSet[1].rules[0]!./client/components/ModulesList.jsx":{"entries":[],"filteredEntries":3,"debug":false},"./node_modules/babel-loader/lib/index.js babel-loader ./node_modules/babel-loader/lib/index.js??ruleSet[1].rules[0]!./client/components/Search.jsx":{"entries":[],"filteredEntries":3,"debug":false},"./node_modules/babel-loader/lib/index.js babel-loader ./node_modules/babel-loader/lib/index.js??ruleSet[1].rules[0]!./client/components/ContextMenu.jsx":{"entries":[],"filteredEntries":3,"debug":false},"webpack.DefinePlugin":{"entries":[],"filteredEntries":137,"debug":false},"./node_modules/babel-loader/lib/index.js babel-loader ./node_modules/babel-loader/lib/index.js??ruleSet[1].rules[0]!./client/components/SwitcherItem.jsx":{"entries":[],"filteredEntries":3,"debug":false},"./node_modules/babel-loader/lib/index.js babel-loader ./node_modules/babel-loader/lib/index.js??ruleSet[1].rules[0]!./client/components/Button.jsx":{"entries":[],"filteredEntries":3,"debug":false},"./node_modules/babel-loader/lib/index.js babel-loader ./node_modules/babel-loader/lib/index.js??ruleSet[1].rules[0]!./client/components/ModuleItem.jsx":{"entries":[],"filteredEntries":3,"debug":false},"./node_modules/babel-loader/lib/index.js babel-loader ./node_modules/babel-loader/lib/index.js??ruleSet[1].rules[0]!./client/components/Icon.jsx":{"entries":[],"filteredEntries":3,"debug":false},"./node_modules/babel-loader/lib/index.js babel-loader ./node_modules/babel-loader/lib/index.js??ruleSet[1].rules[0]!./client/components/CheckboxListItem.jsx":{"entries":[],"filteredEntries":3,"debug":false},"./node_modules/babel-loader/lib/index.js babel-loader ./node_modules/babel-loader/lib/index.js??ruleSet[1].rules[0]!./client/components/ContextMenuItem.jsx":{"entries":[],"filteredEntries":3,"debug":false},"./node_modules/babel-loader/lib/index.js babel-loader ./node_modules/babel-loader/lib/index.js??ruleSet[1].rules[0]!./client/lib/PureComponent.jsx":{"entries":[],"filteredEntries":3,"debug":false},"webpack.Compiler":{"entries":[],"filteredEntries":7,"debug":false},"webpack.Compilation":{"entries":[],"filteredEntries":27,"debug":false},"webpack.FlagDependencyExportsPlugin":{"entries":[],"filteredEntries":4,"debug":false},"webpack.InnerGraphPlugin":{"entries":[],"filteredEntries":1,"debug":false},"webpack.SideEffectsFlagPlugin":{"entries":[],"filteredEntries":1,"debug":false},"webpack.FlagDependencyUsagePlugin":{"entries":[],"filteredEntries":2,"debug":false},"webpack.buildChunkGraph":{"entries":[],"filteredEntries":9,"debug":false},"webpack.SplitChunksPlugin":{"entries":[],"filteredEntries":4,"debug":false},"webpack.ModuleConcatenationPlugin":{"entries":[],"filteredEntries":8,"debug":false},"webpack.FileSystemInfo":{"entries":[],"filteredEntries":11,"debug":false},"webpack.Watching":{"entries":[],"filteredEntries":1,"debug":false}},"version":"5.102.1","time":4182,"assetsByChunkName":{"main":["viewer.js"]},"filteredAssets":1,"filteredModules":172,"filteredErrorDetailsCount":0,"errors":[],"errorsCount":0,"filteredWarningDetailsCount":0,"warnings":[],"warningsCount":0}