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

Add ability to filter to only initial JS by entrypoint #519

Merged
19 changes: 19 additions & 0 deletions client/components/Dropdown.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
.container {
font: var(--main-font);
white-space: nowrap;
}

.select {
border: 1px solid #aaa;
border-radius: 4px;
display: block;
width: 100%;
color: #7f7f7f;
height: 27px;
}

.label {
font-size: 11px;
font-weight: bold;
margin-bottom: 7px;
}
25 changes: 25 additions & 0 deletions client/components/Dropdown.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import PureComponent from '../lib/PureComponent';

import s from './Dropdown.css';
export default class Dropdown extends PureComponent {

render() {
const {label, defaultOption, onSelectionChange, options} = this.props;

return (
<div className={s.container}>
<div className={s.label}>
{label}:
</div>
<div>
<select className={s.select} id={label} name={label} onChange={onSelectionChange}>
<option value={defaultOption}>{defaultOption}</option>
{options.map(option =>
<option key={option} value={option}>{option}</option>
)}
</select>
</div>
</div>
);
}
}
20 changes: 20 additions & 0 deletions client/components/ModulesTreemap.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,16 @@ import s from './ModulesTreemap.css';
import Search from './Search';
import {store} from '../store';
import ModulesList from './ModulesList';
import Dropdown from './Dropdown';

const SIZE_SWITCH_ITEMS = [
{label: 'Stat', prop: 'statSize'},
{label: 'Parsed', prop: 'parsedSize'},
{label: 'Gzipped', prop: 'gzipSize'}
];

const DEFAULT_DROPDOWN_SELECTION = 'Select an entrypoint';

