Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Loosen bundle parsing logic #181

Merged
merged 2 commits into from
May 21, 2018
Merged
Show file tree
Hide file tree
Changes from all 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 @@ -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