From 36acd2267b0c3852dedab8fcd4dfaba650652e3c Mon Sep 17 00:00:00 2001 From: Ben Regenspan Date: Mon, 14 Jan 2019 22:30:09 -0500 Subject: [PATCH 01/31] Expose parent asset names --- src/analyzer.js | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/analyzer.js b/src/analyzer.js index 62ac9968..44135aeb 100644 --- a/src/analyzer.js +++ b/src/analyzer.js @@ -89,6 +89,12 @@ function getViewerData(bundleStats, bundleDir, opts) { }); asset.tree = createModulesTree(asset.modules); + + const parentChunks = _.flatten(statAsset.chunks.map(chunkId => bundleStats.chunks[chunkId].parents)); + const parentAssetNames = bundleStats.assets + .filter(asset => asset.chunks.some(chunk => parentChunks.includes(chunk))) + .map(asset => asset.name); + asset.parentAssetNames = parentAssetNames; }, {}); return _.transform(assets, (result, asset, filename) => { @@ -101,7 +107,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'), + parentAssetNames: asset.parentAssetNames }); }, []); } From 8b8d64ea7b333af645f20dcf439dcf3842d24051 Mon Sep 17 00:00:00 2001 From: Ben Regenspan Date: Mon, 14 Jan 2019 23:05:46 -0500 Subject: [PATCH 02/31] Start adding auto-selection of bundle parents --- client/components/ModulesTreemap.jsx | 11 +++++++++++ client/components/Treemap.jsx | 8 ++++++-- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/client/components/ModulesTreemap.jsx b/client/components/ModulesTreemap.jsx index 20af498f..c2aaaafc 100644 --- a/client/components/ModulesTreemap.jsx +++ b/client/components/ModulesTreemap.jsx @@ -97,6 +97,7 @@ export default class ModulesTreemap extends Component { highlightGroups={this.highlightedModules} weightProp={store.activeSize} onMouseLeave={this.handleMouseLeaveTreemap} + onGroupClick={this.handleTreemapGroupClick} onGroupHover={this.handleTreemapGroupHover}/> {tooltipContent} @@ -211,6 +212,16 @@ export default class ModulesTreemap extends Component { this.setState({showTooltip: false}); }; + handleTreemapGroupClick = event => { + const {group} = event; + + if (group && group.parentAssetNames) { + const groupAndParentAssets = [group.label, ...group.parentAssetNames]; + const filteredChunks = store.allChunks.filter(chunk => groupAndParentAssets.includes(chunk.label)); + store.selectedChunks = filteredChunks; + } + }; + handleTreemapGroupHover = event => { const {group} = event; diff --git a/client/components/Treemap.jsx b/client/components/Treemap.jsx index 9ccf6705..d0d86706 100644 --- a/client/components/Treemap.jsx +++ b/client/components/Treemap.jsx @@ -91,8 +91,12 @@ export default class Treemap extends Component { }, onGroupClick(event) { preventDefault(event); - component.zoomOutDisabled = false; - this.zoom(event.group); + if (props.onGroupClick) { + props.onGroupClick.call(component, event); + } else { + component.zoomOutDisabled = false; + this.zoom(event.group); + } }, onGroupDoubleClick: preventDefault, onGroupHover(event) { From c6ed04ed491c2960a52eb08f7134ef70964ff9cd Mon Sep 17 00:00:00 2001 From: Ben Regenspan Date: Tue, 15 Jan 2019 10:12:59 -0500 Subject: [PATCH 03/31] Cleanup, extract function --- src/analyzer.js | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/src/analyzer.js b/src/analyzer.js index 44135aeb..2ad7bdc6 100644 --- a/src/analyzer.js +++ b/src/analyzer.js @@ -6,8 +6,8 @@ const gzipSize = require('gzip-size'); const Logger = require('./Logger'); const Folder = require('./tree/Folder').default; -const {parseBundle} = require('./parseUtils'); -const {createAssetsFilter} = require('./utils'); +const { parseBundle } = require('./parseUtils'); +const { createAssetsFilter } = require('./utils'); const FILENAME_QUERY_REGEXP = /\?.*$/; const FILENAME_EXTENSIONS = /\.(js|mjs)$/i; @@ -90,11 +90,9 @@ function getViewerData(bundleStats, bundleDir, opts) { asset.tree = createModulesTree(asset.modules); - const parentChunks = _.flatten(statAsset.chunks.map(chunkId => bundleStats.chunks[chunkId].parents)); - const parentAssetNames = bundleStats.assets - .filter(asset => asset.chunks.some(chunk => parentChunks.includes(chunk))) - .map(asset => asset.name); - asset.parentAssetNames = parentAssetNames; + asset.parentAssetNames = getParentAssets(statAsset, bundleStats).map( + asset => asset.name + ); }, {}); return _.transform(assets, (result, asset, filename) => { @@ -129,6 +127,16 @@ function getBundleModules(bundleStats) { .value(); } +function getParentAssets(statAsset, bundleStats) { + // Get asset objects corresponding to parent bundles of the specified asset + const parentChunks = _.flatten( + statAsset.chunks.map(chunkId => bundleStats.chunks[chunkId].parents) + ); + return bundleStats.assets.filter(asset => + asset.chunks.some(chunk => parentChunks.includes(chunk)) + ); +} + function assetHasModule(statAsset, statModule) { // Checking if this module is the part of asset chunks return _.some(statModule.chunks, moduleChunk => From 924beec10ec34bd0fa5b54317a00ffeacec05ed9 Mon Sep 17 00:00:00 2001 From: Ben Regenspan Date: Fri, 18 Jan 2019 11:28:21 -0500 Subject: [PATCH 04/31] Use ctrl-click to select parent bundles --- client/components/ModulesTreemap.jsx | 49 ++++++++++++++-------------- client/components/Treemap.jsx | 22 ++++++------- 2 files changed, 35 insertions(+), 36 deletions(-) diff --git a/client/components/ModulesTreemap.jsx b/client/components/ModulesTreemap.jsx index c2aaaafc..8e3da51b 100644 --- a/client/components/ModulesTreemap.jsx +++ b/client/components/ModulesTreemap.jsx @@ -1,10 +1,10 @@ /** @jsx h */ -import {h, Component} from 'preact'; +import { h, Component } from 'preact'; import filesize from 'filesize'; -import {computed} from 'mobx'; -import {observer} from 'mobx-preact'; +import { computed } from 'mobx'; +import { observer } from 'mobx-preact'; -import {isChunkParsed} from '../utils'; +import { isChunkParsed } from '../utils'; import Treemap from './Treemap'; import Tooltip from './Tooltip'; import Switcher from './Switcher'; @@ -14,13 +14,13 @@ import CheckboxList from './CheckboxList'; import s from './ModulesTreemap.css'; import Search from './Search'; -import {store} from '../store'; +import { store } from '../store'; import ModulesList from './ModulesList'; const SIZE_SWITCH_ITEMS = [ - {label: 'Stat', prop: 'statSize'}, - {label: 'Parsed', prop: 'parsedSize'}, - {label: 'Gzipped', prop: 'gzipSize'} + { label: 'Stat', prop: 'statSize' }, + { label: 'Parsed', prop: 'parsedSize' }, + { label: 'Gzipped', prop: 'gzipSize' } ]; @observer @@ -32,7 +32,7 @@ export default class ModulesTreemap extends Component { }; render() { - const {sidebarPinned, showTooltip, tooltipContent} = this.state; + const { sidebarPinned, showTooltip, tooltipContent } = this.state; return (
@@ -44,7 +44,7 @@ export default class ModulesTreemap extends Component { + onSwitch={this.handleSizeSwitch} /> {store.hasConcatenatedModules &&
+ onQueryChange={this.handleQueryChange} />
{this.foundModulesInfo}
{store.isSearching && store.hasFoundModules &&
- {store.foundModulesByChunk.map(({chunk, modules}) => + {store.foundModulesByChunk.map(({ chunk, modules }) =>
this.treemap.zoomToGroup(chunk)}> @@ -75,7 +75,7 @@ export default class ModulesTreemap extends Component { showSize={store.activeSize} highlightedText={store.searchQueryRegexp} isModuleVisible={this.isModuleVisible} - onModuleClick={this.handleFoundModuleClick}/> + onModuleClick={this.handleFoundModuleClick} />
)}
@@ -87,7 +87,7 @@ export default class ModulesTreemap extends Component { items={this.chunkItems} checkedItems={store.selectedChunks} renderLabel={this.renderChunkItemLabel} - onChange={this.handleSelectedChunksChange}/> + onChange={this.handleSelectedChunksChange} />
} @@ -97,8 +97,8 @@ export default class ModulesTreemap extends Component { highlightGroups={this.highlightedModules} weightProp={store.activeSize} onMouseLeave={this.handleMouseLeaveTreemap} - onGroupClick={this.handleTreemapGroupClick} - onGroupHover={this.handleTreemapGroupHover}/> + onGroupHover={this.handleTreemapGroupHover} + onGroupSecondaryClick={this.handleTreemapGroupSecondaryClick} /> {tooltipContent} @@ -141,7 +141,7 @@ export default class ModulesTreemap extends Component { } @computed get chunkItems() { - const {allChunks, activeSize} = store; + const { allChunks, activeSize } = store; let chunkItems = [...allChunks]; if (activeSize !== 'statSize') { @@ -188,7 +188,7 @@ export default class ModulesTreemap extends Component { } handleSidebarPinStateChange = pinned => { - this.setState({sidebarPinned: pinned}); + this.setState({ sidebarPinned: pinned }); setTimeout(() => this.treemap.resize()); } @@ -209,12 +209,11 @@ export default class ModulesTreemap extends Component { }; handleMouseLeaveTreemap = () => { - this.setState({showTooltip: false}); + this.setState({ showTooltip: false }); }; - handleTreemapGroupClick = event => { - const {group} = event; - + handleTreemapGroupSecondaryClick = event => { + const { group } = event; if (group && group.parentAssetNames) { const groupAndParentAssets = [group.label, ...group.parentAssetNames]; const filteredChunks = store.allChunks.filter(chunk => groupAndParentAssets.includes(chunk.label)); @@ -223,7 +222,7 @@ export default class ModulesTreemap extends Component { }; handleTreemapGroupHover = event => { - const {group} = event; + const { group } = event; if (group) { this.setState({ @@ -231,7 +230,7 @@ export default class ModulesTreemap extends Component { tooltipContent: this.getTooltipContent(group) }); } else { - this.setState({showTooltip: false}); + this.setState({ showTooltip: false }); } }; @@ -249,7 +248,7 @@ export default class ModulesTreemap extends Component { return (
{module.label}
-
+
{this.renderModuleSize(module, 'stat')} {!module.inaccurateSizes && this.renderModuleSize(module, 'parsed')} {!module.inaccurateSizes && this.renderModuleSize(module, 'gzip')} diff --git a/client/components/Treemap.jsx b/client/components/Treemap.jsx index d0d86706..eef597a8 100644 --- a/client/components/Treemap.jsx +++ b/client/components/Treemap.jsx @@ -1,5 +1,5 @@ /** @jsx h */ -import {h, Component} from 'preact'; +import { h, Component } from 'preact'; import FoamTree from 'carrotsearch.foamtree'; export default class Treemap extends Component { @@ -40,19 +40,19 @@ export default class Treemap extends Component { render() { return ( -
+
); } saveNodeRef = node => (this.node = node); getTreemapDataObject(data = this.props.data) { - return {groups: data}; + return { groups: data }; } createTreemap() { const component = this; - const {props} = this; + const { props } = this; return new FoamTree({ element: this.node, @@ -76,7 +76,7 @@ export default class Treemap extends Component { vars.titleBarShown = false; }, groupColorDecorator(options, properties, variables) { - const {highlightGroups} = component.props; + const { highlightGroups } = component.props; const module = properties.group; if (highlightGroups && highlightGroups.has(module)) { @@ -91,12 +91,12 @@ export default class Treemap extends Component { }, onGroupClick(event) { preventDefault(event); - if (props.onGroupClick) { - props.onGroupClick.call(component, event); - } else { - component.zoomOutDisabled = false; - this.zoom(event.group); + if (event.ctrlKey && props.onGroupSecondaryClick) { + props.onGroupSecondaryClick.call(component, event); + return; } + component.zoomOutDisabled = false; + this.zoom(event.group); }, onGroupDoubleClick: preventDefault, onGroupHover(event) { @@ -111,7 +111,7 @@ export default class Treemap extends Component { } }, onGroupMouseWheel(event) { - const {scale} = this.get('viewport'); + const { scale } = this.get('viewport'); const isZoomOut = (event.delta < 0); if (isZoomOut) { From 2f93aee1e67c00f43ac10e474aedb9f7d1b8ce93 Mon Sep 17 00:00:00 2001 From: Ben Regenspan Date: Fri, 18 Jan 2019 12:04:56 -0500 Subject: [PATCH 05/31] "bundle" => "chunk" --- src/analyzer.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/analyzer.js b/src/analyzer.js index 2ad7bdc6..567991e9 100644 --- a/src/analyzer.js +++ b/src/analyzer.js @@ -128,7 +128,7 @@ function getBundleModules(bundleStats) { } function getParentAssets(statAsset, bundleStats) { - // Get asset objects corresponding to parent bundles of the specified asset + // Get asset objects corresponding to parent chunks of the specified asset const parentChunks = _.flatten( statAsset.chunks.map(chunkId => bundleStats.chunks[chunkId].parents) ); From 34d0d8c275e10a4bafd1ad61fb7acaa48256fc0b Mon Sep 17 00:00:00 2001 From: Ben Regenspan Date: Mon, 21 Jan 2019 14:57:19 -0500 Subject: [PATCH 06/31] Start adding context menu TODOs: - refactor out shared functionality with Tooltip - styling - test coverage --- client/components/ContextMenu.jsx | 105 +++++++++++++++++++++++++++ client/components/ModulesTreemap.jsx | 17 ++++- client/components/Tooltip.jsx | 1 + client/components/Treemap.jsx | 2 +- 4 files changed, 120 insertions(+), 5 deletions(-) create mode 100644 client/components/ContextMenu.jsx diff --git a/client/components/ContextMenu.jsx b/client/components/ContextMenu.jsx new file mode 100644 index 00000000..136462a0 --- /dev/null +++ b/client/components/ContextMenu.jsx @@ -0,0 +1,105 @@ +/** @jsx h */ +import {h, Component} from 'preact'; +import cls from 'classnames'; +import { store } from '../store'; + +import s from './Tooltip.css'; + +export default class ContextMenu extends Component { + + static marginX = 10; + static marginY = 30; + + constructor(props) { + super(props); + + this.mouseCoords = { + x: 0, + y: 0 + }; + + this.state = { + left: 0, + top: 0 + }; + } + + componentDidMount() { + document.addEventListener('mousemove', this.onMouseMove, false); + } + + shouldComponentUpdate(nextProps) { + return this.props.visible || nextProps.visible; + } + + componentWillUnmount() { + document.removeEventListener('mousemove', this.onMouseMove); + } + + render() { + const {visible} = this.props; + const className = cls({ + [s.container]: true, + [s.hidden]: !visible + }); + + return ( +
    +
  • Show parent chunks
  • +
+ ); + } + + handleClickFilterToParents = () => { + const {chunk} = this.props; + if (chunk && chunk.parentAssetNames) { + const groupAndParentAssets = [chunk.label, ...chunk.parentAssetNames]; + const filteredChunks = store.allChunks.filter(chunk => groupAndParentAssets.includes(chunk.label)); + store.selectedChunks = filteredChunks; + } + } + + saveNode = node => (this.node = node); + + getStyle() { + return { + left: this.state.left, + top: this.state.top + }; + } + + updatePosition() { + if (!this.props.visible) return; + + const pos = { + left: this.mouseCoords.x + ContextMenu.marginX, + top: this.mouseCoords.y + ContextMenu.marginY + }; + + const boundingRect = this.node.getBoundingClientRect(); + + if (pos.left + boundingRect.width > window.innerWidth) { + // Shifting horizontally + pos.left = window.innerWidth - boundingRect.width; + } + + if (pos.top + boundingRect.height > window.innerHeight) { + // Flipping vertically + pos.top = this.mouseCoords.y - ContextMenu.marginY - boundingRect.height; + } + + this.setState(pos); + } + + onMouseMove = event => { + Object.assign(this.mouseCoords, { + x: event.pageX, + y: event.pageY + }); + + if (this.props.visible) { + this.updatePosition(); + } + }; + +} diff --git a/client/components/ModulesTreemap.jsx b/client/components/ModulesTreemap.jsx index 8e3da51b..f7dd182e 100644 --- a/client/components/ModulesTreemap.jsx +++ b/client/components/ModulesTreemap.jsx @@ -11,6 +11,7 @@ import Switcher from './Switcher'; import Sidebar from './Sidebar'; import Checkbox from './Checkbox'; import CheckboxList from './CheckboxList'; +import ContextMenu from './ContextMenu'; import s from './ModulesTreemap.css'; import Search from './Search'; @@ -26,13 +27,14 @@ const SIZE_SWITCH_ITEMS = [ @observer export default class ModulesTreemap extends Component { state = { + selectedChunk: null, sidebarPinned: false, showTooltip: false, tooltipContent: null }; render() { - const { sidebarPinned, showTooltip, tooltipContent } = this.state; + const { selectedChunk, sidebarPinned, showTooltip, tooltipContent } = this.state; return (
@@ -102,6 +104,9 @@ export default class ModulesTreemap extends Component { {tooltipContent} + {selectedChunk && + + }
); } @@ -215,9 +220,13 @@ export default class ModulesTreemap extends Component { handleTreemapGroupSecondaryClick = event => { const { group } = event; if (group && group.parentAssetNames) { - const groupAndParentAssets = [group.label, ...group.parentAssetNames]; - const filteredChunks = store.allChunks.filter(chunk => groupAndParentAssets.includes(chunk.label)); - store.selectedChunks = filteredChunks; + this.setState({ + selectedChunk: group + }); + } else { + this.setState({ + selectedChunk: null + }) } }; diff --git a/client/components/Tooltip.jsx b/client/components/Tooltip.jsx index 9262402f..ad8262f3 100644 --- a/client/components/Tooltip.jsx +++ b/client/components/Tooltip.jsx @@ -47,6 +47,7 @@ export default class Tooltip extends Component { className={className} style={this.getStyle()}> {children} +

Right-click to view options related to this chunk

); } diff --git a/client/components/Treemap.jsx b/client/components/Treemap.jsx index eef597a8..de05e372 100644 --- a/client/components/Treemap.jsx +++ b/client/components/Treemap.jsx @@ -91,7 +91,7 @@ export default class Treemap extends Component { }, onGroupClick(event) { preventDefault(event); - if (event.ctrlKey && props.onGroupSecondaryClick) { + if ((event.ctrlKey || event.secondary) && props.onGroupSecondaryClick) { props.onGroupSecondaryClick.call(component, event); return; } From eadcd0c332f8013a635f5079dd0058ce61ac733a Mon Sep 17 00:00:00 2001 From: Ben Regenspan Date: Mon, 21 Jan 2019 15:06:16 -0500 Subject: [PATCH 07/31] Implement context menu options --- client/components/ContextMenu.jsx | 26 ++++++++++++++++++++++---- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/client/components/ContextMenu.jsx b/client/components/ContextMenu.jsx index 136462a0..522709f8 100644 --- a/client/components/ContextMenu.jsx +++ b/client/components/ContextMenu.jsx @@ -44,16 +44,34 @@ export default class ContextMenu extends Component { }); return ( -
    +
      +
    • Hide chunk
    • Show parent chunks
    • +
    • Hide all other chunks
    ); } + handleClickHideChunk = () => { + const {chunk: selectedChunk} = this.props; + if (selectedChunk && selectedChunk.label) { + const filteredChunks = store.allChunks.filter(chunk => chunk.label !== selectedChunk.label); + store.selectedChunks = filteredChunks; + } + } + + handleClickFilterToChunk = () => { + const {chunk: selectedChunk} = this.props; + if (selectedChunk && selectedChunk.label) { + const filteredChunks = store.allChunks.filter(chunk => chunk.label === selectedChunk.label); + store.selectedChunks = filteredChunks; + } + } + handleClickFilterToParents = () => { - const {chunk} = this.props; - if (chunk && chunk.parentAssetNames) { - const groupAndParentAssets = [chunk.label, ...chunk.parentAssetNames]; + const {chunk: selectedChunk} = this.props; + if (selectedChunk && selectedChunk.parentAssetNames) { + const groupAndParentAssets = [selectedChunk.label, ...selectedChunk.parentAssetNames]; const filteredChunks = store.allChunks.filter(chunk => groupAndParentAssets.includes(chunk.label)); store.selectedChunks = filteredChunks; } From 80e51dcd6cdcb7faec6ae51422b4566fb940419d Mon Sep 17 00:00:00 2001 From: Ben Regenspan Date: Mon, 21 Jan 2019 16:04:38 -0500 Subject: [PATCH 08/31] Basic styling + positioning --- client/components/ContextMenu.css | 28 +++++++++++ client/components/ContextMenu.jsx | 71 +++++++--------------------- client/components/ModulesTreemap.jsx | 15 +++--- 3 files changed, 55 insertions(+), 59 deletions(-) create mode 100644 client/components/ContextMenu.css diff --git a/client/components/ContextMenu.css b/client/components/ContextMenu.css new file mode 100644 index 00000000..38d559aa --- /dev/null +++ b/client/components/ContextMenu.css @@ -0,0 +1,28 @@ +.container { + font: var(--main-font); + position: absolute; + padding: 0; + border-radius: 4px; + background: #fff; + border: 1px solid #aaa; + list-style: none; + opacity: 1; + white-space: nowrap; + visibility: visible; + transition: opacity .2s ease, visibility .2s ease; +} + +.hidden { + opacity: 0; + visibility: hidden; +} + +.item { + cursor: pointer; + margin: 0; + padding: 4px 6px; +} + +.item:hover { + background: #ccc; +} diff --git a/client/components/ContextMenu.jsx b/client/components/ContextMenu.jsx index 522709f8..f794795a 100644 --- a/client/components/ContextMenu.jsx +++ b/client/components/ContextMenu.jsx @@ -1,53 +1,37 @@ /** @jsx h */ import {h, Component} from 'preact'; import cls from 'classnames'; -import { store } from '../store'; +import {store} from '../store'; -import s from './Tooltip.css'; +import s from './ContextMenu.css'; export default class ContextMenu extends Component { - - static marginX = 10; - static marginY = 30; - constructor(props) { super(props); - - this.mouseCoords = { - x: 0, - y: 0 - }; - - this.state = { - left: 0, - top: 0 - }; } componentDidMount() { - document.addEventListener('mousemove', this.onMouseMove, false); + this.boundingRect = this.node.getBoundingClientRect(); } shouldComponentUpdate(nextProps) { return this.props.visible || nextProps.visible; } - componentWillUnmount() { - document.removeEventListener('mousemove', this.onMouseMove); - } - render() { const {visible} = this.props; - const className = cls({ + const containerClassName = cls({ [s.container]: true, [s.hidden]: !visible }); - + const itemClassName = cls({ + [s.item]: true + }); return ( -
      -
    • Hide chunk
    • -
    • Show parent chunks
    • -
    • Hide all other chunks
    • +
        +
      • Hide chunk
      • +
      • Show parent chunks
      • +
      • Hide all other chunks
      ); } @@ -80,22 +64,16 @@ export default class ContextMenu extends Component { saveNode = node => (this.node = node); getStyle() { - return { - left: this.state.left, - top: this.state.top - }; - } + const {boundingRect} = this; + if (!this.props.visible || !boundingRect) return; - updatePosition() { - if (!this.props.visible) return; + const {coords} = this.props; const pos = { - left: this.mouseCoords.x + ContextMenu.marginX, - top: this.mouseCoords.y + ContextMenu.marginY + left: coords.x, + top: coords.y }; - const boundingRect = this.node.getBoundingClientRect(); - if (pos.left + boundingRect.width > window.innerWidth) { // Shifting horizontally pos.left = window.innerWidth - boundingRect.width; @@ -103,21 +81,8 @@ export default class ContextMenu extends Component { if (pos.top + boundingRect.height > window.innerHeight) { // Flipping vertically - pos.top = this.mouseCoords.y - ContextMenu.marginY - boundingRect.height; + pos.top = this.coords.y - boundingRect.height; } - - this.setState(pos); + return pos; } - - onMouseMove = event => { - Object.assign(this.mouseCoords, { - x: event.pageX, - y: event.pageY - }); - - if (this.props.visible) { - this.updatePosition(); - } - }; - } diff --git a/client/components/ModulesTreemap.jsx b/client/components/ModulesTreemap.jsx index f7dd182e..7f33e001 100644 --- a/client/components/ModulesTreemap.jsx +++ b/client/components/ModulesTreemap.jsx @@ -28,13 +28,14 @@ const SIZE_SWITCH_ITEMS = [ export default class ModulesTreemap extends Component { state = { selectedChunk: null, + selectedMouseCoords: {x:0, y:0}, sidebarPinned: false, showTooltip: false, tooltipContent: null }; render() { - const { selectedChunk, sidebarPinned, showTooltip, tooltipContent } = this.state; + const { selectedChunk, selectedMouseCoords, sidebarPinned, showTooltip, tooltipContent } = this.state; return (
      @@ -104,9 +105,7 @@ export default class ModulesTreemap extends Component { {tooltipContent} - {selectedChunk && - - } +
      ); } @@ -218,10 +217,14 @@ export default class ModulesTreemap extends Component { }; handleTreemapGroupSecondaryClick = event => { - const { group } = event; + const { group, x, y } = event; if (group && group.parentAssetNames) { this.setState({ - selectedChunk: group + selectedChunk: group, + selectedMouseCoords: { + x, + y + } }); } else { this.setState({ From 60fb1a1a0d98fbc51f232afb82b4f6cec9f11fb4 Mon Sep 17 00:00:00 2001 From: Ben Regenspan Date: Mon, 21 Jan 2019 16:07:37 -0500 Subject: [PATCH 09/31] Fix "hide chunk" filter action --- client/components/ContextMenu.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/components/ContextMenu.jsx b/client/components/ContextMenu.jsx index f794795a..0d18d62d 100644 --- a/client/components/ContextMenu.jsx +++ b/client/components/ContextMenu.jsx @@ -39,7 +39,7 @@ export default class ContextMenu extends Component { handleClickHideChunk = () => { const {chunk: selectedChunk} = this.props; if (selectedChunk && selectedChunk.label) { - const filteredChunks = store.allChunks.filter(chunk => chunk.label !== selectedChunk.label); + const filteredChunks = store.selectedChunks.filter(chunk => chunk.label !== selectedChunk.label); store.selectedChunks = filteredChunks; } } From 2d9ffadf4fd8d0b0b97c2b01d5d8144aa37e02d7 Mon Sep 17 00:00:00 2001 From: Ben Regenspan Date: Mon, 21 Jan 2019 16:21:22 -0500 Subject: [PATCH 10/31] Hide chunk context menu after use --- client/components/ContextMenu.jsx | 9 +++++++++ client/components/ModulesTreemap.jsx | 27 +++++++++++++++++++++++---- 2 files changed, 32 insertions(+), 4 deletions(-) diff --git a/client/components/ContextMenu.jsx b/client/components/ContextMenu.jsx index 0d18d62d..48539fd5 100644 --- a/client/components/ContextMenu.jsx +++ b/client/components/ContextMenu.jsx @@ -42,6 +42,7 @@ export default class ContextMenu extends Component { const filteredChunks = store.selectedChunks.filter(chunk => chunk.label !== selectedChunk.label); store.selectedChunks = filteredChunks; } + this.hide(); } handleClickFilterToChunk = () => { @@ -50,6 +51,7 @@ export default class ContextMenu extends Component { const filteredChunks = store.allChunks.filter(chunk => chunk.label === selectedChunk.label); store.selectedChunks = filteredChunks; } + this.hide(); } handleClickFilterToParents = () => { @@ -59,6 +61,13 @@ export default class ContextMenu extends Component { const filteredChunks = store.allChunks.filter(chunk => groupAndParentAssets.includes(chunk.label)); store.selectedChunks = filteredChunks; } + this.hide(); + } + + hide() { + if (this.props.onHide) { + this.props.onHide(); + } } saveNode = node => (this.node = node); diff --git a/client/components/ModulesTreemap.jsx b/client/components/ModulesTreemap.jsx index 7f33e001..82611866 100644 --- a/client/components/ModulesTreemap.jsx +++ b/client/components/ModulesTreemap.jsx @@ -30,12 +30,20 @@ export default class ModulesTreemap extends Component { selectedChunk: null, selectedMouseCoords: {x:0, y:0}, sidebarPinned: false, + showChunkContextMenu: false, showTooltip: false, tooltipContent: null }; render() { - const { selectedChunk, selectedMouseCoords, sidebarPinned, showTooltip, tooltipContent } = this.state; + const { + selectedChunk, + selectedMouseCoords, + sidebarPinned, + showChunkContextMenu, + showTooltip, + tooltipContent + } = this.state; return (
      @@ -105,7 +113,10 @@ export default class ModulesTreemap extends Component { {tooltipContent} - +
      ); } @@ -185,6 +196,12 @@ export default class ModulesTreemap extends Component { store.showConcatenatedModulesContent = flag; } + handleChunkContextMenuHide = () => { + this.setState({ + showChunkContextMenu: false + }); + } + handleSidebarToggle = () => { if (this.state.sidebarPinned) { setTimeout(() => this.treemap.resize()); @@ -224,11 +241,13 @@ export default class ModulesTreemap extends Component { selectedMouseCoords: { x, y - } + }, + showChunkContextMenu: true }); } else { this.setState({ - selectedChunk: null + selectedChunk: null, + showChunkContextMenu: false }) } }; From 297a946c85583d6c93015a5010cc1a515f73629e Mon Sep 17 00:00:00 2001 From: Ben Regenspan Date: Mon, 21 Jan 2019 16:25:24 -0500 Subject: [PATCH 11/31] Add "show all chunks" action --- client/components/ContextMenu.jsx | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/client/components/ContextMenu.jsx b/client/components/ContextMenu.jsx index 48539fd5..e3951df7 100644 --- a/client/components/ContextMenu.jsx +++ b/client/components/ContextMenu.jsx @@ -32,6 +32,7 @@ export default class ContextMenu extends Component {
    • Hide chunk
    • Show parent chunks
    • Hide all other chunks
    • +
    • Show all chunks
    ); } @@ -64,6 +65,11 @@ export default class ContextMenu extends Component { this.hide(); } + handleClickShowAllChunks = () => { + store.selectedChunks = store.allChunks; + this.hide(); + } + hide() { if (this.props.onHide) { this.props.onHide(); From defd41c83797e41376cd1e8b1aa970786e5cc063 Mon Sep 17 00:00:00 2001 From: Ben Regenspan Date: Mon, 21 Jan 2019 16:45:32 -0500 Subject: [PATCH 12/31] Only show hint when context menu is available --- client/components/ModulesTreemap.jsx | 8 +++++++- client/components/Tooltip.jsx | 1 - 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/client/components/ModulesTreemap.jsx b/client/components/ModulesTreemap.jsx index 82611866..ad2fe78a 100644 --- a/client/components/ModulesTreemap.jsx +++ b/client/components/ModulesTreemap.jsx @@ -253,7 +253,7 @@ export default class ModulesTreemap extends Component { }; handleTreemapGroupHover = event => { - const { group } = event; + const {group} = event; if (group) { this.setState({ @@ -286,6 +286,12 @@ export default class ModulesTreemap extends Component { {module.path &&
    Path: {module.path}
    } + {module.parentAssetNames && +
    +
    + Right-click to view options related to this chunk +
    + }
); } diff --git a/client/components/Tooltip.jsx b/client/components/Tooltip.jsx index ad8262f3..9262402f 100644 --- a/client/components/Tooltip.jsx +++ b/client/components/Tooltip.jsx @@ -47,7 +47,6 @@ export default class Tooltip extends Component { className={className} style={this.getStyle()}> {children} -

Right-click to view options related to this chunk

); } From c63f9cae4369a8b59f52ef9487d77386caab164e Mon Sep 17 00:00:00 2001 From: Ben Regenspan Date: Mon, 21 Jan 2019 16:47:00 -0500 Subject: [PATCH 13/31] Lint --- client/components/ModulesTreemap.jsx | 46 ++++++++++++++-------------- client/components/Treemap.jsx | 12 ++++---- src/analyzer.js | 4 +-- 3 files changed, 31 insertions(+), 31 deletions(-) diff --git a/client/components/ModulesTreemap.jsx b/client/components/ModulesTreemap.jsx index ad2fe78a..8161796b 100644 --- a/client/components/ModulesTreemap.jsx +++ b/client/components/ModulesTreemap.jsx @@ -1,10 +1,10 @@ /** @jsx h */ -import { h, Component } from 'preact'; +import {h, Component} from 'preact'; import filesize from 'filesize'; -import { computed } from 'mobx'; -import { observer } from 'mobx-preact'; +import {computed} from 'mobx'; +import {observer} from 'mobx-preact'; -import { isChunkParsed } from '../utils'; +import {isChunkParsed} from '../utils'; import Treemap from './Treemap'; import Tooltip from './Tooltip'; import Switcher from './Switcher'; @@ -15,20 +15,20 @@ import ContextMenu from './ContextMenu'; import s from './ModulesTreemap.css'; import Search from './Search'; -import { store } from '../store'; +import {store} from '../store'; import ModulesList from './ModulesList'; const SIZE_SWITCH_ITEMS = [ - { label: 'Stat', prop: 'statSize' }, - { label: 'Parsed', prop: 'parsedSize' }, - { label: 'Gzipped', prop: 'gzipSize' } + {label: 'Stat', prop: 'statSize'}, + {label: 'Parsed', prop: 'parsedSize'}, + {label: 'Gzipped', prop: 'gzipSize'} ]; @observer export default class ModulesTreemap extends Component { state = { selectedChunk: null, - selectedMouseCoords: {x:0, y:0}, + selectedMouseCoords: {x: 0, y: 0}, sidebarPinned: false, showChunkContextMenu: false, showTooltip: false, @@ -55,7 +55,7 @@ export default class ModulesTreemap extends Component { + onSwitch={this.handleSizeSwitch}/> {store.hasConcatenatedModules &&
+ onQueryChange={this.handleQueryChange}/>
{this.foundModulesInfo}
{store.isSearching && store.hasFoundModules &&
- {store.foundModulesByChunk.map(({ chunk, modules }) => + {store.foundModulesByChunk.map(({chunk, modules}) =>
this.treemap.zoomToGroup(chunk)}> @@ -86,7 +86,7 @@ export default class ModulesTreemap extends Component { showSize={store.activeSize} highlightedText={store.searchQueryRegexp} isModuleVisible={this.isModuleVisible} - onModuleClick={this.handleFoundModuleClick} /> + onModuleClick={this.handleFoundModuleClick}/>
)}
@@ -98,7 +98,7 @@ export default class ModulesTreemap extends Component { items={this.chunkItems} checkedItems={store.selectedChunks} renderLabel={this.renderChunkItemLabel} - onChange={this.handleSelectedChunksChange} /> + onChange={this.handleSelectedChunksChange}/>
} @@ -109,14 +109,14 @@ export default class ModulesTreemap extends Component { weightProp={store.activeSize} onMouseLeave={this.handleMouseLeaveTreemap} onGroupHover={this.handleTreemapGroupHover} - onGroupSecondaryClick={this.handleTreemapGroupSecondaryClick} /> + onGroupSecondaryClick={this.handleTreemapGroupSecondaryClick}/> {tooltipContent} + coords={selectedMouseCoords}/>
); } @@ -156,7 +156,7 @@ export default class ModulesTreemap extends Component { } @computed get chunkItems() { - const { allChunks, activeSize } = store; + const {allChunks, activeSize} = store; let chunkItems = [...allChunks]; if (activeSize !== 'statSize') { @@ -209,7 +209,7 @@ export default class ModulesTreemap extends Component { } handleSidebarPinStateChange = pinned => { - this.setState({ sidebarPinned: pinned }); + this.setState({sidebarPinned: pinned}); setTimeout(() => this.treemap.resize()); } @@ -230,11 +230,11 @@ export default class ModulesTreemap extends Component { }; handleMouseLeaveTreemap = () => { - this.setState({ showTooltip: false }); + this.setState({showTooltip: false}); }; handleTreemapGroupSecondaryClick = event => { - const { group, x, y } = event; + const {group, x, y} = event; if (group && group.parentAssetNames) { this.setState({ selectedChunk: group, @@ -248,7 +248,7 @@ export default class ModulesTreemap extends Component { this.setState({ selectedChunk: null, showChunkContextMenu: false - }) + }); } }; @@ -261,7 +261,7 @@ export default class ModulesTreemap extends Component { tooltipContent: this.getTooltipContent(group) }); } else { - this.setState({ showTooltip: false }); + this.setState({showTooltip: false}); } }; @@ -279,7 +279,7 @@ export default class ModulesTreemap extends Component { return (
{module.label}
-
+
{this.renderModuleSize(module, 'stat')} {!module.inaccurateSizes && this.renderModuleSize(module, 'parsed')} {!module.inaccurateSizes && this.renderModuleSize(module, 'gzip')} diff --git a/client/components/Treemap.jsx b/client/components/Treemap.jsx index de05e372..0484da56 100644 --- a/client/components/Treemap.jsx +++ b/client/components/Treemap.jsx @@ -1,5 +1,5 @@ /** @jsx h */ -import { h, Component } from 'preact'; +import {h, Component} from 'preact'; import FoamTree from 'carrotsearch.foamtree'; export default class Treemap extends Component { @@ -40,19 +40,19 @@ export default class Treemap extends Component { render() { return ( -
+
); } saveNodeRef = node => (this.node = node); getTreemapDataObject(data = this.props.data) { - return { groups: data }; + return {groups: data}; } createTreemap() { const component = this; - const { props } = this; + const {props} = this; return new FoamTree({ element: this.node, @@ -76,7 +76,7 @@ export default class Treemap extends Component { vars.titleBarShown = false; }, groupColorDecorator(options, properties, variables) { - const { highlightGroups } = component.props; + const {highlightGroups} = component.props; const module = properties.group; if (highlightGroups && highlightGroups.has(module)) { @@ -111,7 +111,7 @@ export default class Treemap extends Component { } }, onGroupMouseWheel(event) { - const { scale } = this.get('viewport'); + const {scale} = this.get('viewport'); const isZoomOut = (event.delta < 0); if (isZoomOut) { diff --git a/src/analyzer.js b/src/analyzer.js index 567991e9..b9372cbc 100644 --- a/src/analyzer.js +++ b/src/analyzer.js @@ -6,8 +6,8 @@ const gzipSize = require('gzip-size'); const Logger = require('./Logger'); const Folder = require('./tree/Folder').default; -const { parseBundle } = require('./parseUtils'); -const { createAssetsFilter } = require('./utils'); +const {parseBundle} = require('./parseUtils'); +const {createAssetsFilter} = require('./utils'); const FILENAME_QUERY_REGEXP = /\?.*$/; const FILENAME_EXTENSIONS = /\.(js|mjs)$/i; From 0c9372c5db5fc6f17590313f4866704cb89313cd Mon Sep 17 00:00:00 2001 From: Ben Regenspan Date: Mon, 21 Jan 2019 17:11:17 -0500 Subject: [PATCH 14/31] Clearer isAChunk test --- client/components/ModulesTreemap.jsx | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/client/components/ModulesTreemap.jsx b/client/components/ModulesTreemap.jsx index 8161796b..cdd56601 100644 --- a/client/components/ModulesTreemap.jsx +++ b/client/components/ModulesTreemap.jsx @@ -235,7 +235,7 @@ export default class ModulesTreemap extends Component { handleTreemapGroupSecondaryClick = event => { const {group, x, y} = event; - if (group && group.parentAssetNames) { + if (group && this.isAChunk(group)) { this.setState({ selectedChunk: group, selectedMouseCoords: { @@ -267,6 +267,9 @@ export default class ModulesTreemap extends Component { handleFoundModuleClick = module => this.treemap.zoomToGroup(module); + /** Tests whether specified module/group is a top-level group corresponding to a chunk */ + isAChunk = group => !!group.parentAssetNames + isModuleVisible = module => ( this.treemap.isGroupRendered(module) ) @@ -286,7 +289,7 @@ export default class ModulesTreemap extends Component { {module.path &&
Path: {module.path}
} - {module.parentAssetNames && + {this.isAChunk(module) &&

Right-click to view options related to this chunk From 31a156f5e42f0b42e6138302c041c9870e470334 Mon Sep 17 00:00:00 2001 From: Ben Regenspan Date: Wed, 23 Jan 2019 01:29:11 -0500 Subject: [PATCH 15/31] Hide context menu after click outside --- client/components/ContextMenu.jsx | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/client/components/ContextMenu.jsx b/client/components/ContextMenu.jsx index e3951df7..1b66f29d 100644 --- a/client/components/ContextMenu.jsx +++ b/client/components/ContextMenu.jsx @@ -18,6 +18,14 @@ export default class ContextMenu extends Component { return this.props.visible || nextProps.visible; } + componentDidUpdate(prevProps) { + if (this.props.visible && !prevProps.visible) { + document.addEventListener('mousedown', this.handleDocumentMousedown); + } else if (prevProps.visible && !this.props.visible) { + document.removeEventListener('mousedown', this.handleDocumentMousedown); + } + } + render() { const {visible} = this.props; const containerClassName = cls({ @@ -70,6 +78,12 @@ export default class ContextMenu extends Component { this.hide(); } + handleDocumentMousedown = (e) => { + if (!this.node.contains(e.target)) { + this.hide(); + } + } + hide() { if (this.props.onHide) { this.props.onHide(); From fda56e780942819b5829a52638a698b78afe8b84 Mon Sep 17 00:00:00 2001 From: Ben Regenspan Date: Wed, 23 Jan 2019 01:39:17 -0500 Subject: [PATCH 16/31] Styling --- client/components/ContextMenu.css | 4 ++-- client/components/ContextMenu.jsx | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/client/components/ContextMenu.css b/client/components/ContextMenu.css index 38d559aa..1889290c 100644 --- a/client/components/ContextMenu.css +++ b/client/components/ContextMenu.css @@ -20,9 +20,9 @@ .item { cursor: pointer; margin: 0; - padding: 4px 6px; + padding: 8px 14px; } .item:hover { - background: #ccc; + background: #ffefd7; } diff --git a/client/components/ContextMenu.jsx b/client/components/ContextMenu.jsx index 1b66f29d..854a0e86 100644 --- a/client/components/ContextMenu.jsx +++ b/client/components/ContextMenu.jsx @@ -40,6 +40,7 @@ export default class ContextMenu extends Component {
  • Hide chunk
  • Show parent chunks
  • Hide all other chunks
  • +
  • Show all chunks
  • ); From 514561e7c4b94c5e11b1d2ed45b76560a28f2d92 Mon Sep 17 00:00:00 2001 From: Ben Regenspan Date: Sun, 27 Jan 2019 17:54:38 -0500 Subject: [PATCH 17/31] Fix obtaining parent chunks in case of string chunk ID --- src/analyzer.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/analyzer.js b/src/analyzer.js index b9372cbc..3d6a683d 100644 --- a/src/analyzer.js +++ b/src/analyzer.js @@ -129,9 +129,11 @@ function getBundleModules(bundleStats) { function getParentAssets(statAsset, bundleStats) { // Get asset objects corresponding to parent chunks of the specified asset - const parentChunks = _.flatten( - statAsset.chunks.map(chunkId => bundleStats.chunks[chunkId].parents) - ); + const parentChunks = _(statAsset.chunks) + .map(chunkId => _.find(bundleStats.chunks, {id: chunkId})) + .map('parents') + .flatten() + .value(); return bundleStats.assets.filter(asset => asset.chunks.some(chunk => parentChunks.includes(chunk)) ); From 60354e0c46497121ece6efbc923981473128edb7 Mon Sep 17 00:00:00 2001 From: Ben Regenspan Date: Sun, 27 Jan 2019 20:46:54 -0500 Subject: [PATCH 18/31] Add test coverage re: exposing parentAssetNames for chunks --- test/analyzer.js | 15 + test/stats/with-code-splitting.json | 801 ++++++++++++++++++++++++++++ 2 files changed, 816 insertions(+) create mode 100644 test/stats/with-code-splitting.json diff --git a/test/analyzer.js b/test/analyzer.js index d5c0a05e..ce22e48d 100644 --- a/test/analyzer.js +++ b/test/analyzer.js @@ -49,6 +49,21 @@ describe('Analyzer', function () { await expectValidReport({statSize: 136}); }); + it('should expose names of parent chunk assets for code-split chunks', async function () { + // Test using build stats from the code splitting example at https://git.io/fhiTe + generateReportFrom('with-code-splitting.json'); + const chartData = await getChartData(); + expect(chartData[0]).to.containSubset({ + 'parentAssetNames': [ + 'output.js' + ] + }); + await expectValidReport({ + bundleLabel: '0.output.js', + statSize: 13 + }); + }); + it('should use information about concatenated modules generated by webpack 4', async function () { generateReportFrom('with-module-concatenation-info/stats.json'); const chartData = await getChartData(); diff --git a/test/stats/with-code-splitting.json b/test/stats/with-code-splitting.json new file mode 100644 index 00000000..d7a3aff6 --- /dev/null +++ b/test/stats/with-code-splitting.json @@ -0,0 +1,801 @@ +{ + "errors": [], + "warnings": [], + "version": "4.29.0", + "hash": "c4f92a36be6f014c7dc2", + "time": 145, + "builtAt": 1548638493185, + "publicPath": "dist/", + "outputPath": "/webpack/examples/code-splitting-harmony/dist", + "assetsByChunkName": { + "main": "output.js" + }, + "assets": [ + { + "name": "0.output.js", + "size": 275, + "chunks": [ + 0 + ], + "chunkNames": [], + "emitted": true + }, + { + "name": "1.output.js", + "size": 284, + "chunks": [ + 1 + ], + "chunkNames": [], + "emitted": true + }, + { + "name": "3.output.js", + "size": 270, + "chunks": [ + 3 + ], + "chunkNames": [], + "emitted": true + }, + { + "name": "output.js", + "size": 9933, + "chunks": [ + 2 + ], + "chunkNames": [ + "main" + ], + "emitted": true + } + ], + "filteredAssets": 0, + "entrypoints": { + "main": { + "chunks": [ + 2 + ], + "assets": [ + "output.js" + ], + "children": {}, + "childAssets": {} + } + }, + "namedChunkGroups": { + "main": { + "chunks": [ + 2 + ], + "assets": [ + "output.js" + ], + "children": {}, + "childAssets": {} + } + }, + "chunks": [ + { + "id": 0, + "rendered": true, + "initial": false, + "entry": false, + "size": 13, + "names": [], + "files": [ + "0.output.js" + ], + "hash": "1e662b61f1fa5874e1ac", + "siblings": [], + "parents": [ + 2 + ], + "children": [], + "childrenByOrder": {}, + "modules": [ + { + "id": 0, + "identifier": "/webpack/examples/code-splitting-harmony/node_modules/c/1.js", + "name": "./node_modules/c/1.js", + "index": 4, + "index2": 4, + "size": 13, + "cacheable": true, + "built": true, + "optional": true, + "prefetched": false, + "chunks": [ + 0 + ], + "issuer": "/webpack/examples/code-splitting-harmony/node_modules/c lazy /^\\.\\/.*$/ groupOptions: {} namespace object", + "issuerId": 4, + "issuerName": "./node_modules/c lazy ^\\.\\/.*$ namespace object", + "issuerPath": [ + { + "id": 2, + "identifier": "/webpack/examples/code-splitting-harmony/example.js", + "name": "./example.js" + }, + { + "id": 4, + "identifier": "/webpack/examples/code-splitting-harmony/node_modules/c lazy /^\\.\\/.*$/ groupOptions: {} namespace object", + "name": "./node_modules/c lazy ^\\.\\/.*$ namespace object" + } + ], + "failed": false, + "errors": 0, + "warnings": 0, + "assets": [], + "reasons": [ + { + "moduleId": 4, + "moduleIdentifier": "/webpack/examples/code-splitting-harmony/node_modules/c lazy /^\\.\\/.*$/ groupOptions: {} namespace object", + "module": "./node_modules/c lazy ^\\.\\/.*$ namespace object", + "moduleName": "./node_modules/c lazy ^\\.\\/.*$ namespace object", + "type": "context element", + "userRequest": "./1", + "loc": "./1" + }, + { + "moduleId": 4, + "moduleIdentifier": "/webpack/examples/code-splitting-harmony/node_modules/c lazy /^\\.\\/.*$/ groupOptions: {} namespace object", + "module": "./node_modules/c lazy ^\\.\\/.*$ namespace object", + "moduleName": "./node_modules/c lazy ^\\.\\/.*$ namespace object", + "type": "context element", + "userRequest": "./1.js", + "loc": "./1.js" + } + ], + "providedExports": null, + "optimizationBailout": [], + "depth": 2, + "source": "// module c/1" + } + ], + "filteredModules": 0, + "origins": [ + { + "moduleId": 4, + "module": "/webpack/examples/code-splitting-harmony/node_modules/c lazy /^\\.\\/.*$/ groupOptions: {} namespace object", + "moduleIdentifier": "/webpack/examples/code-splitting-harmony/node_modules/c lazy /^\\.\\/.*$/ groupOptions: {} namespace object", + "moduleName": "./node_modules/c lazy ^\\.\\/.*$ namespace object", + "loc": "./1", + "request": "./1", + "reasons": [] + }, + { + "moduleId": 4, + "module": "/webpack/examples/code-splitting-harmony/node_modules/c lazy /^\\.\\/.*$/ groupOptions: {} namespace object", + "moduleIdentifier": "/webpack/examples/code-splitting-harmony/node_modules/c lazy /^\\.\\/.*$/ groupOptions: {} namespace object", + "moduleName": "./node_modules/c lazy ^\\.\\/.*$ namespace object", + "loc": "./1.js", + "request": "./1.js", + "reasons": [] + } + ] + }, + { + "id": 1, + "rendered": true, + "initial": false, + "entry": false, + "size": 13, + "names": [], + "files": [ + "1.output.js" + ], + "hash": "bfcb9bc3cdf7038d8a93", + "siblings": [], + "parents": [ + 2 + ], + "children": [], + "childrenByOrder": {}, + "modules": [ + { + "id": 1, + "identifier": "/webpack/examples/code-splitting-harmony/node_modules/c/2.js", + "name": "./node_modules/c/2.js", + "index": 5, + "index2": 5, + "size": 13, + "cacheable": true, + "built": true, + "optional": true, + "prefetched": false, + "chunks": [ + 1 + ], + "issuer": "/webpack/examples/code-splitting-harmony/node_modules/c lazy /^\\.\\/.*$/ groupOptions: {} namespace object", + "issuerId": 4, + "issuerName": "./node_modules/c lazy ^\\.\\/.*$ namespace object", + "issuerPath": [ + { + "id": 2, + "identifier": "/webpack/examples/code-splitting-harmony/example.js", + "name": "./example.js" + }, + { + "id": 4, + "identifier": "/webpack/examples/code-splitting-harmony/node_modules/c lazy /^\\.\\/.*$/ groupOptions: {} namespace object", + "name": "./node_modules/c lazy ^\\.\\/.*$ namespace object" + } + ], + "failed": false, + "errors": 0, + "warnings": 0, + "assets": [], + "reasons": [ + { + "moduleId": 4, + "moduleIdentifier": "/webpack/examples/code-splitting-harmony/node_modules/c lazy /^\\.\\/.*$/ groupOptions: {} namespace object", + "module": "./node_modules/c lazy ^\\.\\/.*$ namespace object", + "moduleName": "./node_modules/c lazy ^\\.\\/.*$ namespace object", + "type": "context element", + "userRequest": "./2", + "loc": "./2" + }, + { + "moduleId": 4, + "moduleIdentifier": "/webpack/examples/code-splitting-harmony/node_modules/c lazy /^\\.\\/.*$/ groupOptions: {} namespace object", + "module": "./node_modules/c lazy ^\\.\\/.*$ namespace object", + "moduleName": "./node_modules/c lazy ^\\.\\/.*$ namespace object", + "type": "context element", + "userRequest": "./2.js", + "loc": "./2.js" + } + ], + "providedExports": null, + "optimizationBailout": [], + "depth": 2, + "source": "// module c/2" + } + ], + "filteredModules": 0, + "origins": [ + { + "moduleId": 4, + "module": "/webpack/examples/code-splitting-harmony/node_modules/c lazy /^\\.\\/.*$/ groupOptions: {} namespace object", + "moduleIdentifier": "/webpack/examples/code-splitting-harmony/node_modules/c lazy /^\\.\\/.*$/ groupOptions: {} namespace object", + "moduleName": "./node_modules/c lazy ^\\.\\/.*$ namespace object", + "loc": "./2", + "request": "./2", + "reasons": [] + }, + { + "moduleId": 4, + "module": "/webpack/examples/code-splitting-harmony/node_modules/c lazy /^\\.\\/.*$/ groupOptions: {} namespace object", + "moduleIdentifier": "/webpack/examples/code-splitting-harmony/node_modules/c lazy /^\\.\\/.*$/ groupOptions: {} namespace object", + "moduleName": "./node_modules/c lazy ^\\.\\/.*$ namespace object", + "loc": "./2.js", + "request": "./2.js", + "reasons": [] + } + ] + }, + { + "id": 2, + "rendered": true, + "initial": true, + "entry": true, + "size": 414, + "names": [ + "main" + ], + "files": [ + "output.js" + ], + "hash": "87c76c49d5f80884a77d", + "siblings": [], + "parents": [], + "children": [ + 0, + 1, + 3 + ], + "childrenByOrder": {}, + "modules": [ + { + "id": 2, + "identifier": "/webpack/examples/code-splitting-harmony/example.js", + "name": "./example.js", + "index": 0, + "index2": 2, + "size": 243, + "cacheable": true, + "built": true, + "optional": false, + "prefetched": false, + "chunks": [ + 2 + ], + "issuer": null, + "issuerId": null, + "issuerName": null, + "issuerPath": null, + "failed": false, + "errors": 0, + "warnings": 0, + "assets": [], + "reasons": [ + { + "moduleId": null, + "moduleIdentifier": null, + "module": null, + "moduleName": null, + "type": "single entry", + "userRequest": "/webpack/examples/code-splitting-harmony/example.js", + "loc": "main" + } + ], + "providedExports": [], + "optimizationBailout": [], + "depth": 0, + "source": "import a from \"a\";\n\nimport(\"b\").then(function(b) {\n\tconsole.log(\"b loaded\", b);\n})\n\nfunction loadC(name) {\n\treturn import(\"c/\" + name);\n}\n\nPromise.all([loadC(\"1\"), loadC(\"2\")]).then(function(arr) {\n\tconsole.log(\"c/1 and c/2 loaded\", arr);\n});\n" + }, + { + "id": 3, + "identifier": "/webpack/examples/code-splitting-harmony/node_modules/a.js", + "name": "./node_modules/a.js", + "index": 1, + "index2": 0, + "size": 11, + "cacheable": true, + "built": true, + "optional": false, + "prefetched": false, + "chunks": [ + 2 + ], + "issuer": "/webpack/examples/code-splitting-harmony/example.js", + "issuerId": 2, + "issuerName": "./example.js", + "issuerPath": [ + { + "id": 2, + "identifier": "/webpack/examples/code-splitting-harmony/example.js", + "name": "./example.js" + } + ], + "failed": false, + "errors": 0, + "warnings": 0, + "assets": [], + "reasons": [ + { + "moduleId": 2, + "moduleIdentifier": "/webpack/examples/code-splitting-harmony/example.js", + "module": "./example.js", + "moduleName": "./example.js", + "type": "harmony side effect evaluation", + "userRequest": "a", + "loc": "1:0-18" + } + ], + "providedExports": null, + "optimizationBailout": [], + "depth": 1, + "source": "// module a" + }, + { + "id": 4, + "identifier": "/webpack/examples/code-splitting-harmony/node_modules/c lazy /^\\.\\/.*$/ groupOptions: {} namespace object", + "name": "./node_modules/c lazy ^\\.\\/.*$ namespace object", + "index": 2, + "index2": 1, + "size": 160, + "built": true, + "optional": false, + "prefetched": false, + "chunks": [ + 2 + ], + "issuer": "/webpack/examples/code-splitting-harmony/example.js", + "issuerId": 2, + "issuerName": "./example.js", + "issuerPath": [ + { + "id": 2, + "identifier": "/webpack/examples/code-splitting-harmony/example.js", + "name": "./example.js" + } + ], + "failed": false, + "errors": 0, + "warnings": 0, + "assets": [], + "reasons": [ + { + "moduleId": 2, + "moduleIdentifier": "/webpack/examples/code-splitting-harmony/example.js", + "module": "./example.js", + "moduleName": "./example.js", + "type": "import() context lazy", + "userRequest": "c", + "loc": "8:8-27" + } + ], + "providedExports": null, + "optimizationBailout": [], + "depth": 1 + } + ], + "filteredModules": 0, + "origins": [ + { + "module": "", + "moduleIdentifier": "", + "moduleName": "", + "loc": "main", + "request": "/webpack/examples/code-splitting-harmony/example.js", + "reasons": [] + } + ] + }, + { + "id": 3, + "rendered": true, + "initial": false, + "entry": false, + "size": 11, + "names": [], + "files": [ + "3.output.js" + ], + "hash": "9da6621e51370ece9905", + "siblings": [], + "parents": [ + 2 + ], + "children": [], + "childrenByOrder": {}, + "modules": [ + { + "id": 5, + "identifier": "/webpack/examples/code-splitting-harmony/node_modules/b.js", + "name": "./node_modules/b.js", + "index": 3, + "index2": 3, + "size": 11, + "cacheable": true, + "built": true, + "optional": false, + "prefetched": false, + "chunks": [ + 3 + ], + "issuer": "/webpack/examples/code-splitting-harmony/example.js", + "issuerId": 2, + "issuerName": "./example.js", + "issuerPath": [ + { + "id": 2, + "identifier": "/webpack/examples/code-splitting-harmony/example.js", + "name": "./example.js" + } + ], + "failed": false, + "errors": 0, + "warnings": 0, + "assets": [], + "reasons": [ + { + "moduleId": 2, + "moduleIdentifier": "/webpack/examples/code-splitting-harmony/example.js", + "module": "./example.js", + "moduleName": "./example.js", + "type": "import()", + "userRequest": "b", + "loc": "3:0-11" + } + ], + "providedExports": null, + "optimizationBailout": [], + "depth": 1, + "source": "// module b" + } + ], + "filteredModules": 0, + "origins": [ + { + "moduleId": 2, + "module": "/webpack/examples/code-splitting-harmony/example.js", + "moduleIdentifier": "/webpack/examples/code-splitting-harmony/example.js", + "moduleName": "./example.js", + "loc": "3:0-11", + "request": "b", + "reasons": [] + } + ] + } + ], + "modules": [ + { + "id": 0, + "identifier": "/webpack/examples/code-splitting-harmony/node_modules/c/1.js", + "name": "./node_modules/c/1.js", + "index": 4, + "index2": 4, + "size": 13, + "cacheable": true, + "built": true, + "optional": true, + "prefetched": false, + "chunks": [ + 0 + ], + "issuer": "/webpack/examples/code-splitting-harmony/node_modules/c lazy /^\\.\\/.*$/ groupOptions: {} namespace object", + "issuerId": 4, + "issuerName": "./node_modules/c lazy ^\\.\\/.*$ namespace object", + "issuerPath": [ + { + "id": 2, + "identifier": "/webpack/examples/code-splitting-harmony/example.js", + "name": "./example.js" + }, + { + "id": 4, + "identifier": "/webpack/examples/code-splitting-harmony/node_modules/c lazy /^\\.\\/.*$/ groupOptions: {} namespace object", + "name": "./node_modules/c lazy ^\\.\\/.*$ namespace object" + } + ], + "failed": false, + "errors": 0, + "warnings": 0, + "assets": [], + "reasons": [ + { + "moduleId": 4, + "moduleIdentifier": "/webpack/examples/code-splitting-harmony/node_modules/c lazy /^\\.\\/.*$/ groupOptions: {} namespace object", + "module": "./node_modules/c lazy ^\\.\\/.*$ namespace object", + "moduleName": "./node_modules/c lazy ^\\.\\/.*$ namespace object", + "type": "context element", + "userRequest": "./1", + "loc": "./1" + }, + { + "moduleId": 4, + "moduleIdentifier": "/webpack/examples/code-splitting-harmony/node_modules/c lazy /^\\.\\/.*$/ groupOptions: {} namespace object", + "module": "./node_modules/c lazy ^\\.\\/.*$ namespace object", + "moduleName": "./node_modules/c lazy ^\\.\\/.*$ namespace object", + "type": "context element", + "userRequest": "./1.js", + "loc": "./1.js" + } + ], + "providedExports": null, + "optimizationBailout": [], + "depth": 2, + "source": "// module c/1" + }, + { + "id": 1, + "identifier": "/webpack/examples/code-splitting-harmony/node_modules/c/2.js", + "name": "./node_modules/c/2.js", + "index": 5, + "index2": 5, + "size": 13, + "cacheable": true, + "built": true, + "optional": true, + "prefetched": false, + "chunks": [ + 1 + ], + "issuer": "/webpack/examples/code-splitting-harmony/node_modules/c lazy /^\\.\\/.*$/ groupOptions: {} namespace object", + "issuerId": 4, + "issuerName": "./node_modules/c lazy ^\\.\\/.*$ namespace object", + "issuerPath": [ + { + "id": 2, + "identifier": "/webpack/examples/code-splitting-harmony/example.js", + "name": "./example.js" + }, + { + "id": 4, + "identifier": "/webpack/examples/code-splitting-harmony/node_modules/c lazy /^\\.\\/.*$/ groupOptions: {} namespace object", + "name": "./node_modules/c lazy ^\\.\\/.*$ namespace object" + } + ], + "failed": false, + "errors": 0, + "warnings": 0, + "assets": [], + "reasons": [ + { + "moduleId": 4, + "moduleIdentifier": "/webpack/examples/code-splitting-harmony/node_modules/c lazy /^\\.\\/.*$/ groupOptions: {} namespace object", + "module": "./node_modules/c lazy ^\\.\\/.*$ namespace object", + "moduleName": "./node_modules/c lazy ^\\.\\/.*$ namespace object", + "type": "context element", + "userRequest": "./2", + "loc": "./2" + }, + { + "moduleId": 4, + "moduleIdentifier": "/webpack/examples/code-splitting-harmony/node_modules/c lazy /^\\.\\/.*$/ groupOptions: {} namespace object", + "module": "./node_modules/c lazy ^\\.\\/.*$ namespace object", + "moduleName": "./node_modules/c lazy ^\\.\\/.*$ namespace object", + "type": "context element", + "userRequest": "./2.js", + "loc": "./2.js" + } + ], + "providedExports": null, + "optimizationBailout": [], + "depth": 2, + "source": "// module c/2" + }, + { + "id": 2, + "identifier": "/webpack/examples/code-splitting-harmony/example.js", + "name": "./example.js", + "index": 0, + "index2": 2, + "size": 243, + "cacheable": true, + "built": true, + "optional": false, + "prefetched": false, + "chunks": [ + 2 + ], + "issuer": null, + "issuerId": null, + "issuerName": null, + "issuerPath": null, + "failed": false, + "errors": 0, + "warnings": 0, + "assets": [], + "reasons": [ + { + "moduleId": null, + "moduleIdentifier": null, + "module": null, + "moduleName": null, + "type": "single entry", + "userRequest": "/webpack/examples/code-splitting-harmony/example.js", + "loc": "main" + } + ], + "providedExports": [], + "optimizationBailout": [], + "depth": 0, + "source": "import a from \"a\";\n\nimport(\"b\").then(function(b) {\n\tconsole.log(\"b loaded\", b);\n})\n\nfunction loadC(name) {\n\treturn import(\"c/\" + name);\n}\n\nPromise.all([loadC(\"1\"), loadC(\"2\")]).then(function(arr) {\n\tconsole.log(\"c/1 and c/2 loaded\", arr);\n});\n" + }, + { + "id": 3, + "identifier": "/webpack/examples/code-splitting-harmony/node_modules/a.js", + "name": "./node_modules/a.js", + "index": 1, + "index2": 0, + "size": 11, + "cacheable": true, + "built": true, + "optional": false, + "prefetched": false, + "chunks": [ + 2 + ], + "issuer": "/webpack/examples/code-splitting-harmony/example.js", + "issuerId": 2, + "issuerName": "./example.js", + "issuerPath": [ + { + "id": 2, + "identifier": "/webpack/examples/code-splitting-harmony/example.js", + "name": "./example.js" + } + ], + "failed": false, + "errors": 0, + "warnings": 0, + "assets": [], + "reasons": [ + { + "moduleId": 2, + "moduleIdentifier": "/webpack/examples/code-splitting-harmony/example.js", + "module": "./example.js", + "moduleName": "./example.js", + "type": "harmony side effect evaluation", + "userRequest": "a", + "loc": "1:0-18" + } + ], + "providedExports": null, + "optimizationBailout": [], + "depth": 1, + "source": "// module a" + }, + { + "id": 4, + "identifier": "/webpack/examples/code-splitting-harmony/node_modules/c lazy /^\\.\\/.*$/ groupOptions: {} namespace object", + "name": "./node_modules/c lazy ^\\.\\/.*$ namespace object", + "index": 2, + "index2": 1, + "size": 160, + "built": true, + "optional": false, + "prefetched": false, + "chunks": [ + 2 + ], + "issuer": "/webpack/examples/code-splitting-harmony/example.js", + "issuerId": 2, + "issuerName": "./example.js", + "issuerPath": [ + { + "id": 2, + "identifier": "/webpack/examples/code-splitting-harmony/example.js", + "name": "./example.js" + } + ], + "failed": false, + "errors": 0, + "warnings": 0, + "assets": [], + "reasons": [ + { + "moduleId": 2, + "moduleIdentifier": "/webpack/examples/code-splitting-harmony/example.js", + "module": "./example.js", + "moduleName": "./example.js", + "type": "import() context lazy", + "userRequest": "c", + "loc": "8:8-27" + } + ], + "providedExports": null, + "optimizationBailout": [], + "depth": 1 + }, + { + "id": 5, + "identifier": "/webpack/examples/code-splitting-harmony/node_modules/b.js", + "name": "./node_modules/b.js", + "index": 3, + "index2": 3, + "size": 11, + "cacheable": true, + "built": true, + "optional": false, + "prefetched": false, + "chunks": [ + 3 + ], + "issuer": "/webpack/examples/code-splitting-harmony/example.js", + "issuerId": 2, + "issuerName": "./example.js", + "issuerPath": [ + { + "id": 2, + "identifier": "/webpack/examples/code-splitting-harmony/example.js", + "name": "./example.js" + } + ], + "failed": false, + "errors": 0, + "warnings": 0, + "assets": [], + "reasons": [ + { + "moduleId": 2, + "moduleIdentifier": "/webpack/examples/code-splitting-harmony/example.js", + "module": "./example.js", + "moduleName": "./example.js", + "type": "import()", + "userRequest": "b", + "loc": "3:0-11" + } + ], + "providedExports": null, + "optimizationBailout": [], + "depth": 1, + "source": "// module b" + } + ], + "filteredModules": 0, + "children": [] +} From 7e0e87e95377a51c1b6db3d9b244e61e9bb1153d Mon Sep 17 00:00:00 2001 From: Ben Regenspan Date: Sun, 27 Jan 2019 21:22:10 -0500 Subject: [PATCH 19/31] Fix flipping of menu position when on edge of viewport --- client/components/ContextMenu.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/components/ContextMenu.jsx b/client/components/ContextMenu.jsx index 854a0e86..7fda13f3 100644 --- a/client/components/ContextMenu.jsx +++ b/client/components/ContextMenu.jsx @@ -111,7 +111,7 @@ export default class ContextMenu extends Component { if (pos.top + boundingRect.height > window.innerHeight) { // Flipping vertically - pos.top = this.coords.y - boundingRect.height; + pos.top = coords.y - boundingRect.height; } return pos; } From e39741fc3526e97a24e36e233c6e997cb9162322 Mon Sep 17 00:00:00 2001 From: Ben Regenspan Date: Mon, 28 Jan 2019 18:50:58 -0500 Subject: [PATCH 20/31] Prevent treemap mousedown event triggering when closing menu --- client/components/ContextMenu.jsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/client/components/ContextMenu.jsx b/client/components/ContextMenu.jsx index 7fda13f3..5df7aae2 100644 --- a/client/components/ContextMenu.jsx +++ b/client/components/ContextMenu.jsx @@ -20,9 +20,9 @@ export default class ContextMenu extends Component { componentDidUpdate(prevProps) { if (this.props.visible && !prevProps.visible) { - document.addEventListener('mousedown', this.handleDocumentMousedown); + document.addEventListener('mousedown', this.handleDocumentMousedown, true); } else if (prevProps.visible && !this.props.visible) { - document.removeEventListener('mousedown', this.handleDocumentMousedown); + document.removeEventListener('mousedown', this.handleDocumentMousedown, true); } } @@ -81,6 +81,8 @@ export default class ContextMenu extends Component { handleDocumentMousedown = (e) => { if (!this.node.contains(e.target)) { + e.preventDefault(); + e.stopPropagation(); this.hide(); } } From 65c8c03b5c5550751084d8331851fe5e282d9db5 Mon Sep 17 00:00:00 2001 From: Ben Regenspan Date: Mon, 28 Jan 2019 19:36:32 -0500 Subject: [PATCH 21/31] Use elementIsOutside helper --- client/components/ContextMenu.jsx | 3 ++- client/utils.js | 4 ++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/client/components/ContextMenu.jsx b/client/components/ContextMenu.jsx index 5df7aae2..1a8ac0c0 100644 --- a/client/components/ContextMenu.jsx +++ b/client/components/ContextMenu.jsx @@ -2,6 +2,7 @@ import {h, Component} from 'preact'; import cls from 'classnames'; import {store} from '../store'; +import {elementIsOutside} from '../utils'; import s from './ContextMenu.css'; @@ -80,7 +81,7 @@ export default class ContextMenu extends Component { } handleDocumentMousedown = (e) => { - if (!this.node.contains(e.target)) { + if (elementIsOutside(e.target, this.node)) { e.preventDefault(); e.stopPropagation(); this.hide(); diff --git a/client/utils.js b/client/utils.js index ef5290b5..9ef1cbed 100644 --- a/client/utils.js +++ b/client/utils.js @@ -13,3 +13,7 @@ export function walkModules(modules, cb) { } } } + +export function elementIsOutside(elem, container) { + return !(elem === container || container.contains(elem)); +} From 682232fec68b1b9221e65b4a21bdeaa35be4a4f6 Mon Sep 17 00:00:00 2001 From: Ben Regenspan Date: Sat, 6 Apr 2019 18:32:43 -0400 Subject: [PATCH 22/31] Document sidebar and context menu --- README.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/README.md b/README.md index c5910322..ff024fc4 100644 --- a/README.md +++ b/README.md @@ -148,6 +148,23 @@ as Uglify, then this value will reflect the minified size of your code. This is the size of running the parsed bundles/modules through gzip compression. +

    Selecting Which Chunks to Display

    + +When opened, the report displays all of the Webpack chunks for your project. It's possible to filter to a more specific list of chunks by using the sidebar or the chunk context menu. + +### Sidebar + +The Sidebar Menu can be opened by clicking the `>` button at the top left of the report. You can select or deselect chunks to display under the "Show chunks" heading there. + +### Chunk Context Menu + +The Chunk Context Menu can be opened by right-clicking or `Ctrl`-clicking on a specific chunk in the report. It provides the following options: + + * **Hide chunk:** Hides the selected chunk + * **Show parent chunks:** Filters down to show only the chunk and its parent chunks + * **Filter to chunk:** Hides all chunks besides the selected one + * **Show all chunks:** Un-hides any hidden chunks, returning the report to its initial, unfiltered view +

    Troubleshooting

    ### I can't see all the dependencies in a chunk From a8fa407765981c4298db597df294f6ac6d564d93 Mon Sep 17 00:00:00 2001 From: Ben Regenspan Date: Sat, 6 Apr 2019 20:06:14 -0400 Subject: [PATCH 23/31] Fix flickering menu position on-close; clean up shouldRender --- client/components/ContextMenu.jsx | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/client/components/ContextMenu.jsx b/client/components/ContextMenu.jsx index 1a8ac0c0..714f4e3a 100644 --- a/client/components/ContextMenu.jsx +++ b/client/components/ContextMenu.jsx @@ -1,12 +1,13 @@ /** @jsx h */ -import {h, Component} from 'preact'; +import {h} from 'preact'; import cls from 'classnames'; +import PureComponent from '../lib/PureComponent'; import {store} from '../store'; import {elementIsOutside} from '../utils'; import s from './ContextMenu.css'; -export default class ContextMenu extends Component { +export default class ContextMenu extends PureComponent { constructor(props) { super(props); } @@ -15,10 +16,6 @@ export default class ContextMenu extends Component { this.boundingRect = this.node.getBoundingClientRect(); } - shouldComponentUpdate(nextProps) { - return this.props.visible || nextProps.visible; - } - componentDidUpdate(prevProps) { if (this.props.visible && !prevProps.visible) { document.addEventListener('mousedown', this.handleDocumentMousedown, true); @@ -98,7 +95,10 @@ export default class ContextMenu extends Component { getStyle() { const {boundingRect} = this; - if (!this.props.visible || !boundingRect) return; + + // Upon the first render of this component, we don't yet know + // its dimensions, so can't position it yet + if (!boundingRect) return; const {coords} = this.props; From 64fdb246ca59a8cea980535676415944918d08a7 Mon Sep 17 00:00:00 2001 From: Ben Regenspan Date: Sat, 6 Apr 2019 20:26:50 -0400 Subject: [PATCH 24/31] Close any open context menu upon window resize --- client/components/ModulesTreemap.jsx | 13 ++++++++++++- client/components/Treemap.jsx | 5 +++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/client/components/ModulesTreemap.jsx b/client/components/ModulesTreemap.jsx index cdd56601..0e9f12d5 100644 --- a/client/components/ModulesTreemap.jsx +++ b/client/components/ModulesTreemap.jsx @@ -109,7 +109,8 @@ export default class ModulesTreemap extends Component { weightProp={store.activeSize} onMouseLeave={this.handleMouseLeaveTreemap} onGroupHover={this.handleTreemapGroupHover} - onGroupSecondaryClick={this.handleTreemapGroupSecondaryClick}/> + onGroupSecondaryClick={this.handleTreemapGroupSecondaryClick} + onResize={this.handleResize}/> {tooltipContent} @@ -202,6 +203,16 @@ export default class ModulesTreemap extends Component { }); } + handleResize = () => { + // Close any open context menu when the report is resized, + // so it doesn't show in an incorrect position + if (this.state.showChunkContextMenu) { + this.setState({ + showChunkContextMenu: false + }); + } + } + handleSidebarToggle = () => { if (this.state.sidebarPinned) { setTimeout(() => this.treemap.resize()); diff --git a/client/components/Treemap.jsx b/client/components/Treemap.jsx index 0484da56..7f37a027 100644 --- a/client/components/Treemap.jsx +++ b/client/components/Treemap.jsx @@ -149,7 +149,12 @@ export default class Treemap extends Component { } resize = () => { + const {props} = this; this.treemap.resize(); + + if (props.onResize) { + props.onResize(); + } } } From 6718ef9232f6d9d7b0d44c8af01f21c5bda09e99 Mon Sep 17 00:00:00 2001 From: Ben Regenspan Date: Sat, 6 Apr 2019 20:41:24 -0400 Subject: [PATCH 25/31] Clearer check for differentiating assets from modules --- client/components/ModulesTreemap.jsx | 7 ++----- src/analyzer.js | 1 + 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/client/components/ModulesTreemap.jsx b/client/components/ModulesTreemap.jsx index 0e9f12d5..1b2489a2 100644 --- a/client/components/ModulesTreemap.jsx +++ b/client/components/ModulesTreemap.jsx @@ -246,7 +246,7 @@ export default class ModulesTreemap extends Component { handleTreemapGroupSecondaryClick = event => { const {group, x, y} = event; - if (group && this.isAChunk(group)) { + if (group && group.isAsset) { this.setState({ selectedChunk: group, selectedMouseCoords: { @@ -278,9 +278,6 @@ export default class ModulesTreemap extends Component { handleFoundModuleClick = module => this.treemap.zoomToGroup(module); - /** Tests whether specified module/group is a top-level group corresponding to a chunk */ - isAChunk = group => !!group.parentAssetNames - isModuleVisible = module => ( this.treemap.isGroupRendered(module) ) @@ -300,7 +297,7 @@ export default class ModulesTreemap extends Component { {module.path &&
    Path: {module.path}
    } - {this.isAChunk(module) && + {module.isAsset &&

    Right-click to view options related to this chunk diff --git a/src/analyzer.js b/src/analyzer.js index 3d6a683d..085b1765 100644 --- a/src/analyzer.js +++ b/src/analyzer.js @@ -98,6 +98,7 @@ function getViewerData(bundleStats, bundleDir, opts) { return _.transform(assets, (result, asset, filename) => { result.push({ label: filename, + isAsset: true, // 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. From c9bae5b38377bce68613fc03db471a78904e6753 Mon Sep 17 00:00:00 2001 From: Ben Regenspan Date: Sat, 6 Apr 2019 20:45:32 -0400 Subject: [PATCH 26/31] Remove "filter to parent chunks" option for now --- README.md | 1 - client/components/ContextMenu.jsx | 11 - src/analyzer.js | 19 +- test/analyzer.js | 15 - test/stats/with-code-splitting.json | 801 ---------------------------- 5 files changed, 1 insertion(+), 846 deletions(-) delete mode 100644 test/stats/with-code-splitting.json diff --git a/README.md b/README.md index ff024fc4..11bf6de9 100644 --- a/README.md +++ b/README.md @@ -161,7 +161,6 @@ The Sidebar Menu can be opened by clicking the `>` button at the top left of the The Chunk Context Menu can be opened by right-clicking or `Ctrl`-clicking on a specific chunk in the report. It provides the following options: * **Hide chunk:** Hides the selected chunk - * **Show parent chunks:** Filters down to show only the chunk and its parent chunks * **Filter to chunk:** Hides all chunks besides the selected one * **Show all chunks:** Un-hides any hidden chunks, returning the report to its initial, unfiltered view diff --git a/client/components/ContextMenu.jsx b/client/components/ContextMenu.jsx index 714f4e3a..5cd4d1ca 100644 --- a/client/components/ContextMenu.jsx +++ b/client/components/ContextMenu.jsx @@ -36,7 +36,6 @@ export default class ContextMenu extends PureComponent { return (
    • Hide chunk
    • -
    • Show parent chunks
    • Hide all other chunks

    • Show all chunks
    • @@ -62,16 +61,6 @@ export default class ContextMenu extends PureComponent { this.hide(); } - handleClickFilterToParents = () => { - const {chunk: selectedChunk} = this.props; - if (selectedChunk && selectedChunk.parentAssetNames) { - const groupAndParentAssets = [selectedChunk.label, ...selectedChunk.parentAssetNames]; - const filteredChunks = store.allChunks.filter(chunk => groupAndParentAssets.includes(chunk.label)); - store.selectedChunks = filteredChunks; - } - this.hide(); - } - handleClickShowAllChunks = () => { store.selectedChunks = store.allChunks; this.hide(); diff --git a/src/analyzer.js b/src/analyzer.js index 085b1765..1d1317a8 100644 --- a/src/analyzer.js +++ b/src/analyzer.js @@ -89,10 +89,6 @@ function getViewerData(bundleStats, bundleDir, opts) { }); asset.tree = createModulesTree(asset.modules); - - asset.parentAssetNames = getParentAssets(statAsset, bundleStats).map( - asset => asset.name - ); }, {}); return _.transform(assets, (result, asset, filename) => { @@ -106,8 +102,7 @@ function getViewerData(bundleStats, bundleDir, opts) { statSize: asset.tree.size || asset.size, parsedSize: asset.parsedSize, gzipSize: asset.gzipSize, - groups: _.invokeMap(asset.tree.children, 'toChartData'), - parentAssetNames: asset.parentAssetNames + groups: _.invokeMap(asset.tree.children, 'toChartData') }); }, []); } @@ -128,18 +123,6 @@ function getBundleModules(bundleStats) { .value(); } -function getParentAssets(statAsset, bundleStats) { - // Get asset objects corresponding to parent chunks of the specified asset - const parentChunks = _(statAsset.chunks) - .map(chunkId => _.find(bundleStats.chunks, {id: chunkId})) - .map('parents') - .flatten() - .value(); - return bundleStats.assets.filter(asset => - asset.chunks.some(chunk => parentChunks.includes(chunk)) - ); -} - function assetHasModule(statAsset, statModule) { // Checking if this module is the part of asset chunks return _.some(statModule.chunks, moduleChunk => diff --git a/test/analyzer.js b/test/analyzer.js index ce22e48d..d5c0a05e 100644 --- a/test/analyzer.js +++ b/test/analyzer.js @@ -49,21 +49,6 @@ describe('Analyzer', function () { await expectValidReport({statSize: 136}); }); - it('should expose names of parent chunk assets for code-split chunks', async function () { - // Test using build stats from the code splitting example at https://git.io/fhiTe - generateReportFrom('with-code-splitting.json'); - const chartData = await getChartData(); - expect(chartData[0]).to.containSubset({ - 'parentAssetNames': [ - 'output.js' - ] - }); - await expectValidReport({ - bundleLabel: '0.output.js', - statSize: 13 - }); - }); - it('should use information about concatenated modules generated by webpack 4', async function () { generateReportFrom('with-module-concatenation-info/stats.json'); const chartData = await getChartData(); diff --git a/test/stats/with-code-splitting.json b/test/stats/with-code-splitting.json deleted file mode 100644 index d7a3aff6..00000000 --- a/test/stats/with-code-splitting.json +++ /dev/null @@ -1,801 +0,0 @@ -{ - "errors": [], - "warnings": [], - "version": "4.29.0", - "hash": "c4f92a36be6f014c7dc2", - "time": 145, - "builtAt": 1548638493185, - "publicPath": "dist/", - "outputPath": "/webpack/examples/code-splitting-harmony/dist", - "assetsByChunkName": { - "main": "output.js" - }, - "assets": [ - { - "name": "0.output.js", - "size": 275, - "chunks": [ - 0 - ], - "chunkNames": [], - "emitted": true - }, - { - "name": "1.output.js", - "size": 284, - "chunks": [ - 1 - ], - "chunkNames": [], - "emitted": true - }, - { - "name": "3.output.js", - "size": 270, - "chunks": [ - 3 - ], - "chunkNames": [], - "emitted": true - }, - { - "name": "output.js", - "size": 9933, - "chunks": [ - 2 - ], - "chunkNames": [ - "main" - ], - "emitted": true - } - ], - "filteredAssets": 0, - "entrypoints": { - "main": { - "chunks": [ - 2 - ], - "assets": [ - "output.js" - ], - "children": {}, - "childAssets": {} - } - }, - "namedChunkGroups": { - "main": { - "chunks": [ - 2 - ], - "assets": [ - "output.js" - ], - "children": {}, - "childAssets": {} - } - }, - "chunks": [ - { - "id": 0, - "rendered": true, - "initial": false, - "entry": false, - "size": 13, - "names": [], - "files": [ - "0.output.js" - ], - "hash": "1e662b61f1fa5874e1ac", - "siblings": [], - "parents": [ - 2 - ], - "children": [], - "childrenByOrder": {}, - "modules": [ - { - "id": 0, - "identifier": "/webpack/examples/code-splitting-harmony/node_modules/c/1.js", - "name": "./node_modules/c/1.js", - "index": 4, - "index2": 4, - "size": 13, - "cacheable": true, - "built": true, - "optional": true, - "prefetched": false, - "chunks": [ - 0 - ], - "issuer": "/webpack/examples/code-splitting-harmony/node_modules/c lazy /^\\.\\/.*$/ groupOptions: {} namespace object", - "issuerId": 4, - "issuerName": "./node_modules/c lazy ^\\.\\/.*$ namespace object", - "issuerPath": [ - { - "id": 2, - "identifier": "/webpack/examples/code-splitting-harmony/example.js", - "name": "./example.js" - }, - { - "id": 4, - "identifier": "/webpack/examples/code-splitting-harmony/node_modules/c lazy /^\\.\\/.*$/ groupOptions: {} namespace object", - "name": "./node_modules/c lazy ^\\.\\/.*$ namespace object" - } - ], - "failed": false, - "errors": 0, - "warnings": 0, - "assets": [], - "reasons": [ - { - "moduleId": 4, - "moduleIdentifier": "/webpack/examples/code-splitting-harmony/node_modules/c lazy /^\\.\\/.*$/ groupOptions: {} namespace object", - "module": "./node_modules/c lazy ^\\.\\/.*$ namespace object", - "moduleName": "./node_modules/c lazy ^\\.\\/.*$ namespace object", - "type": "context element", - "userRequest": "./1", - "loc": "./1" - }, - { - "moduleId": 4, - "moduleIdentifier": "/webpack/examples/code-splitting-harmony/node_modules/c lazy /^\\.\\/.*$/ groupOptions: {} namespace object", - "module": "./node_modules/c lazy ^\\.\\/.*$ namespace object", - "moduleName": "./node_modules/c lazy ^\\.\\/.*$ namespace object", - "type": "context element", - "userRequest": "./1.js", - "loc": "./1.js" - } - ], - "providedExports": null, - "optimizationBailout": [], - "depth": 2, - "source": "// module c/1" - } - ], - "filteredModules": 0, - "origins": [ - { - "moduleId": 4, - "module": "/webpack/examples/code-splitting-harmony/node_modules/c lazy /^\\.\\/.*$/ groupOptions: {} namespace object", - "moduleIdentifier": "/webpack/examples/code-splitting-harmony/node_modules/c lazy /^\\.\\/.*$/ groupOptions: {} namespace object", - "moduleName": "./node_modules/c lazy ^\\.\\/.*$ namespace object", - "loc": "./1", - "request": "./1", - "reasons": [] - }, - { - "moduleId": 4, - "module": "/webpack/examples/code-splitting-harmony/node_modules/c lazy /^\\.\\/.*$/ groupOptions: {} namespace object", - "moduleIdentifier": "/webpack/examples/code-splitting-harmony/node_modules/c lazy /^\\.\\/.*$/ groupOptions: {} namespace object", - "moduleName": "./node_modules/c lazy ^\\.\\/.*$ namespace object", - "loc": "./1.js", - "request": "./1.js", - "reasons": [] - } - ] - }, - { - "id": 1, - "rendered": true, - "initial": false, - "entry": false, - "size": 13, - "names": [], - "files": [ - "1.output.js" - ], - "hash": "bfcb9bc3cdf7038d8a93", - "siblings": [], - "parents": [ - 2 - ], - "children": [], - "childrenByOrder": {}, - "modules": [ - { - "id": 1, - "identifier": "/webpack/examples/code-splitting-harmony/node_modules/c/2.js", - "name": "./node_modules/c/2.js", - "index": 5, - "index2": 5, - "size": 13, - "cacheable": true, - "built": true, - "optional": true, - "prefetched": false, - "chunks": [ - 1 - ], - "issuer": "/webpack/examples/code-splitting-harmony/node_modules/c lazy /^\\.\\/.*$/ groupOptions: {} namespace object", - "issuerId": 4, - "issuerName": "./node_modules/c lazy ^\\.\\/.*$ namespace object", - "issuerPath": [ - { - "id": 2, - "identifier": "/webpack/examples/code-splitting-harmony/example.js", - "name": "./example.js" - }, - { - "id": 4, - "identifier": "/webpack/examples/code-splitting-harmony/node_modules/c lazy /^\\.\\/.*$/ groupOptions: {} namespace object", - "name": "./node_modules/c lazy ^\\.\\/.*$ namespace object" - } - ], - "failed": false, - "errors": 0, - "warnings": 0, - "assets": [], - "reasons": [ - { - "moduleId": 4, - "moduleIdentifier": "/webpack/examples/code-splitting-harmony/node_modules/c lazy /^\\.\\/.*$/ groupOptions: {} namespace object", - "module": "./node_modules/c lazy ^\\.\\/.*$ namespace object", - "moduleName": "./node_modules/c lazy ^\\.\\/.*$ namespace object", - "type": "context element", - "userRequest": "./2", - "loc": "./2" - }, - { - "moduleId": 4, - "moduleIdentifier": "/webpack/examples/code-splitting-harmony/node_modules/c lazy /^\\.\\/.*$/ groupOptions: {} namespace object", - "module": "./node_modules/c lazy ^\\.\\/.*$ namespace object", - "moduleName": "./node_modules/c lazy ^\\.\\/.*$ namespace object", - "type": "context element", - "userRequest": "./2.js", - "loc": "./2.js" - } - ], - "providedExports": null, - "optimizationBailout": [], - "depth": 2, - "source": "// module c/2" - } - ], - "filteredModules": 0, - "origins": [ - { - "moduleId": 4, - "module": "/webpack/examples/code-splitting-harmony/node_modules/c lazy /^\\.\\/.*$/ groupOptions: {} namespace object", - "moduleIdentifier": "/webpack/examples/code-splitting-harmony/node_modules/c lazy /^\\.\\/.*$/ groupOptions: {} namespace object", - "moduleName": "./node_modules/c lazy ^\\.\\/.*$ namespace object", - "loc": "./2", - "request": "./2", - "reasons": [] - }, - { - "moduleId": 4, - "module": "/webpack/examples/code-splitting-harmony/node_modules/c lazy /^\\.\\/.*$/ groupOptions: {} namespace object", - "moduleIdentifier": "/webpack/examples/code-splitting-harmony/node_modules/c lazy /^\\.\\/.*$/ groupOptions: {} namespace object", - "moduleName": "./node_modules/c lazy ^\\.\\/.*$ namespace object", - "loc": "./2.js", - "request": "./2.js", - "reasons": [] - } - ] - }, - { - "id": 2, - "rendered": true, - "initial": true, - "entry": true, - "size": 414, - "names": [ - "main" - ], - "files": [ - "output.js" - ], - "hash": "87c76c49d5f80884a77d", - "siblings": [], - "parents": [], - "children": [ - 0, - 1, - 3 - ], - "childrenByOrder": {}, - "modules": [ - { - "id": 2, - "identifier": "/webpack/examples/code-splitting-harmony/example.js", - "name": "./example.js", - "index": 0, - "index2": 2, - "size": 243, - "cacheable": true, - "built": true, - "optional": false, - "prefetched": false, - "chunks": [ - 2 - ], - "issuer": null, - "issuerId": null, - "issuerName": null, - "issuerPath": null, - "failed": false, - "errors": 0, - "warnings": 0, - "assets": [], - "reasons": [ - { - "moduleId": null, - "moduleIdentifier": null, - "module": null, - "moduleName": null, - "type": "single entry", - "userRequest": "/webpack/examples/code-splitting-harmony/example.js", - "loc": "main" - } - ], - "providedExports": [], - "optimizationBailout": [], - "depth": 0, - "source": "import a from \"a\";\n\nimport(\"b\").then(function(b) {\n\tconsole.log(\"b loaded\", b);\n})\n\nfunction loadC(name) {\n\treturn import(\"c/\" + name);\n}\n\nPromise.all([loadC(\"1\"), loadC(\"2\")]).then(function(arr) {\n\tconsole.log(\"c/1 and c/2 loaded\", arr);\n});\n" - }, - { - "id": 3, - "identifier": "/webpack/examples/code-splitting-harmony/node_modules/a.js", - "name": "./node_modules/a.js", - "index": 1, - "index2": 0, - "size": 11, - "cacheable": true, - "built": true, - "optional": false, - "prefetched": false, - "chunks": [ - 2 - ], - "issuer": "/webpack/examples/code-splitting-harmony/example.js", - "issuerId": 2, - "issuerName": "./example.js", - "issuerPath": [ - { - "id": 2, - "identifier": "/webpack/examples/code-splitting-harmony/example.js", - "name": "./example.js" - } - ], - "failed": false, - "errors": 0, - "warnings": 0, - "assets": [], - "reasons": [ - { - "moduleId": 2, - "moduleIdentifier": "/webpack/examples/code-splitting-harmony/example.js", - "module": "./example.js", - "moduleName": "./example.js", - "type": "harmony side effect evaluation", - "userRequest": "a", - "loc": "1:0-18" - } - ], - "providedExports": null, - "optimizationBailout": [], - "depth": 1, - "source": "// module a" - }, - { - "id": 4, - "identifier": "/webpack/examples/code-splitting-harmony/node_modules/c lazy /^\\.\\/.*$/ groupOptions: {} namespace object", - "name": "./node_modules/c lazy ^\\.\\/.*$ namespace object", - "index": 2, - "index2": 1, - "size": 160, - "built": true, - "optional": false, - "prefetched": false, - "chunks": [ - 2 - ], - "issuer": "/webpack/examples/code-splitting-harmony/example.js", - "issuerId": 2, - "issuerName": "./example.js", - "issuerPath": [ - { - "id": 2, - "identifier": "/webpack/examples/code-splitting-harmony/example.js", - "name": "./example.js" - } - ], - "failed": false, - "errors": 0, - "warnings": 0, - "assets": [], - "reasons": [ - { - "moduleId": 2, - "moduleIdentifier": "/webpack/examples/code-splitting-harmony/example.js", - "module": "./example.js", - "moduleName": "./example.js", - "type": "import() context lazy", - "userRequest": "c", - "loc": "8:8-27" - } - ], - "providedExports": null, - "optimizationBailout": [], - "depth": 1 - } - ], - "filteredModules": 0, - "origins": [ - { - "module": "", - "moduleIdentifier": "", - "moduleName": "", - "loc": "main", - "request": "/webpack/examples/code-splitting-harmony/example.js", - "reasons": [] - } - ] - }, - { - "id": 3, - "rendered": true, - "initial": false, - "entry": false, - "size": 11, - "names": [], - "files": [ - "3.output.js" - ], - "hash": "9da6621e51370ece9905", - "siblings": [], - "parents": [ - 2 - ], - "children": [], - "childrenByOrder": {}, - "modules": [ - { - "id": 5, - "identifier": "/webpack/examples/code-splitting-harmony/node_modules/b.js", - "name": "./node_modules/b.js", - "index": 3, - "index2": 3, - "size": 11, - "cacheable": true, - "built": true, - "optional": false, - "prefetched": false, - "chunks": [ - 3 - ], - "issuer": "/webpack/examples/code-splitting-harmony/example.js", - "issuerId": 2, - "issuerName": "./example.js", - "issuerPath": [ - { - "id": 2, - "identifier": "/webpack/examples/code-splitting-harmony/example.js", - "name": "./example.js" - } - ], - "failed": false, - "errors": 0, - "warnings": 0, - "assets": [], - "reasons": [ - { - "moduleId": 2, - "moduleIdentifier": "/webpack/examples/code-splitting-harmony/example.js", - "module": "./example.js", - "moduleName": "./example.js", - "type": "import()", - "userRequest": "b", - "loc": "3:0-11" - } - ], - "providedExports": null, - "optimizationBailout": [], - "depth": 1, - "source": "// module b" - } - ], - "filteredModules": 0, - "origins": [ - { - "moduleId": 2, - "module": "/webpack/examples/code-splitting-harmony/example.js", - "moduleIdentifier": "/webpack/examples/code-splitting-harmony/example.js", - "moduleName": "./example.js", - "loc": "3:0-11", - "request": "b", - "reasons": [] - } - ] - } - ], - "modules": [ - { - "id": 0, - "identifier": "/webpack/examples/code-splitting-harmony/node_modules/c/1.js", - "name": "./node_modules/c/1.js", - "index": 4, - "index2": 4, - "size": 13, - "cacheable": true, - "built": true, - "optional": true, - "prefetched": false, - "chunks": [ - 0 - ], - "issuer": "/webpack/examples/code-splitting-harmony/node_modules/c lazy /^\\.\\/.*$/ groupOptions: {} namespace object", - "issuerId": 4, - "issuerName": "./node_modules/c lazy ^\\.\\/.*$ namespace object", - "issuerPath": [ - { - "id": 2, - "identifier": "/webpack/examples/code-splitting-harmony/example.js", - "name": "./example.js" - }, - { - "id": 4, - "identifier": "/webpack/examples/code-splitting-harmony/node_modules/c lazy /^\\.\\/.*$/ groupOptions: {} namespace object", - "name": "./node_modules/c lazy ^\\.\\/.*$ namespace object" - } - ], - "failed": false, - "errors": 0, - "warnings": 0, - "assets": [], - "reasons": [ - { - "moduleId": 4, - "moduleIdentifier": "/webpack/examples/code-splitting-harmony/node_modules/c lazy /^\\.\\/.*$/ groupOptions: {} namespace object", - "module": "./node_modules/c lazy ^\\.\\/.*$ namespace object", - "moduleName": "./node_modules/c lazy ^\\.\\/.*$ namespace object", - "type": "context element", - "userRequest": "./1", - "loc": "./1" - }, - { - "moduleId": 4, - "moduleIdentifier": "/webpack/examples/code-splitting-harmony/node_modules/c lazy /^\\.\\/.*$/ groupOptions: {} namespace object", - "module": "./node_modules/c lazy ^\\.\\/.*$ namespace object", - "moduleName": "./node_modules/c lazy ^\\.\\/.*$ namespace object", - "type": "context element", - "userRequest": "./1.js", - "loc": "./1.js" - } - ], - "providedExports": null, - "optimizationBailout": [], - "depth": 2, - "source": "// module c/1" - }, - { - "id": 1, - "identifier": "/webpack/examples/code-splitting-harmony/node_modules/c/2.js", - "name": "./node_modules/c/2.js", - "index": 5, - "index2": 5, - "size": 13, - "cacheable": true, - "built": true, - "optional": true, - "prefetched": false, - "chunks": [ - 1 - ], - "issuer": "/webpack/examples/code-splitting-harmony/node_modules/c lazy /^\\.\\/.*$/ groupOptions: {} namespace object", - "issuerId": 4, - "issuerName": "./node_modules/c lazy ^\\.\\/.*$ namespace object", - "issuerPath": [ - { - "id": 2, - "identifier": "/webpack/examples/code-splitting-harmony/example.js", - "name": "./example.js" - }, - { - "id": 4, - "identifier": "/webpack/examples/code-splitting-harmony/node_modules/c lazy /^\\.\\/.*$/ groupOptions: {} namespace object", - "name": "./node_modules/c lazy ^\\.\\/.*$ namespace object" - } - ], - "failed": false, - "errors": 0, - "warnings": 0, - "assets": [], - "reasons": [ - { - "moduleId": 4, - "moduleIdentifier": "/webpack/examples/code-splitting-harmony/node_modules/c lazy /^\\.\\/.*$/ groupOptions: {} namespace object", - "module": "./node_modules/c lazy ^\\.\\/.*$ namespace object", - "moduleName": "./node_modules/c lazy ^\\.\\/.*$ namespace object", - "type": "context element", - "userRequest": "./2", - "loc": "./2" - }, - { - "moduleId": 4, - "moduleIdentifier": "/webpack/examples/code-splitting-harmony/node_modules/c lazy /^\\.\\/.*$/ groupOptions: {} namespace object", - "module": "./node_modules/c lazy ^\\.\\/.*$ namespace object", - "moduleName": "./node_modules/c lazy ^\\.\\/.*$ namespace object", - "type": "context element", - "userRequest": "./2.js", - "loc": "./2.js" - } - ], - "providedExports": null, - "optimizationBailout": [], - "depth": 2, - "source": "// module c/2" - }, - { - "id": 2, - "identifier": "/webpack/examples/code-splitting-harmony/example.js", - "name": "./example.js", - "index": 0, - "index2": 2, - "size": 243, - "cacheable": true, - "built": true, - "optional": false, - "prefetched": false, - "chunks": [ - 2 - ], - "issuer": null, - "issuerId": null, - "issuerName": null, - "issuerPath": null, - "failed": false, - "errors": 0, - "warnings": 0, - "assets": [], - "reasons": [ - { - "moduleId": null, - "moduleIdentifier": null, - "module": null, - "moduleName": null, - "type": "single entry", - "userRequest": "/webpack/examples/code-splitting-harmony/example.js", - "loc": "main" - } - ], - "providedExports": [], - "optimizationBailout": [], - "depth": 0, - "source": "import a from \"a\";\n\nimport(\"b\").then(function(b) {\n\tconsole.log(\"b loaded\", b);\n})\n\nfunction loadC(name) {\n\treturn import(\"c/\" + name);\n}\n\nPromise.all([loadC(\"1\"), loadC(\"2\")]).then(function(arr) {\n\tconsole.log(\"c/1 and c/2 loaded\", arr);\n});\n" - }, - { - "id": 3, - "identifier": "/webpack/examples/code-splitting-harmony/node_modules/a.js", - "name": "./node_modules/a.js", - "index": 1, - "index2": 0, - "size": 11, - "cacheable": true, - "built": true, - "optional": false, - "prefetched": false, - "chunks": [ - 2 - ], - "issuer": "/webpack/examples/code-splitting-harmony/example.js", - "issuerId": 2, - "issuerName": "./example.js", - "issuerPath": [ - { - "id": 2, - "identifier": "/webpack/examples/code-splitting-harmony/example.js", - "name": "./example.js" - } - ], - "failed": false, - "errors": 0, - "warnings": 0, - "assets": [], - "reasons": [ - { - "moduleId": 2, - "moduleIdentifier": "/webpack/examples/code-splitting-harmony/example.js", - "module": "./example.js", - "moduleName": "./example.js", - "type": "harmony side effect evaluation", - "userRequest": "a", - "loc": "1:0-18" - } - ], - "providedExports": null, - "optimizationBailout": [], - "depth": 1, - "source": "// module a" - }, - { - "id": 4, - "identifier": "/webpack/examples/code-splitting-harmony/node_modules/c lazy /^\\.\\/.*$/ groupOptions: {} namespace object", - "name": "./node_modules/c lazy ^\\.\\/.*$ namespace object", - "index": 2, - "index2": 1, - "size": 160, - "built": true, - "optional": false, - "prefetched": false, - "chunks": [ - 2 - ], - "issuer": "/webpack/examples/code-splitting-harmony/example.js", - "issuerId": 2, - "issuerName": "./example.js", - "issuerPath": [ - { - "id": 2, - "identifier": "/webpack/examples/code-splitting-harmony/example.js", - "name": "./example.js" - } - ], - "failed": false, - "errors": 0, - "warnings": 0, - "assets": [], - "reasons": [ - { - "moduleId": 2, - "moduleIdentifier": "/webpack/examples/code-splitting-harmony/example.js", - "module": "./example.js", - "moduleName": "./example.js", - "type": "import() context lazy", - "userRequest": "c", - "loc": "8:8-27" - } - ], - "providedExports": null, - "optimizationBailout": [], - "depth": 1 - }, - { - "id": 5, - "identifier": "/webpack/examples/code-splitting-harmony/node_modules/b.js", - "name": "./node_modules/b.js", - "index": 3, - "index2": 3, - "size": 11, - "cacheable": true, - "built": true, - "optional": false, - "prefetched": false, - "chunks": [ - 3 - ], - "issuer": "/webpack/examples/code-splitting-harmony/example.js", - "issuerId": 2, - "issuerName": "./example.js", - "issuerPath": [ - { - "id": 2, - "identifier": "/webpack/examples/code-splitting-harmony/example.js", - "name": "./example.js" - } - ], - "failed": false, - "errors": 0, - "warnings": 0, - "assets": [], - "reasons": [ - { - "moduleId": 2, - "moduleIdentifier": "/webpack/examples/code-splitting-harmony/example.js", - "module": "./example.js", - "moduleName": "./example.js", - "type": "import()", - "userRequest": "b", - "loc": "3:0-11" - } - ], - "providedExports": null, - "optimizationBailout": [], - "depth": 1, - "source": "// module b" - } - ], - "filteredModules": 0, - "children": [] -} From 68b7cfb3d4a04d7b20a3d853b2d975da7a0139f3 Mon Sep 17 00:00:00 2001 From: Ben Regenspan Date: Sun, 7 Apr 2019 14:29:03 -0400 Subject: [PATCH 27/31] Disable inapplicable context menu options --- client/components/ContextMenu.css | 10 ---------- client/components/ContextMenu.jsx | 24 ++++++++++++++---------- client/components/ContextMenuItem.css | 24 ++++++++++++++++++++++++ client/components/ContextMenuItem.jsx | 17 +++++++++++++++++ 4 files changed, 55 insertions(+), 20 deletions(-) create mode 100644 client/components/ContextMenuItem.css create mode 100644 client/components/ContextMenuItem.jsx diff --git a/client/components/ContextMenu.css b/client/components/ContextMenu.css index 1889290c..b8441dfe 100644 --- a/client/components/ContextMenu.css +++ b/client/components/ContextMenu.css @@ -16,13 +16,3 @@ opacity: 0; visibility: hidden; } - -.item { - cursor: pointer; - margin: 0; - padding: 8px 14px; -} - -.item:hover { - background: #ffefd7; -} diff --git a/client/components/ContextMenu.jsx b/client/components/ContextMenu.jsx index 5cd4d1ca..35c78969 100644 --- a/client/components/ContextMenu.jsx +++ b/client/components/ContextMenu.jsx @@ -1,6 +1,7 @@ /** @jsx h */ import {h} from 'preact'; import cls from 'classnames'; +import ContextMenuItem from './ContextMenuItem'; import PureComponent from '../lib/PureComponent'; import {store} from '../store'; import {elementIsOutside} from '../utils'; @@ -8,10 +9,6 @@ import {elementIsOutside} from '../utils'; import s from './ContextMenu.css'; export default class ContextMenu extends PureComponent { - constructor(props) { - super(props); - } - componentDidMount() { this.boundingRect = this.node.getBoundingClientRect(); } @@ -30,15 +27,22 @@ export default class ContextMenu extends PureComponent { [s.container]: true, [s.hidden]: !visible }); - const itemClassName = cls({ - [s.item]: true - }); + const multipleChunksSelected = store.selectedChunks.length > 1; return (
        -
      • Hide chunk
      • -
      • Hide all other chunks
      • + + Hide chunk + + + Hide all other chunks +
        -
      • Show all chunks
      • + + Show all chunks +
      ); } diff --git a/client/components/ContextMenuItem.css b/client/components/ContextMenuItem.css new file mode 100644 index 00000000..cd0f84e2 --- /dev/null +++ b/client/components/ContextMenuItem.css @@ -0,0 +1,24 @@ +.item { + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + -o-user-select: none; + user-select: none; + + cursor: pointer; + margin: 0; + padding: 8px 14px; +} + +.item:hover { + background: #ffefd7; +} + +.disabled { + cursor: default; + color: gray; +} + +.item.disabled:hover { + background: transparent; +} diff --git a/client/components/ContextMenuItem.jsx b/client/components/ContextMenuItem.jsx new file mode 100644 index 00000000..e03e6486 --- /dev/null +++ b/client/components/ContextMenuItem.jsx @@ -0,0 +1,17 @@ +/** @jsx h */ +import {h} from 'preact'; +import cls from 'classnames'; +import s from './ContextMenuItem.css'; + +function noop() { + return false; +} + +export default function ContextMenuItem({children, disabled, onClick}) { + const className = cls({ + [s.item]: true, + [s.disabled]: disabled + }); + const handler = disabled ? noop : onClick; + return (
    • {children}
    • ); +} From 4598e12f6f4db1b06173e0cec8f67ba673cb82d4 Mon Sep 17 00:00:00 2001 From: Ben Regenspan Date: Mon, 8 Apr 2019 09:44:27 -0400 Subject: [PATCH 28/31] Documentation: fix incorrect action name --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 11bf6de9..20de600b 100644 --- a/README.md +++ b/README.md @@ -161,7 +161,7 @@ The Sidebar Menu can be opened by clicking the `>` button at the top left of the The Chunk Context Menu can be opened by right-clicking or `Ctrl`-clicking on a specific chunk in the report. It provides the following options: * **Hide chunk:** Hides the selected chunk - * **Filter to chunk:** Hides all chunks besides the selected one + * **Hide all other chunks:** Hides all chunks besides the selected one * **Show all chunks:** Un-hides any hidden chunks, returning the report to its initial, unfiltered view

      Troubleshooting

      From d9c96a36f82d71394226827967b5bb0dbbe488b0 Mon Sep 17 00:00:00 2001 From: Ben Regenspan Date: Mon, 8 Apr 2019 09:47:12 -0400 Subject: [PATCH 29/31] Remove manually-added CSS browser prefixes --- client/components/ContextMenuItem.css | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/client/components/ContextMenuItem.css b/client/components/ContextMenuItem.css index cd0f84e2..a33bc962 100644 --- a/client/components/ContextMenuItem.css +++ b/client/components/ContextMenuItem.css @@ -1,13 +1,8 @@ .item { - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - -o-user-select: none; - user-select: none; - cursor: pointer; margin: 0; padding: 8px 14px; + user-select: none; } .item:hover { From 83adbbbfd91fd847c180f72c0fdc9e6b55639650 Mon Sep 17 00:00:00 2001 From: Ben Regenspan Date: Mon, 8 Apr 2019 12:58:10 -0400 Subject: [PATCH 30/31] Re-open context menu immediately when different bundle is selected --- client/components/ContextMenu.jsx | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/client/components/ContextMenu.jsx b/client/components/ContextMenu.jsx index 35c78969..0a5db3a8 100644 --- a/client/components/ContextMenu.jsx +++ b/client/components/ContextMenu.jsx @@ -70,8 +70,15 @@ export default class ContextMenu extends PureComponent { this.hide(); } + /** + * Handle document-wide `mousedown` events to detect clicks + * outside the context menu. + * @param {MouseEvent} e - DOM mouse event object + * @returns {void} + */ handleDocumentMousedown = (e) => { - if (elementIsOutside(e.target, this.node)) { + const isSecondaryClick = e.ctrlKey || e.button === 2; + if (!isSecondaryClick && elementIsOutside(e.target, this.node)) { e.preventDefault(); e.stopPropagation(); this.hide(); From 2b53d43694fed86d61bdd5981907c3144391c604 Mon Sep 17 00:00:00 2001 From: Ben Regenspan Date: Mon, 8 Apr 2019 13:10:38 -0400 Subject: [PATCH 31/31] Document event type --- client/components/Treemap.jsx | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/client/components/Treemap.jsx b/client/components/Treemap.jsx index 7f37a027..5e83124f 100644 --- a/client/components/Treemap.jsx +++ b/client/components/Treemap.jsx @@ -89,6 +89,12 @@ export default class Treemap extends Component { }; } }, + /** + * Handle Foamtree's "group clicked" event + * @param {FoamtreeEvent} event - Foamtree event object + * (see https://get.carrotsearch.com/foamtree/demo/api/index.html#event-details) + * @returns {void} + */ onGroupClick(event) { preventDefault(event); if ((event.ctrlKey || event.secondary) && props.onGroupSecondaryClick) {