From a63259394126fe33721b7361fd674e759e425225 Mon Sep 17 00:00:00 2001 From: Sam Le Date: Mon, 15 Aug 2022 15:33:18 +0000 Subject: [PATCH] initial implementation --- client/components/Dropdown.css | 20 +++++++++ client/components/Dropdown.jsx | 64 ++++++++++++++++++++++++++++ client/components/DropdownOption.jsx | 11 +++++ client/components/ModulesTreemap.jsx | 9 ++++ client/store.js | 12 ++++++ client/viewer.jsx | 2 +- src/template.js | 3 +- src/utils.js | 5 +++ src/viewer.js | 27 +++++++++++- test/utils.js | 15 ++++++- test/viewer.js | 36 +++++++++++++++- 11 files changed, 198 insertions(+), 6 deletions(-) create mode 100644 client/components/Dropdown.css create mode 100644 client/components/Dropdown.jsx create mode 100644 client/components/DropdownOption.jsx diff --git a/client/components/Dropdown.css b/client/components/Dropdown.css new file mode 100644 index 00000000..9625f651 --- /dev/null +++ b/client/components/Dropdown.css @@ -0,0 +1,20 @@ +.container { + font: var(--main-font); + white-space: nowrap; +} + +.select { + border: 1px solid #aaa; + border-radius: 4px; + display: block; + flex: 1; + width: 100%; + color: #7f7f7f; + height: 27px; +} + +.label { + font-size: 11px; + font-weight: bold; + margin-bottom: 7px; +} diff --git a/client/components/Dropdown.jsx b/client/components/Dropdown.jsx new file mode 100644 index 00000000..864233ba --- /dev/null +++ b/client/components/Dropdown.jsx @@ -0,0 +1,64 @@ +import PureComponent from '../lib/PureComponent'; +import DropdownOption from './DropdownOption'; + +import s from './Dropdown.css'; +import {store} from '../store'; + +const DEFAULT_DROPDOWN_SELECTION = 'Select an entrypoint'; + +export default class Dropdown extends PureComponent { + + constructor(props) { + super(props); + this.state = { + selectedOption: DEFAULT_DROPDOWN_SELECTION + }; + } + + render() { + const {label, options} = this.props; + + return ( +
+
+ {label}: +
+
+ +
+
+ ); + } + + handleSelection = (event) => { + const selected = event.target.value; + + if (selected === DEFAULT_DROPDOWN_SELECTION) { + store.selectedChunks = store.allChunks; + return; + } + + this.setState({selectedOption: selected}, () => { + store.selectedChunks = []; + for (const chunk of store.allChunks) { + if (store.entrypointsToChunksMap[this.state.selectedOption].has(chunk.label)) { + store.selectedChunks.push(chunk); + } + } + }); + + // this.setState({selectedOption: selected}, () => { + // store.selectedChunks = []; + // for (const chunk of store.allChunks) { + // if (chunk.label in store.entrypointsToChunksMap[this.state.selectedOption]) { + // store.selectedChunks.push(chunk); + // } + // } + // }); + } +} diff --git a/client/components/DropdownOption.jsx b/client/components/DropdownOption.jsx new file mode 100644 index 00000000..6d2586f1 --- /dev/null +++ b/client/components/DropdownOption.jsx @@ -0,0 +1,11 @@ +import {Component} from 'preact'; + +export default class DropdownOption extends Component { + render() { + const {value} = this.props; + + return ( + + ); + } +} diff --git a/client/components/ModulesTreemap.jsx b/client/components/ModulesTreemap.jsx index 1afe3a9b..7dfdac7d 100644 --- a/client/components/ModulesTreemap.jsx +++ b/client/components/ModulesTreemap.jsx @@ -17,6 +17,7 @@ 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'}, @@ -78,6 +79,10 @@ export default class ModulesTreemap extends Component { } +
+ +
{ + // this.entrypointsToChunksMap[entrypoint] = new Set(chunkList); + // }); + + this.entrypointsToChunksMap = Object.entries(_entrypointsToChunksMap).reduce((entrypointsToChunksMap, [entrypoint, chunks]) => { + entrypointsToChunksMap[entrypoint] = new Set(chunks); + return entrypointsToChunksMap; + }, {}); + } + @computed get hasParsedSizes() { return this.allChunks.some(isChunkParsed); } diff --git a/client/viewer.jsx b/client/viewer.jsx index fca77438..4b57d3bd 100644 --- a/client/viewer.jsx +++ b/client/viewer.jsx @@ -20,7 +20,7 @@ try { window.addEventListener('load', () => { store.defaultSize = `${window.defaultSizes}Size`; store.setModules(window.chartData); - + store.setEntrypoints(window.entrypointsToChunksMap); render( , document.getElementById('app') diff --git a/src/template.js b/src/template.js index 4ef02634..c62ef212 100644 --- a/src/template.js +++ b/src/template.js @@ -39,7 +39,7 @@ function getScript(filename, mode) { } } -function renderViewer({title, enableWebSocket, chartData, defaultSizes, mode} = {}) { +function renderViewer({title, enableWebSocket, chartData, entrypointsToChunksMap, defaultSizes, mode} = {}) { return html` @@ -58,6 +58,7 @@ function renderViewer({title, enableWebSocket, chartData, defaultSizes, mode} =
diff --git a/src/utils.js b/src/utils.js index be4e40cf..b1a4a54b 100644 --- a/src/utils.js +++ b/src/utils.js @@ -63,3 +63,8 @@ exports.open = function (uri, logger) { logger.debug(`Opener failed to open "${uri}":\n${err}`); } }; + +exports.isJsFile = function (fileName) { + const JS_REGEX = /\.js$/u; + return JS_REGEX.test(fileName); +}; \ No newline at end of file diff --git a/src/viewer.js b/src/viewer.js index 87304f54..aeceae5a 100644 --- a/src/viewer.js +++ b/src/viewer.js @@ -9,7 +9,7 @@ const {bold} = require('chalk'); const Logger = require('./Logger'); const analyzer = require('./analyzer'); -const {open} = require('./utils'); +const {isJsFile, open} = require('./utils'); const {renderViewer} = require('./template'); const projectRoot = path.resolve(__dirname, '..'); @@ -26,6 +26,7 @@ module.exports = { startServer, generateReport, generateJSONReport, + getEntrypointsToChunksMap, // deprecated start: startServer }; @@ -45,8 +46,9 @@ async function startServer(bundleStats, opts) { const analyzerOpts = {logger, excludeAssets}; let chartData = getChartData(analyzerOpts, bundleStats, bundleDir); + const entrypointsToChunksMap = getEntrypointsToChunksMap(bundleStats); - if (!chartData) return; + if (!chartData || !entrypointsToChunksMap) return; const sirvMiddleware = sirv(`${projectRoot}/public`, { // disables caching and traverse the file system on every request @@ -59,6 +61,7 @@ async function startServer(bundleStats, opts) { mode: 'server', title: resolveTitle(reportTitle), chartData, + entrypointsToChunksMap, defaultSizes, enableWebSocket: true }); @@ -133,6 +136,7 @@ async function generateReport(bundleStats, opts) { } = opts || {}; const chartData = getChartData({logger, excludeAssets}, bundleStats, bundleDir); + const entrypointsToChunksMap = getEntrypointsToChunksMap(bundleStats); if (!chartData) return; @@ -140,6 +144,7 @@ async function generateReport(bundleStats, opts) { mode: 'static', title: resolveTitle(reportTitle), chartData, + entrypointsToChunksMap, defaultSizes, enableWebSocket: false }); @@ -187,3 +192,21 @@ function getChartData(analyzerOpts, ...args) { return chartData; } + +function getEntrypointsToChunksMap(bundleStats) { + if (bundleStats === null || bundleStats === undefined) { + return {}; + } + + return Object.values(bundleStats.entrypoints || {}).reduce((entrypointsToChunksMap, entrypoint) => { + if (!(entrypoint.name in entrypointsToChunksMap)) { + entrypointsToChunksMap[entrypoint.name] = []; + } + entrypoint.assets.forEach(asset => { + if (isJsFile(asset.name)) { + entrypointsToChunksMap[entrypoint.name].push(asset.name); + } + }); + return entrypointsToChunksMap; + }, {}); +} \ No newline at end of file diff --git a/test/utils.js b/test/utils.js index 191788c4..980b8c9c 100644 --- a/test/utils.js +++ b/test/utils.js @@ -1,7 +1,7 @@ const chai = require('chai'); chai.use(require('chai-subset')); const {expect} = chai; -const {createAssetsFilter} = require('../lib/utils'); +const {createAssetsFilter, isJsFile} = require('../lib/utils'); describe('createAssetsFilter', function () { @@ -57,3 +57,16 @@ describe('createAssetsFilter', function () { }); }); + +describe('isJsFile', function () { + it('should recognize .js files', function () { + expect(isJsFile('file.js')).to.equal(true); + expect(isJsFile('file.ts')).to.equal(false); + expect(isJsFile('file.css')).to.equal(false); + }); + + it('should filter out .js files', function () { + const DUMMY_FILES = ['file.js', 'file.css', 'file.ts']; + expect(DUMMY_FILES.filter(isJsFile).join()).to.equal(['file.js'].join()); + }); +}); \ No newline at end of file diff --git a/test/viewer.js b/test/viewer.js index 717b1e1d..46fad919 100644 --- a/test/viewer.js +++ b/test/viewer.js @@ -5,7 +5,7 @@ const crypto = require('crypto'); const net = require('net'); const Logger = require('../lib/Logger'); -const {startServer} = require('../lib/viewer.js'); +const {getEntrypointsToChunksMap, startServer} = require('../lib/viewer.js'); describe('WebSocket server', function () { it('should not crash when an error is emitted on the websocket', function (done) { @@ -69,3 +69,37 @@ describe('WebSocket server', function () { .catch(done); }); }); + +describe('getEntrypointsToChunksMap', function () { + it('should map entrypoints correctly to chuks', function () { + const bundleStats = { + entrypoints: { + 'entrypoint': { + name: 'entrypoint', + assets: [ + { + name: 'chunk.js' + }, + { + name: 'chunk.css' + } + ] + } + } + }; + expect(JSON.stringify(getEntrypointsToChunksMap(bundleStats))).to.equal(JSON.stringify({ + 'entrypoint': ['chunk.js'] + })); + }); + + it('should handle when bundle stats does not have entrypoints', function () { + const bundleStatsWithoutEntryPoints = {}; + expect(JSON.stringify(getEntrypointsToChunksMap(bundleStatsWithoutEntryPoints))).to.equal(JSON.stringify({})); + }); + + it('should handle when entrypoints is empty', function () { + const bundleStatsEmptyEntryPoint = {entrypoints: {}}; + expect(JSON.stringify(getEntrypointsToChunksMap(bundleStatsEmptyEntryPoint))).to.equal(JSON.stringify({})); + }); + +}); \ No newline at end of file