Skip to content

Commit

Permalink
Merge pull request #181 from webpack-contrib/loosen-parsing-logic
Browse files Browse the repository at this point in the history
Loosen bundle parsing logic
  • Loading branch information
th0r authored May 21, 2018
2 parents da314a7 + 4944a5b commit 2c05555
Show file tree
Hide file tree
Showing 19 changed files with 1,170 additions and 82 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,11 @@ _Note: Gaps between patch versions are faulty, broken or test releases._

<!-- Add changelog entries for new changes under this section -->

* **Improvement**
* Loosen bundle parsing logic ([#181](https://github.com/webpack-contrib/webpack-bundle-analyzer/pull/181)). Now analyzer will still show parsed sizes even if:
* It can't parse some bundle chunks. Those chunks just won't have content in the report. Fixes issues like [#160](https://github.com/webpack-contrib/webpack-bundle-analyzer/issues/160).
* Some bundle chunks are missing (it couldn't find files to parse). Those chunks just won't be visible in the report for parsed/gzipped sizes.

## 2.12.0

* **New Feature**
Expand Down
55 changes: 35 additions & 20 deletions client/components/ModulesTreemap.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import { h, Component } from 'preact';
import filesize from 'filesize';

import { isChunkParsed } from '../utils';
import Treemap from './Treemap';
import Tooltip from './Tooltip';
import Switcher from './Switcher';
Expand All @@ -20,17 +21,17 @@ export default class ModulesTreemap extends Component {

constructor(props) {
super(props);
this.setData(props.data, true);
this.updateChunks(props.chunks, { initial: true });
}

componentWillReceiveProps(newProps) {
if (newProps.data !== this.props.data) {
this.setData(newProps.data);
if (newProps.chunks !== this.props.chunks) {
this.updateChunks(newProps.chunks);
}
}

render() {
const { data, showTooltip, tooltipContent, activeSizeItem } = this.state;
const { visibleChunks, showTooltip, tooltipContent, activeSizeItem } = this.state;

return (
<div className={s.container}>
Expand All @@ -52,7 +53,7 @@ export default class ModulesTreemap extends Component {
}
</Sidebar>
<Treemap className={s.map}
data={data}
data={visibleChunks}
weightProp={activeSizeItem.prop}
onMouseLeave={this.handleMouseLeaveTreemap}
onGroupHover={this.handleTreemapGroupHover}/>
Expand Down Expand Up @@ -88,12 +89,12 @@ export default class ModulesTreemap extends Component {
};

handleSizeSwitch = sizeSwitchItem => {
this.setState({ activeSizeItem: sizeSwitchItem });
this.updateChunks(this.chunks, { activeSizeItem: sizeSwitchItem });
};

handleVisibleChunksChange = visibleChunkItems => {
this.visibleChunkItems = visibleChunkItems;
this.setState({ data: this.getVisibleChunksData() });
this.updateVisibleChunks();
};

handleMouseLeaveTreemap = () => {
Expand All @@ -115,36 +116,50 @@ export default class ModulesTreemap extends Component {

get totalChunksSize() {
const sizeProp = this.state.activeSizeItem.prop;
return this.props.data.reduce((totalSize, chunk) => totalSize + chunk[sizeProp], 0);
return this.chunks.reduce((totalSize, chunk) =>
totalSize + (chunk[sizeProp] || 0),
0);
}

setData(data, initial) {
const hasParsedSizes = (typeof data[0].parsedSize === 'number');
updateChunks(chunks, { initial, activeSizeItem } = {}) {
this.chunks = chunks;

const hasParsedSizes = chunks.some(isChunkParsed);
this.sizeSwitchItems = hasParsedSizes ? SIZE_SWITCH_ITEMS : SIZE_SWITCH_ITEMS.slice(0, 1);
const activeSizeItemProp = initial ? `${this.props.defaultSizes}Size` : this.state.activeSizeItem.prop;
let activeSizeItem = this.sizeSwitchItems.find(item => item.prop === activeSizeItemProp);
if (!activeSizeItem) activeSizeItem = this.sizeSwitchItems[0];

const chunkItems = [...data]
.sort((chunk1, chunk2) => chunk2[activeSizeItem.prop] - chunk1[activeSizeItem.prop]);
if (!activeSizeItem) {
const activeSizeItemProp = initial ? `${this.props.defaultSizes}Size` : this.state.activeSizeItem.prop;
activeSizeItem = this.sizeSwitchItems.find(item => item.prop === activeSizeItemProp);
if (!activeSizeItem) activeSizeItem = this.sizeSwitchItems[0];
}

let chunkItems = [...chunks];

if (activeSizeItem.prop !== 'statSize') {
chunkItems = chunkItems.filter(isChunkParsed);
}

chunkItems.sort((chunk1, chunk2) => chunk2[activeSizeItem.prop] - chunk1[activeSizeItem.prop]);

if (initial) {
this.visibleChunkItems = chunkItems;
}

this.setState({
data: this.getVisibleChunksData(),
showTooltip: false,
tooltipContent: null,
activeSizeItem,
chunkItems
});
this.updateVisibleChunks();
}

getVisibleChunksData() {
return this.props.data.filter(chunk =>
this.visibleChunkItems.find(item => item.label === chunk.label)
);
updateVisibleChunks() {
this.setState({
visibleChunks: this.chunks.filter(chunk =>
this.visibleChunkItems.find(visibleChunk => chunk.label === visibleChunk.label)
)
});
}

getTooltipContent(module) {
Expand Down
49 changes: 29 additions & 20 deletions client/components/Treemap.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,20 +11,18 @@ export default class Treemap extends Component {
}

componentDidMount() {
this.setWeightProp(this.props.weightProp);
this.updateData(this.props.weightProp);
this.treemap = this.createTreemap();
window.addEventListener('resize', this.treemap.resize, false);
window.addEventListener('resize', this.treemap.resize);
}

componentWillReceiveProps(nextProps) {
if (nextProps.data !== this.props.data) {
this.setWeightProp(nextProps.weightProp, nextProps.data);
this.treemap.set({
dataObject: { groups: nextProps.data }
});
} else if (nextProps.weightProp !== this.props.weightProp) {
this.setWeightProp(nextProps.weightProp);
this.update();
if (
nextProps.data !== this.props.data ||
nextProps.weightProp !== this.props.weightProp
) {
this.updateData(nextProps.weightProp, nextProps.data);
this.treemap.set({ dataObject: this.treemapDataObject });
}
}

Expand All @@ -45,6 +43,10 @@ export default class Treemap extends Component {

saveNode = node => (this.node = node);

get treemapDataObject() {
return { groups: this.data };
}

createTreemap() {
const component = this;
const { props } = this;
Expand All @@ -63,9 +65,7 @@ export default class Treemap extends Component {
fadeDuration: 0,
zoomMouseWheelDuration: 300,
openCloseDuration: 200,
dataObject: {
groups: this.props.data
},
dataObject: this.treemapDataObject,
titleBarDecorator(opts, props, vars) {
vars.titleBarShown = false;
},
Expand Down Expand Up @@ -106,20 +106,29 @@ export default class Treemap extends Component {
this.treemap.update();
}

setWeightProp(prop, data) {
updateData(sizeProp, data) {
data = data || this.props.data;
this.data = getDataForSize(data, sizeProp);
}

data.forEach(setProp);

function setProp(group) {
group.weight = group[prop];
}

function getDataForSize(data, sizeProp) {
return data.reduce((filteredData, group) => {
if (group[sizeProp]) {
if (group.groups) {
group.groups.forEach(setProp);
group = {
...group,
groups: getDataForSize(group.groups, sizeProp)
};
}

group.weight = group[sizeProp];
filteredData.push(group);
}
}

return filteredData;
}, []);
}

function preventDefault(event) {
Expand Down
3 changes: 3 additions & 0 deletions client/utils.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export function isChunkParsed(chunk) {
return (typeof chunk.parsedSize === 'number');
}
2 changes: 1 addition & 1 deletion client/viewer.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ window.addEventListener('load', () => {
let app;
function renderApp(chartData, initialRender) {
app = render(
<ModulesTreemap data={chartData} defaultSizes={window.defaultSizes}/>,
<ModulesTreemap chunks={chartData} defaultSizes={window.defaultSizes}/>,
document.getElementById('app'),
app
);
Expand Down
40 changes: 15 additions & 25 deletions src/analyzer.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,36 +53,34 @@ function getViewerData(bundleStats, bundleDir, opts) {
try {
bundleInfo = parseBundle(assetFile);
} catch (err) {
bundleInfo = null;
}

if (!bundleInfo) {
logger.warn(
`\nCouldn't parse bundle asset "${assetFile}".\n` +
'Analyzer will use module sizes from stats file.\n'
);
parsedModules = null;
bundlesSources = null;
break;
const msg = (err.code === 'ENOENT') ? 'no such file' : err.message;
logger.warn(`Error parsing bundle asset "${assetFile}": ${msg}`);
continue;
}

bundlesSources[statAsset.name] = bundleInfo.src;
_.assign(parsedModules, bundleInfo.modules);
}

if (_.isEmpty(bundlesSources)) {
bundlesSources = null;
parsedModules = null;
logger.warn('\nNo bundles were parsed. Analyzer will show only original module sizes from stats file.\n');
}
}

const modules = getBundleModules(bundleStats);
const assets = _.transform(bundleStats.assets, (result, statAsset) => {
const asset = result[statAsset.name] = _.pick(statAsset, 'size');

if (bundlesSources) {
if (bundlesSources && _.has(bundlesSources, statAsset.name)) {
asset.parsedSize = bundlesSources[statAsset.name].length;
asset.gzipSize = gzipSize.sync(bundlesSources[statAsset.name]);
}

// Picking modules from current bundle script
asset.modules = _(modules)
.filter(statModule => assetHasModule(statAsset, statModule, parsedModules))
.filter(statModule => assetHasModule(statAsset, statModule))
.each(statModule => {
if (parsedModules) {
statModule.parsedSrc = parsedModules[statModule.id];
Expand All @@ -98,7 +96,8 @@ function getViewerData(bundleStats, bundleDir, opts) {
// Not using `asset.size` here provided by Webpack because it can be very confusing when `UglifyJsPlugin` is used.
// In this case all module sizes from stats file will represent unminified module sizes, but `asset.size` will
// be the size of minified bundle.
statSize: asset.tree.size,
// Using `asset.size` only if current asset doesn't contain any modules (resulting size equals 0)
statSize: asset.tree.size || asset.size,
parsedSize: asset.parsedSize,
gzipSize: asset.gzipSize,
groups: _.invokeMap(asset.tree.children, 'toChartData')
Expand All @@ -122,20 +121,11 @@ function getBundleModules(bundleStats) {
.value();
}

function assetHasModule(statAsset, statModule, parsedModules) {
function assetHasModule(statAsset, statModule) {
// Checking if this module is the part of asset chunks
const moduleIsInsideAsset = _.some(statModule.chunks, moduleChunk =>
return _.some(statModule.chunks, moduleChunk =>
_.includes(statAsset.chunks, moduleChunk)
);

return (
moduleIsInsideAsset &&
// ...and that we have found it during parsing
(
!parsedModules ||
_.has(parsedModules, statModule.id)
)
);
}

function createModulesTree(modules) {
Expand Down
14 changes: 9 additions & 5 deletions src/parseUtils.js
Original file line number Diff line number Diff line change
Expand Up @@ -89,15 +89,19 @@ function parseBundle(bundlePath) {
}
);

if (!walkState.locations) {
return null;
let modules;

if (walkState.locations) {
modules = _.mapValues(walkState.locations,
loc => content.slice(loc.start, loc.end)
);
} else {
modules = {};
}

return {
src: content,
modules: _.mapValues(walkState.locations,
loc => content.slice(loc.start, loc.end)
)
modules
};
}

Expand Down
5 changes: 1 addition & 4 deletions src/tree/BaseFolder.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,7 @@ export default class BaseFolder extends Node {

get src() {
if (!_.has(this, '_src')) {
this._src = this.walk((node, src, stop) => {
if (node.src === undefined) return stop(undefined);
return (src += node.src);
}, '', false);
this._src = this.walk((node, src) => (src += node.src || ''), '', false);
}

return this._src;
Expand Down
4 changes: 2 additions & 2 deletions src/tree/Folder.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,12 @@ import { getModulePathParts } from './utils';
export default class Folder extends BaseFolder {

get parsedSize() {
return this.src ? this.src.length : undefined;
return this.src ? this.src.length : 0;
}

get gzipSize() {
if (!_.has(this, '_gzipSize')) {
this._gzipSize = this.src ? gzipSize.sync(this.src) : undefined;
this._gzipSize = this.src ? gzipSize.sync(this.src) : 0;
}

return this._gzipSize;
Expand Down
Loading

0 comments on commit 2c05555

Please sign in to comment.