@observer
export default class ModulesTreemap extends Component {
mouseCoords = {
Expand Down Expand Up @@ -78,6 +81,12 @@ export default class ModulesTreemap extends Component {
</div>
}
</div>
<div className={s.sidebarGroup}>
<Dropdown label="Filter to initial chunks"
defaultOption={DEFAULT_DROPDOWN_SELECTION}
options={store.entrypoints}
onSelectionChange={this.handleSelectionChange}/>
</div>
<div className={s.sidebarGroup}>
<Search label="Search modules"
query={store.searchQuery}
Expand Down Expand Up @@ -206,6 +215,17 @@ export default class ModulesTreemap extends Component {
}
}

handleSelectionChange = (event) => {
const selected = event.target.value;

if (selected === DEFAULT_DROPDOWN_SELECTION) {
store.selectedChunks = store.allChunks;
return;
}

store.selectedChunks = store.allChunks.filter(chunk => chunk.isInitialByEntrypoint[selected] ?? false);
}

handleConcatenatedModulesContentToggle = flag => {
store.showConcatenatedModulesContent = flag;
if (flag) {
Expand Down
4 changes: 4 additions & 0 deletions client/store.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ export class Store {
this.selectedChunks = this.allChunks;
}

setEntrypoints(entrypoints) {
this.entrypoints = entrypoints;
}

@computed get hasParsedSizes() {
return this.allChunks.some(isChunkParsed);
}
Expand Down
2 changes: 1 addition & 1 deletion client/viewer.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ try {
window.addEventListener('load', () => {
store.defaultSize = `${window.defaultSizes}Size`;
store.setModules(window.chartData);

store.setEntrypoints(window.entrypoints);
render(
<ModulesTreemap/>,
document.getElementById('app')
Expand Down
18 changes: 17 additions & 1 deletion src/analyzer.js
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,7 @@ function getViewerData(bundleStats, bundleDir, opts) {
return result;
}, {});

const chunkToInitialByEntrypoint = getChunkToInitialByEntrypoint(bundleStats);
return Object.entries(assets).map(([filename, asset]) => ({
label: filename,
isAsset: true,
Expand All @@ -157,7 +158,8 @@ function getViewerData(bundleStats, bundleDir, opts) {
statSize: asset.tree.size || asset.size,
parsedSize: asset.parsedSize,
gzipSize: asset.gzipSize,
groups: _.invokeMap(asset.tree.children, 'toChartData')
groups: _.invokeMap(asset.tree.children, 'toChartData'),
isInitialByEntrypoint: chunkToInitialByEntrypoint[filename] ?? {}
}));
}

Expand Down Expand Up @@ -211,3 +213,17 @@ function createModulesTree(modules) {

return root;
}

function getChunkToInitialByEntrypoint(bundleStats) {
if (bundleStats == null) {
return {};
}
const chunkToEntrypointInititalMap = {};
Object.values(bundleStats.entrypoints || {}).forEach((entrypoint) => {
for (const asset of entrypoint.assets) {
chunkToEntrypointInititalMap[asset.name] = chunkToEntrypointInititalMap[asset.name] ?? {};
chunkToEntrypointInititalMap[asset.name][entrypoint.name] = true;
}
});
return chunkToEntrypointInititalMap;
};
3 changes: 2 additions & 1 deletion src/template.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ function getScript(filename, mode) {
}
}

function renderViewer({title, enableWebSocket, chartData, defaultSizes, mode} = {}) {
function renderViewer({title, enableWebSocket, chartData, entrypoints, defaultSizes, mode} = {}) {
return html`<!DOCTYPE html>
<html>
<head>
Expand All @@ -58,6 +58,7 @@ function renderViewer({title, enableWebSocket, chartData, defaultSizes, mode} =
<div id="app"></div>
<script>
window.chartData = ${escapeJson(chartData)};
window.entrypoints = ${escapeJson(entrypoints)};
window.defaultSizes = ${escapeJson(defaultSizes)};
</script>
</body>
Expand Down
12 changes: 12 additions & 0 deletions src/viewer.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ module.exports = {
startServer,
generateReport,
generateJSONReport,
getEntrypoints,
// deprecated
start: startServer
};
Expand All @@ -45,6 +46,7 @@ async function startServer(bundleStats, opts) {
const analyzerOpts = {logger, excludeAssets};

let chartData = getChartData(analyzerOpts, bundleStats, bundleDir);
const entrypoints = getEntrypoints(bundleStats);

if (!chartData) return;

Expand All @@ -59,6 +61,7 @@ async function startServer(bundleStats, opts) {
mode: 'server',
title: resolveTitle(reportTitle),
chartData,
entrypoints,
defaultSizes,
enableWebSocket: true
});
Expand Down Expand Up @@ -133,13 +136,15 @@ async function generateReport(bundleStats, opts) {
} = opts || {};

const chartData = getChartData({logger, excludeAssets}, bundleStats, bundleDir);
const entrypoints = getEntrypoints(bundleStats);

if (!chartData) return;

const reportHtml = renderViewer({
mode: 'static',
title: resolveTitle(reportTitle),
chartData,
entrypoints,
defaultSizes,
enableWebSocket: false
});
Expand Down Expand Up @@ -187,3 +192,10 @@ function getChartData(analyzerOpts, ...args) {

return chartData;
}

function getEntrypoints(bundleStats) {
if (bundleStats === null || bundleStats === undefined) {
return [];
}
return Object.values(bundleStats.entrypoints || {}).map(entrypoint => entrypoint.name);
}
16 changes: 15 additions & 1 deletion test/analyzer.js
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ describe('Analyzer', function () {
});
});

it('should gracefully process missing chunks', async function () {
it('should gracefully process missing module chunks', async function () {
generateReportFrom('with-missing-module-chunks/stats.json');
const chartData = await getChartData();
const invalidChunk = _.find(chartData, {label: 'invalid-chunk.js'});
Expand Down Expand Up @@ -195,6 +195,20 @@ describe('Analyzer', function () {
await expectValidReport({bundleLabel: 'bundle.js'});
});

it('should map chunks correctly to entrypoints', async function () {
generateReportFrom('with-multiple-entrypoints/stats.json');
const chartData = await getChartData();
expect(chartData).to.containSubset(
require('./stats/with-multiple-entrypoints/expected-chart-data')
);
});

it('should return empty chartData if there are no entrypoints', async function () {
generateReportFrom('with-no-entrypoints/stats.json');
const chartData = await getChartData();
expect(chartData).to.be.empty;
});

describe('options', function () {
describe('title', function () {
it('should take the --title option', async function () {
Expand Down
Loading